1
1
mirror of https://github.com/go-gitea/gitea synced 2024-12-23 00:54:28 +00:00

Merge branch 'main' into lunny/refactor_diff_funcs

This commit is contained in:
Lunny Xiao 2024-12-16 16:21:00 -08:00 committed by GitHub
commit f77c40e924
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
222 changed files with 1714 additions and 1068 deletions

View File

@ -19,6 +19,8 @@ linters:
- revive - revive
- staticcheck - staticcheck
- stylecheck - stylecheck
- tenv
- testifylint
- typecheck - typecheck
- unconvert - unconvert
- unused - unused
@ -34,6 +36,10 @@ output:
show-stats: true show-stats: true
linters-settings: linters-settings:
testifylint:
disable:
- go-require
- require-error
stylecheck: stylecheck:
checks: ["all", "-ST1005", "-ST1003"] checks: ["all", "-ST1005", "-ST1003"]
nakedret: nakedret:

View File

@ -46,7 +46,6 @@ Wim <wim@42.be> (@42wim)
Jason Song <i@wolfogre.com> (@wolfogre) Jason Song <i@wolfogre.com> (@wolfogre)
Yarden Shoham <git@yardenshoham.com> (@yardenshoham) Yarden Shoham <git@yardenshoham.com> (@yardenshoham)
Yu Tian <zettat123@gmail.com> (@Zettat123) Yu Tian <zettat123@gmail.com> (@Zettat123)
Eddie Yang <576951401@qq.com> (@yp05327)
Dong Ge <gedong_1994@163.com> (@sillyguodong) Dong Ge <gedong_1994@163.com> (@sillyguodong)
Xinyi Gong <hestergong@gmail.com> (@HesterG) Xinyi Gong <hestergong@gmail.com> (@HesterG)
wxiaoguang <wxiaoguang@gmail.com> (@wxiaoguang) wxiaoguang <wxiaoguang@gmail.com> (@wxiaoguang)

View File

@ -28,7 +28,7 @@ XGO_VERSION := go-1.23.x
AIR_PACKAGE ?= github.com/air-verse/air@v1 AIR_PACKAGE ?= github.com/air-verse/air@v1
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.60.3 GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.5.1 MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.5.1
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0
@ -377,12 +377,12 @@ lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig
.PHONY: lint-js .PHONY: lint-js
lint-js: node_modules lint-js: node_modules
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES)
# npx vue-tsc npx vue-tsc
.PHONY: lint-js-fix .PHONY: lint-js-fix
lint-js-fix: node_modules lint-js-fix: node_modules
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix
# npx vue-tsc npx vue-tsc
.PHONY: lint-css .PHONY: lint-css
lint-css: node_modules lint-css: node_modules
@ -451,10 +451,6 @@ lint-templates: .venv node_modules
lint-yaml: .venv lint-yaml: .venv
@poetry run yamllint . @poetry run yamllint .
.PHONY: tsc
tsc:
npx vue-tsc
.PHONY: watch .PHONY: watch
watch: watch:
@bash tools/watch.sh @bash tools/watch.sh

View File

@ -1040,9 +1040,13 @@ LEVEL = Info
;; Don't allow download source archive files from UI ;; Don't allow download source archive files from UI
;DISABLE_DOWNLOAD_SOURCE_ARCHIVES = false ;DISABLE_DOWNLOAD_SOURCE_ARCHIVES = false
;; Allow fork repositories without maximum number limit ;; Allow to fork repositories without maximum number limit
;ALLOW_FORK_WITHOUT_MAXIMUM_LIMIT = true ;ALLOW_FORK_WITHOUT_MAXIMUM_LIMIT = true
;; Allow to fork repositories into the same owner (user or organization)
;; This feature is experimental, not fully tested, and may be changed in the future
;ALLOW_FORK_INTO_SAME_OWNER = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[repository.editor] ;[repository.editor]

2
go.mod
View File

@ -48,7 +48,7 @@ require (
github.com/ethantkoenig/rupture v1.0.1 github.com/ethantkoenig/rupture v1.0.1
github.com/felixge/fgprof v0.9.5 github.com/felixge/fgprof v0.9.5
github.com/fsnotify/fsnotify v1.7.0 github.com/fsnotify/fsnotify v1.7.0
github.com/gliderlabs/ssh v0.3.7 github.com/gliderlabs/ssh v0.3.8
github.com/go-ap/activitypub v0.0.0-20240910141749-b4b8c8aa484c github.com/go-ap/activitypub v0.0.0-20240910141749-b4b8c8aa484c
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/chi/v5 v5.1.0

4
go.sum
View File

@ -293,8 +293,8 @@ github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0= github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0=
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A= github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A=
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
github.com/go-ap/activitypub v0.0.0-20240910141749-b4b8c8aa484c h1:82lzmsy5Nr6JA6HcLRVxGfbdSoWfW45C6jnY3zFS7Ks= github.com/go-ap/activitypub v0.0.0-20240910141749-b4b8c8aa484c h1:82lzmsy5Nr6JA6HcLRVxGfbdSoWfW45C6jnY3zFS7Ks=

View File

@ -137,7 +137,7 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
if err != nil { if err != nil {
return 0, err return 0, err
} }
run.Status = aggregateJobStatus(jobs) run.Status = AggregateJobStatus(jobs)
if run.Started.IsZero() && run.Status.IsRunning() { if run.Started.IsZero() && run.Status.IsRunning() {
run.Started = timeutil.TimeStampNow() run.Started = timeutil.TimeStampNow()
} }
@ -152,29 +152,35 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
return affected, nil return affected, nil
} }
func aggregateJobStatus(jobs []*ActionRunJob) Status { func AggregateJobStatus(jobs []*ActionRunJob) Status {
allDone := true allSuccessOrSkipped := len(jobs) != 0
allWaiting := true allSkipped := len(jobs) != 0
hasFailure := false var hasFailure, hasCancelled, hasWaiting, hasRunning, hasBlocked bool
for _, job := range jobs { for _, job := range jobs {
if !job.Status.IsDone() { allSuccessOrSkipped = allSuccessOrSkipped && (job.Status == StatusSuccess || job.Status == StatusSkipped)
allDone = false allSkipped = allSkipped && job.Status == StatusSkipped
} hasFailure = hasFailure || job.Status == StatusFailure
if job.Status != StatusWaiting && !job.Status.IsDone() { hasCancelled = hasCancelled || job.Status == StatusCancelled
allWaiting = false hasWaiting = hasWaiting || job.Status == StatusWaiting
} hasRunning = hasRunning || job.Status == StatusRunning
if job.Status == StatusFailure || job.Status == StatusCancelled { hasBlocked = hasBlocked || job.Status == StatusBlocked
hasFailure = true
}
}
if allDone {
if hasFailure {
return StatusFailure
} }
switch {
case allSkipped:
return StatusSkipped
case allSuccessOrSkipped:
return StatusSuccess return StatusSuccess
} case hasCancelled:
if allWaiting { return StatusCancelled
return StatusWaiting case hasFailure:
} return StatusFailure
case hasRunning:
return StatusRunning return StatusRunning
case hasWaiting:
return StatusWaiting
case hasBlocked:
return StatusBlocked
default:
return StatusUnknown // it shouldn't happen
}
} }

View File

@ -0,0 +1,85 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAggregateJobStatus(t *testing.T) {
testStatuses := func(expected Status, statuses []Status) {
t.Helper()
var jobs []*ActionRunJob
for _, v := range statuses {
jobs = append(jobs, &ActionRunJob{Status: v})
}
actual := AggregateJobStatus(jobs)
if !assert.Equal(t, expected, actual) {
var statusStrings []string
for _, s := range statuses {
statusStrings = append(statusStrings, s.String())
}
t.Errorf("AggregateJobStatus(%v) = %v, want %v", statusStrings, statusNames[actual], statusNames[expected])
}
}
cases := []struct {
statuses []Status
expected Status
}{
// unknown cases, maybe it shouldn't happen in real world
{[]Status{}, StatusUnknown},
{[]Status{StatusUnknown, StatusSuccess}, StatusUnknown},
{[]Status{StatusUnknown, StatusSkipped}, StatusUnknown},
{[]Status{StatusUnknown, StatusFailure}, StatusFailure},
{[]Status{StatusUnknown, StatusCancelled}, StatusCancelled},
{[]Status{StatusUnknown, StatusWaiting}, StatusWaiting},
{[]Status{StatusUnknown, StatusRunning}, StatusRunning},
{[]Status{StatusUnknown, StatusBlocked}, StatusBlocked},
// success with other status
{[]Status{StatusSuccess}, StatusSuccess},
{[]Status{StatusSuccess, StatusSkipped}, StatusSuccess}, // skipped doesn't affect success
{[]Status{StatusSuccess, StatusFailure}, StatusFailure},
{[]Status{StatusSuccess, StatusCancelled}, StatusCancelled},
{[]Status{StatusSuccess, StatusWaiting}, StatusWaiting},
{[]Status{StatusSuccess, StatusRunning}, StatusRunning},
{[]Status{StatusSuccess, StatusBlocked}, StatusBlocked},
// any cancelled, then cancelled
{[]Status{StatusCancelled}, StatusCancelled},
{[]Status{StatusCancelled, StatusSuccess}, StatusCancelled},
{[]Status{StatusCancelled, StatusSkipped}, StatusCancelled},
{[]Status{StatusCancelled, StatusFailure}, StatusCancelled},
{[]Status{StatusCancelled, StatusWaiting}, StatusCancelled},
{[]Status{StatusCancelled, StatusRunning}, StatusCancelled},
{[]Status{StatusCancelled, StatusBlocked}, StatusCancelled},
// failure with other status, fail fast
// Should "running" win? Maybe no: old code does make "running" win, but GitHub does fail fast.
{[]Status{StatusFailure}, StatusFailure},
{[]Status{StatusFailure, StatusSuccess}, StatusFailure},
{[]Status{StatusFailure, StatusSkipped}, StatusFailure},
{[]Status{StatusFailure, StatusCancelled}, StatusCancelled},
{[]Status{StatusFailure, StatusWaiting}, StatusFailure},
{[]Status{StatusFailure, StatusRunning}, StatusFailure},
{[]Status{StatusFailure, StatusBlocked}, StatusFailure},
// skipped with other status
// TODO: need to clarify whether a PR with "skipped" job status is considered as "mergeable" or not.
{[]Status{StatusSkipped}, StatusSkipped},
{[]Status{StatusSkipped, StatusSuccess}, StatusSuccess},
{[]Status{StatusSkipped, StatusFailure}, StatusFailure},
{[]Status{StatusSkipped, StatusCancelled}, StatusCancelled},
{[]Status{StatusSkipped, StatusWaiting}, StatusWaiting},
{[]Status{StatusSkipped, StatusRunning}, StatusRunning},
{[]Status{StatusSkipped, StatusBlocked}, StatusBlocked},
}
for _, c := range cases {
testStatuses(c.expected, c.statuses)
}
}

View File

@ -17,7 +17,7 @@ func TestGetLatestRunnerToken(t *testing.T) {
token := unittest.AssertExistsAndLoadBean(t, &ActionRunnerToken{ID: 3}) token := unittest.AssertExistsAndLoadBean(t, &ActionRunnerToken{ID: 3})
expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0) expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, token, expectedToken) assert.EqualValues(t, expectedToken, token)
} }
func TestNewRunnerToken(t *testing.T) { func TestNewRunnerToken(t *testing.T) {
@ -26,7 +26,7 @@ func TestNewRunnerToken(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0) expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, token, expectedToken) assert.EqualValues(t, expectedToken, token)
} }
func TestUpdateRunnerToken(t *testing.T) { func TestUpdateRunnerToken(t *testing.T) {
@ -36,5 +36,5 @@ func TestUpdateRunnerToken(t *testing.T) {
assert.NoError(t, UpdateRunnerToken(db.DefaultContext, token)) assert.NoError(t, UpdateRunnerToken(db.DefaultContext, token))
expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0) expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, token, expectedToken) assert.EqualValues(t, expectedToken, token)
} }

View File

@ -4,7 +4,6 @@
package activities_test package activities_test
import ( import (
"fmt"
"testing" "testing"
"time" "time"
@ -91,11 +90,11 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, actions, contributions, "invalid action count: did the test data became too old?") assert.Len(t, actions, contributions, "invalid action count: did the test data became too old?")
assert.Equal(t, count, int64(contributions)) assert.Equal(t, count, int64(contributions))
assert.Equal(t, tc.CountResult, contributions, fmt.Sprintf("testcase '%s'", tc.desc)) assert.Equal(t, tc.CountResult, contributions, "testcase '%s'", tc.desc)
// Test JSON rendering // Test JSON rendering
jsonData, err := json.Marshal(heatmap) jsonData, err := json.Marshal(heatmap)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, tc.JSONResult, string(jsonData)) assert.JSONEq(t, tc.JSONResult, string(jsonData))
} }
} }

View File

@ -18,7 +18,7 @@ func TestOAuth2Application_GenerateClientSecret(t *testing.T) {
app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1}) app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
secret, err := app.GenerateClientSecret(db.DefaultContext) secret, err := app.GenerateClientSecret(db.DefaultContext)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, len(secret) > 0) assert.NotEmpty(t, secret)
unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1, ClientSecret: app.ClientSecret}) unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1, ClientSecret: app.ClientSecret})
} }
@ -165,7 +165,7 @@ func TestOAuth2Grant_GenerateNewAuthorizationCode(t *testing.T) {
code, err := grant.GenerateNewAuthorizationCode(db.DefaultContext, "https://example2.com/callback", "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg", "S256") code, err := grant.GenerateNewAuthorizationCode(db.DefaultContext, "https://example2.com/callback", "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg", "S256")
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, code) assert.NotNil(t, code)
assert.True(t, len(code.Code) > 32) // secret length > 32 assert.Greater(t, len(code.Code), 32) // secret length > 32
} }
func TestOAuth2Grant_TableName(t *testing.T) { func TestOAuth2Grant_TableName(t *testing.T) {

View File

@ -38,8 +38,6 @@ func TestIterate(t *testing.T) {
if !has { if !has {
return db.ErrNotExist{Resource: "repo_unit", ID: repoUnit.ID} return db.ErrNotExist{Resource: "repo_unit", ID: repoUnit.ID}
} }
assert.EqualValues(t, repoUnit.RepoID, repoUnit.RepoID)
assert.EqualValues(t, repoUnit.CreatedUnix, repoUnit.CreatedUnix)
return nil return nil
}) })
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -36,3 +36,41 @@
updated: 1683636626 updated: 1683636626
need_approval: 0 need_approval: 0
approved_by: 0 approved_by: 0
-
id: 793
title: "job output"
repo_id: 4
owner_id: 1
workflow_id: "test.yaml"
index: 189
trigger_user_id: 1
ref: "refs/heads/master"
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
event: "push"
is_fork_pull_request: 0
status: 1
started: 1683636528
stopped: 1683636626
created: 1683636108
updated: 1683636626
need_approval: 0
approved_by: 0
-
id: 794
title: "job output"
repo_id: 4
owner_id: 1
workflow_id: "test.yaml"
index: 190
trigger_user_id: 1
ref: "refs/heads/test"
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
event: "push"
is_fork_pull_request: 0
status: 1
started: 1683636528
stopped: 1683636626
created: 1683636108
updated: 1683636626
need_approval: 0
approved_by: 0

View File

@ -26,3 +26,46 @@
status: 1 status: 1
started: 1683636528 started: 1683636528
stopped: 1683636626 stopped: 1683636626
-
id: 194
run_id: 793
repo_id: 4
owner_id: 1
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
is_fork_pull_request: 0
name: job1 (1)
attempt: 1
job_id: job1
task_id: 49
status: 1
started: 1683636528
stopped: 1683636626
-
id: 195
run_id: 793
repo_id: 4
owner_id: 1
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
is_fork_pull_request: 0
name: job1 (2)
attempt: 1
job_id: job1
task_id: 50
status: 1
started: 1683636528
stopped: 1683636626
-
id: 196
run_id: 793
repo_id: 4
owner_id: 1
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
is_fork_pull_request: 0
name: job2
attempt: 1
job_id: job2
needs: [job1]
task_id: 51
status: 5
started: 1683636528
stopped: 1683636626

View File

@ -57,3 +57,63 @@
log_length: 707 log_length: 707
log_size: 90179 log_size: 90179
log_expired: 0 log_expired: 0
-
id: 49
job_id: 194
attempt: 1
runner_id: 1
status: 1 # success
started: 1683636528
stopped: 1683636626
repo_id: 4
owner_id: 1
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
is_fork_pull_request: 0
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784220
token_salt: ffffffffff
token_last_eight: ffffffff
log_filename: artifact-test2/2f/47.log
log_in_storage: 1
log_length: 707
log_size: 90179
log_expired: 0
-
id: 50
job_id: 195
attempt: 1
runner_id: 1
status: 1 # success
started: 1683636528
stopped: 1683636626
repo_id: 4
owner_id: 1
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
is_fork_pull_request: 0
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784221
token_salt: ffffffffff
token_last_eight: ffffffff
log_filename: artifact-test2/2f/47.log
log_in_storage: 1
log_length: 707
log_size: 90179
log_expired: 0
-
id: 51
job_id: 196
attempt: 1
runner_id: 1
status: 6 # running
started: 1683636528
stopped: 1683636626
repo_id: 4
owner_id: 1
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
is_fork_pull_request: 0
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784222
token_salt: ffffffffff
token_last_eight: ffffffff
log_filename: artifact-test2/2f/47.log
log_in_storage: 1
log_length: 707
log_size: 90179
log_expired: 0

View File

@ -0,0 +1,20 @@
-
id: 1
task_id: 49
output_key: output_a
output_value: abc
-
id: 2
task_id: 49
output_key: output_b
output_value: ''
-
id: 3
task_id: 50
output_key: output_a
output_value: ''
-
id: 4
task_id: 50
output_key: output_b
output_value: bbb

View File

@ -81,3 +81,15 @@
is_deleted: false is_deleted: false
deleted_by_id: 0 deleted_by_id: 0
deleted_unix: 0 deleted_unix: 0
-
id: 15
repo_id: 4
name: 'master'
commit_id: 'c7cd3cd144e6d23c9d6f3d07e52b2c1a956e0338'
commit_message: 'add Readme'
commit_time: 1588147171
pusher_id: 13
is_deleted: false
deleted_by_id: 0
deleted_unix: 0

View File

@ -34,7 +34,7 @@ func TestGetCommitStatuses(t *testing.T) {
SHA: sha1, SHA: sha1,
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, int(maxResults), 5) assert.Equal(t, 5, int(maxResults))
assert.Len(t, statuses, 5) assert.Len(t, statuses, 5)
assert.Equal(t, "ci/awesomeness", statuses[0].Context) assert.Equal(t, "ci/awesomeness", statuses[0].Context)
@ -63,7 +63,7 @@ func TestGetCommitStatuses(t *testing.T) {
SHA: sha1, SHA: sha1,
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, int(maxResults), 5) assert.Equal(t, 5, int(maxResults))
assert.Empty(t, statuses) assert.Empty(t, statuses)
} }

View File

@ -4,7 +4,6 @@
package git package git
import ( import (
"fmt"
"testing" "testing"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -76,7 +75,7 @@ func TestBranchRuleMatch(t *testing.T) {
infact = " not" infact = " not"
} }
assert.EqualValues(t, kase.ExpectedMatch, pb.Match(kase.BranchName), assert.EqualValues(t, kase.ExpectedMatch, pb.Match(kase.BranchName),
fmt.Sprintf("%s should%s match %s but it is%s", kase.BranchName, should, kase.Rule, infact), "%s should%s match %s but it is%s", kase.BranchName, should, kase.Rule, infact,
) )
} }
} }

View File

@ -64,7 +64,7 @@ func TestFetchCodeComments(t *testing.T) {
} }
func TestAsCommentType(t *testing.T) { func TestAsCommentType(t *testing.T) {
assert.Equal(t, issues_model.CommentType(0), issues_model.CommentTypeComment) assert.Equal(t, issues_model.CommentTypeComment, issues_model.CommentType(0))
assert.Equal(t, issues_model.CommentTypeUndefined, issues_model.AsCommentType("")) assert.Equal(t, issues_model.CommentTypeUndefined, issues_model.AsCommentType(""))
assert.Equal(t, issues_model.CommentTypeUndefined, issues_model.AsCommentType("nonsense")) assert.Equal(t, issues_model.CommentTypeUndefined, issues_model.AsCommentType("nonsense"))
assert.Equal(t, issues_model.CommentTypeComment, issues_model.AsCommentType("comment")) assert.Equal(t, issues_model.CommentTypeComment, issues_model.AsCommentType("comment"))

View File

@ -125,7 +125,10 @@ type Issue struct {
IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not. IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not.
PullRequest *PullRequest `xorm:"-"` PullRequest *PullRequest `xorm:"-"`
NumComments int NumComments int
// TODO: RemoveIssueRef: see "repo/issue/branch_selector_field.tmpl"
Ref string Ref string
PinOrder int `xorm:"DEFAULT 0"` PinOrder int `xorm:"DEFAULT 0"`
DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"` DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"`

View File

@ -18,12 +18,12 @@ func RecalculateIssueIndexForRepo(ctx context.Context, repoID int64) error {
} }
defer committer.Close() defer committer.Close()
var max int64 var maxIndex int64
if _, err = db.GetEngine(ctx).Select(" MAX(`index`)").Table("issue").Where("repo_id=?", repoID).Get(&max); err != nil { if _, err = db.GetEngine(ctx).Select(" MAX(`index`)").Table("issue").Where("repo_id=?", repoID).Get(&maxIndex); err != nil {
return err return err
} }
if err = db.SyncMaxResourceIndex(ctx, "issue_index", repoID, max); err != nil { if err = db.SyncMaxResourceIndex(ctx, "issue_index", repoID, maxIndex); err != nil {
return err return err
} }

View File

@ -434,7 +434,7 @@ func assertCreateIssues(t *testing.T, isPull bool) {
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}) milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1})
assert.EqualValues(t, milestone.ID, 1) assert.EqualValues(t, 1, milestone.ID)
reaction := &issues_model.Reaction{ reaction := &issues_model.Reaction{
Type: "heart", Type: "heart",
UserID: owner.ID, UserID: owner.ID,

View File

@ -48,17 +48,17 @@ func TestGetIssueWatchers(t *testing.T) {
iws, err := issues_model.GetIssueWatchers(db.DefaultContext, 1, db.ListOptions{}) iws, err := issues_model.GetIssueWatchers(db.DefaultContext, 1, db.ListOptions{})
assert.NoError(t, err) assert.NoError(t, err)
// Watcher is inactive, thus 0 // Watcher is inactive, thus 0
assert.Len(t, iws, 0) assert.Empty(t, iws)
iws, err = issues_model.GetIssueWatchers(db.DefaultContext, 2, db.ListOptions{}) iws, err = issues_model.GetIssueWatchers(db.DefaultContext, 2, db.ListOptions{})
assert.NoError(t, err) assert.NoError(t, err)
// Watcher is explicit not watching // Watcher is explicit not watching
assert.Len(t, iws, 0) assert.Empty(t, iws)
iws, err = issues_model.GetIssueWatchers(db.DefaultContext, 5, db.ListOptions{}) iws, err = issues_model.GetIssueWatchers(db.DefaultContext, 5, db.ListOptions{})
assert.NoError(t, err) assert.NoError(t, err)
// Issue has no Watchers // Issue has no Watchers
assert.Len(t, iws, 0) assert.Empty(t, iws)
iws, err = issues_model.GetIssueWatchers(db.DefaultContext, 7, db.ListOptions{}) iws, err = issues_model.GetIssueWatchers(db.DefaultContext, 7, db.ListOptions{})
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -31,12 +31,12 @@ func TestLabel_LoadSelectedLabelsAfterClick(t *testing.T) {
// First test : with negative and scope // First test : with negative and scope
label.LoadSelectedLabelsAfterClick([]int64{1, -8}, []string{"", "scope"}) label.LoadSelectedLabelsAfterClick([]int64{1, -8}, []string{"", "scope"})
assert.Equal(t, "1", label.QueryString) assert.Equal(t, "1", label.QueryString)
assert.Equal(t, true, label.IsSelected) assert.True(t, label.IsSelected)
// Second test : with duplicates // Second test : with duplicates
label.LoadSelectedLabelsAfterClick([]int64{1, 7, 1, 7, 7}, []string{"", "scope", "", "scope", "scope"}) label.LoadSelectedLabelsAfterClick([]int64{1, 7, 1, 7, 7}, []string{"", "scope", "", "scope", "scope"})
assert.Equal(t, "1,8", label.QueryString) assert.Equal(t, "1,8", label.QueryString)
assert.Equal(t, false, label.IsSelected) assert.False(t, label.IsSelected)
// Third test : empty set // Third test : empty set
label.LoadSelectedLabelsAfterClick([]int64{}, []string{}) label.LoadSelectedLabelsAfterClick([]int64{}, []string{})
@ -248,7 +248,7 @@ func TestGetLabelsByIssueID(t *testing.T) {
labels, err = issues_model.GetLabelsByIssueID(db.DefaultContext, unittest.NonexistentID) labels, err = issues_model.GetLabelsByIssueID(db.DefaultContext, unittest.NonexistentID)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, labels, 0) assert.Empty(t, labels)
} }
func TestUpdateLabel(t *testing.T) { func TestUpdateLabel(t *testing.T) {
@ -271,7 +271,7 @@ func TestUpdateLabel(t *testing.T) {
assert.EqualValues(t, label.Color, newLabel.Color) assert.EqualValues(t, label.Color, newLabel.Color)
assert.EqualValues(t, label.Name, newLabel.Name) assert.EqualValues(t, label.Name, newLabel.Name)
assert.EqualValues(t, label.Description, newLabel.Description) assert.EqualValues(t, label.Description, newLabel.Description)
assert.EqualValues(t, newLabel.ArchivedUnix, 0) assert.EqualValues(t, 0, newLabel.ArchivedUnix)
unittest.CheckConsistencyFor(t, &issues_model.Label{}, &repo_model.Repository{}) unittest.CheckConsistencyFor(t, &issues_model.Label{}, &repo_model.Repository{})
} }

View File

@ -87,7 +87,7 @@ func TestGetMilestonesByRepoID(t *testing.T) {
IsClosed: optional.Some(false), IsClosed: optional.Some(false),
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, milestones, 0) assert.Empty(t, milestones)
} }
func TestGetMilestones(t *testing.T) { func TestGetMilestones(t *testing.T) {

View File

@ -40,7 +40,7 @@ func TestPullRequestList_LoadReviewCommentsCounts(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, reviewComments, 2) assert.Len(t, reviewComments, 2)
for _, pr := range prs { for _, pr := range prs {
assert.EqualValues(t, reviewComments[pr.IssueID], 1) assert.EqualValues(t, 1, reviewComments[pr.IssueID])
} }
} }

View File

@ -83,7 +83,7 @@ func TestLoadRequestedReviewers(t *testing.T) {
assert.NoError(t, pull.LoadIssue(db.DefaultContext)) assert.NoError(t, pull.LoadIssue(db.DefaultContext))
issue := pull.Issue issue := pull.Issue
assert.NoError(t, issue.LoadRepo(db.DefaultContext)) assert.NoError(t, issue.LoadRepo(db.DefaultContext))
assert.Len(t, pull.RequestedReviewers, 0) assert.Empty(t, pull.RequestedReviewers)
user1, err := user_model.GetUserByID(db.DefaultContext, 1) user1, err := user_model.GetUserByID(db.DefaultContext, 1)
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -32,7 +32,7 @@ func TestCancelStopwatch(t *testing.T) {
_ = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{Type: issues_model.CommentTypeCancelTracking, PosterID: user1.ID, IssueID: issue1.ID}) _ = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{Type: issues_model.CommentTypeCancelTracking, PosterID: user1.ID, IssueID: issue1.ID})
assert.Nil(t, issues_model.CancelStopwatch(db.DefaultContext, user1, issue2)) assert.NoError(t, issues_model.CancelStopwatch(db.DefaultContext, user1, issue2))
} }
func TestStopwatchExists(t *testing.T) { func TestStopwatchExists(t *testing.T) {

View File

@ -50,7 +50,7 @@ func TestGetTrackedTimes(t *testing.T) {
times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{IssueID: -1}) times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{IssueID: -1})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, times, 0) assert.Empty(t, times)
// by User // by User
times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{UserID: 1}) times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{UserID: 1})
@ -60,7 +60,7 @@ func TestGetTrackedTimes(t *testing.T) {
times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{UserID: 3}) times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{UserID: 3})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, times, 0) assert.Empty(t, times)
// by Repo // by Repo
times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{RepositoryID: 2}) times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{RepositoryID: 2})
@ -69,7 +69,7 @@ func TestGetTrackedTimes(t *testing.T) {
assert.Equal(t, int64(1), times[0].Time) assert.Equal(t, int64(1), times[0].Time)
issue, err := issues_model.GetIssueByID(db.DefaultContext, times[0].IssueID) issue, err := issues_model.GetIssueByID(db.DefaultContext, times[0].IssueID)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, issue.RepoID, int64(2)) assert.Equal(t, int64(2), issue.RepoID)
times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{RepositoryID: 1}) times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{RepositoryID: 1})
assert.NoError(t, err) assert.NoError(t, err)
@ -77,7 +77,7 @@ func TestGetTrackedTimes(t *testing.T) {
times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{RepositoryID: 10}) times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{RepositoryID: 10})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, times, 0) assert.Empty(t, times)
} }
func TestTotalTimesForEachUser(t *testing.T) { func TestTotalTimesForEachUser(t *testing.T) {

View File

@ -56,8 +56,8 @@ func Test_AddRepoIDForAttachment(t *testing.T) {
err := x.Table("attachment").Where("issue_id > 0").Find(&issueAttachments) err := x.Table("attachment").Where("issue_id > 0").Find(&issueAttachments)
assert.NoError(t, err) assert.NoError(t, err)
for _, attach := range issueAttachments { for _, attach := range issueAttachments {
assert.Greater(t, attach.RepoID, int64(0)) assert.Positive(t, attach.RepoID)
assert.Greater(t, attach.IssueID, int64(0)) assert.Positive(t, attach.IssueID)
var issue Issue var issue Issue
has, err := x.ID(attach.IssueID).Get(&issue) has, err := x.ID(attach.IssueID).Get(&issue)
assert.NoError(t, err) assert.NoError(t, err)
@ -69,8 +69,8 @@ func Test_AddRepoIDForAttachment(t *testing.T) {
err = x.Table("attachment").Where("release_id > 0").Find(&releaseAttachments) err = x.Table("attachment").Where("release_id > 0").Find(&releaseAttachments)
assert.NoError(t, err) assert.NoError(t, err)
for _, attach := range releaseAttachments { for _, attach := range releaseAttachments {
assert.Greater(t, attach.RepoID, int64(0)) assert.Positive(t, attach.RepoID)
assert.Greater(t, attach.ReleaseID, int64(0)) assert.Positive(t, attach.ReleaseID)
var release Release var release Release
has, err := x.ID(attach.ReleaseID).Get(&release) has, err := x.ID(attach.ReleaseID).Get(&release)
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -107,12 +107,12 @@ func Test_RepositoryFormat(t *testing.T) {
repo = new(Repository) repo = new(Repository)
ok, err := x.ID(2).Get(repo) ok, err := x.ID(2).Get(repo)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, true, ok) assert.True(t, ok)
assert.EqualValues(t, "sha1", repo.ObjectFormatName) assert.EqualValues(t, "sha1", repo.ObjectFormatName)
repo = new(Repository) repo = new(Repository)
ok, err = x.ID(id).Get(repo) ok, err = x.ID(id).Get(repo)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, true, ok) assert.True(t, ok)
assert.EqualValues(t, "sha256", repo.ObjectFormatName) assert.EqualValues(t, "sha256", repo.ObjectFormatName)
} }

View File

@ -39,7 +39,7 @@ func Test_AddUniqueIndexForProjectIssue(t *testing.T) {
tables, err := x.DBMetas() tables, err := x.DBMetas()
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 1, len(tables)) assert.Len(t, tables, 1)
found := false found := false
for _, index := range tables[0].Indexes { for _, index := range tables[0].Indexes {
if index.Type == schemas.UniqueType { if index.Type == schemas.UniqueType {

View File

@ -40,7 +40,7 @@ func TestFindOrgs(t *testing.T) {
IncludePrivate: false, IncludePrivate: false,
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, orgs, 0) assert.Empty(t, orgs)
total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
UserID: 4, UserID: 4,

View File

@ -283,7 +283,7 @@ func TestGetOrgUsersByOrgID(t *testing.T) {
OrgID: unittest.NonexistentID, OrgID: unittest.NonexistentID,
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, orgUsers, 0) assert.Empty(t, orgUsers)
} }
func TestChangeOrgUserStatus(t *testing.T) { func TestChangeOrgUserStatus(t *testing.T) {

View File

@ -15,7 +15,7 @@ func TestAccessMode(t *testing.T) {
m := ParseAccessMode(name) m := ParseAccessMode(name)
assert.Equal(t, AccessMode(i), m) assert.Equal(t, AccessMode(i), m)
} }
assert.Equal(t, AccessMode(4), AccessModeOwner) assert.Equal(t, AccessModeOwner, AccessMode(4))
assert.Equal(t, "owner", AccessModeOwner.ToString()) assert.Equal(t, "owner", AccessModeOwner.ToString())
assert.Equal(t, AccessModeNone, ParseAccessMode("owner")) assert.Equal(t, AccessModeNone, ParseAccessMode("owner"))
assert.Equal(t, AccessModeNone, ParseAccessMode("invalid")) assert.Equal(t, AccessModeNone, ParseAccessMode("invalid"))

View File

@ -5,7 +5,6 @@ package project
import ( import (
"fmt" "fmt"
"strings"
"testing" "testing"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -66,7 +65,7 @@ func Test_moveIssuesToAnotherColumn(t *testing.T) {
issues, err = column1.GetIssues(db.DefaultContext) issues, err = column1.GetIssues(db.DefaultContext)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, issues, 0) assert.Empty(t, issues)
issues, err = column2.GetIssues(db.DefaultContext) issues, err = column2.GetIssues(db.DefaultContext)
assert.NoError(t, err) assert.NoError(t, err)
@ -123,5 +122,5 @@ func Test_NewColumn(t *testing.T) {
ProjectID: project1.ID, ProjectID: project1.ID,
}) })
assert.Error(t, err) assert.Error(t, err)
assert.True(t, strings.Contains(err.Error(), "maximum number of columns reached")) assert.Contains(t, err.Error(), "maximum number of columns reached")
} }

View File

@ -144,8 +144,8 @@ func TestGetRepositoryByURL(t *testing.T) {
assert.NotNil(t, repo) assert.NotNil(t, repo)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, repo.ID, int64(2)) assert.Equal(t, int64(2), repo.ID)
assert.Equal(t, repo.OwnerID, int64(2)) assert.Equal(t, int64(2), repo.OwnerID)
} }
test(t, "https://try.gitea.io/user2/repo2") test(t, "https://try.gitea.io/user2/repo2")
@ -159,8 +159,8 @@ func TestGetRepositoryByURL(t *testing.T) {
assert.NotNil(t, repo) assert.NotNil(t, repo)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, repo.ID, int64(2)) assert.Equal(t, int64(2), repo.ID)
assert.Equal(t, repo.OwnerID, int64(2)) assert.Equal(t, int64(2), repo.OwnerID)
} }
test(t, "git+ssh://sshuser@try.gitea.io/user2/repo2") test(t, "git+ssh://sshuser@try.gitea.io/user2/repo2")
@ -177,8 +177,8 @@ func TestGetRepositoryByURL(t *testing.T) {
assert.NotNil(t, repo) assert.NotNil(t, repo)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, repo.ID, int64(2)) assert.Equal(t, int64(2), repo.ID)
assert.Equal(t, repo.OwnerID, int64(2)) assert.Equal(t, int64(2), repo.OwnerID)
} }
test(t, "sshuser@try.gitea.io:user2/repo2") test(t, "sshuser@try.gitea.io:user2/repo2")

View File

@ -52,7 +52,7 @@ func TestRepository_GetStargazers2(t *testing.T) {
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
gazers, err := repo_model.GetStargazers(db.DefaultContext, repo, db.ListOptions{Page: 0}) gazers, err := repo_model.GetStargazers(db.DefaultContext, repo, db.ListOptions{Page: 0})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, gazers, 0) assert.Empty(t, gazers)
} }
func TestClearRepoStars(t *testing.T) { func TestClearRepoStars(t *testing.T) {
@ -71,5 +71,5 @@ func TestClearRepoStars(t *testing.T) {
gazers, err := repo_model.GetStargazers(db.DefaultContext, repo, db.ListOptions{Page: 0}) gazers, err := repo_model.GetStargazers(db.DefaultContext, repo, db.ListOptions{Page: 0})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, gazers, 0) assert.Empty(t, gazers)
} }

View File

@ -21,7 +21,7 @@ func TestRepoAssignees(t *testing.T) {
users, err := repo_model.GetRepoAssignees(db.DefaultContext, repo2) users, err := repo_model.GetRepoAssignees(db.DefaultContext, repo2)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, users, 1) assert.Len(t, users, 1)
assert.Equal(t, users[0].ID, int64(2)) assert.Equal(t, int64(2), users[0].ID)
repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21}) repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21})
users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21) users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21)

View File

@ -41,7 +41,7 @@ func TestGetWatchers(t *testing.T) {
watches, err = repo_model.GetWatchers(db.DefaultContext, unittest.NonexistentID) watches, err = repo_model.GetWatchers(db.DefaultContext, unittest.NonexistentID)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, watches, 0) assert.Empty(t, watches)
} }
func TestRepository_GetWatchers(t *testing.T) { func TestRepository_GetWatchers(t *testing.T) {
@ -58,7 +58,7 @@ func TestRepository_GetWatchers(t *testing.T) {
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 9}) repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 9})
watchers, err = repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1}) watchers, err = repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, watchers, 0) assert.Empty(t, watchers)
} }
func TestWatchIfAuto(t *testing.T) { func TestWatchIfAuto(t *testing.T) {

View File

@ -79,7 +79,7 @@ func AssertExistsAndLoadMap(t assert.TestingT, table string, conditions ...any)
e := db.GetEngine(db.DefaultContext).Table(table) e := db.GetEngine(db.DefaultContext).Table(table)
res, err := whereOrderConditions(e, conditions).Query() res, err := whereOrderConditions(e, conditions).Query()
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, len(res) == 1, assert.Len(t, res, 1,
"Expected to find one row in %s (with conditions %+v), but found %d", "Expected to find one row in %s (with conditions %+v), but found %d",
table, conditions, len(res), table, conditions, len(res),
) )

View File

@ -97,8 +97,7 @@ func TestListEmails(t *testing.T) {
} }
emails, count, err := user_model.SearchEmails(db.DefaultContext, opts) emails, count, err := user_model.SearchEmails(db.DefaultContext, opts)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEqual(t, int64(0), count) assert.Greater(t, count, int64(5))
assert.True(t, count > 5)
contains := func(match func(s *user_model.SearchEmailResult) bool) bool { contains := func(match func(s *user_model.SearchEmailResult) bool) bool {
for _, v := range emails { for _, v := range emails {

View File

@ -56,5 +56,5 @@ func TestSettings(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
settings, err = user_model.GetUserAllSettings(db.DefaultContext, 99) settings, err = user_model.GetUserAllSettings(db.DefaultContext, 99)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, settings, 0) assert.Empty(t, settings)
} }

View File

@ -201,7 +201,7 @@ func TestNewGitSig(t *testing.T) {
assert.NotContains(t, sig.Name, "<") assert.NotContains(t, sig.Name, "<")
assert.NotContains(t, sig.Name, ">") assert.NotContains(t, sig.Name, ">")
assert.NotContains(t, sig.Name, "\n") assert.NotContains(t, sig.Name, "\n")
assert.NotEqual(t, len(strings.TrimSpace(sig.Name)), 0) assert.NotEmpty(t, strings.TrimSpace(sig.Name))
} }
} }
@ -216,7 +216,7 @@ func TestDisplayName(t *testing.T) {
if len(strings.TrimSpace(user.FullName)) == 0 { if len(strings.TrimSpace(user.FullName)) == 0 {
assert.Equal(t, user.Name, displayName) assert.Equal(t, user.Name, displayName)
} }
assert.NotEqual(t, len(strings.TrimSpace(displayName)), 0) assert.NotEmpty(t, strings.TrimSpace(displayName))
} }
} }
@ -322,15 +322,15 @@ func TestGetMaileableUsersByIDs(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, results, 1) assert.Len(t, results, 1)
if len(results) > 1 { if len(results) > 1 {
assert.Equal(t, results[0].ID, 1) assert.Equal(t, 1, results[0].ID)
} }
results, err = user_model.GetMaileableUsersByIDs(db.DefaultContext, []int64{1, 4}, true) results, err = user_model.GetMaileableUsersByIDs(db.DefaultContext, []int64{1, 4}, true)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, results, 2) assert.Len(t, results, 2)
if len(results) > 2 { if len(results) > 2 {
assert.Equal(t, results[0].ID, 1) assert.Equal(t, 1, results[0].ID)
assert.Equal(t, results[1].ID, 4) assert.Equal(t, 4, results[1].ID)
} }
} }
@ -499,7 +499,7 @@ func Test_ValidateUser(t *testing.T) {
{ID: 2, Visibility: structs.VisibleTypePrivate}: true, {ID: 2, Visibility: structs.VisibleTypePrivate}: true,
} }
for kase, expected := range kases { for kase, expected := range kases {
assert.EqualValues(t, expected, nil == user_model.ValidateUser(kase), fmt.Sprintf("case: %+v", kase)) assert.EqualValues(t, expected, nil == user_model.ValidateUser(kase), "case: %+v", kase)
} }
} }
@ -570,11 +570,11 @@ func TestDisabledUserFeatures(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
assert.Len(t, setting.Admin.UserDisabledFeatures.Values(), 0) assert.Empty(t, setting.Admin.UserDisabledFeatures.Values())
// no features should be disabled with a plain login type // no features should be disabled with a plain login type
assert.LessOrEqual(t, user.LoginType, auth.Plain) assert.LessOrEqual(t, user.LoginType, auth.Plain)
assert.Len(t, user_model.DisabledFeaturesWithLoginType(user).Values(), 0) assert.Empty(t, user_model.DisabledFeaturesWithLoginType(user).Values())
for _, f := range testValues.Values() { for _, f := range testValues.Values() {
assert.False(t, user_model.IsFeatureDisabledWithLoginType(user, f)) assert.False(t, user_model.IsFeatureDisabledWithLoginType(user, f))
} }
@ -600,5 +600,5 @@ func TestGetInactiveUsers(t *testing.T) {
interval := time.Now().Unix() - 1730468968 + 3600*24 interval := time.Now().Unix() - 1730468968 + 3600*24
users, err = user_model.GetInactiveUsers(db.DefaultContext, time.Duration(interval*int64(time.Second))) users, err = user_model.GetInactiveUsers(db.DefaultContext, time.Duration(interval*int64(time.Second)))
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, users, 0) assert.Empty(t, users)
} }

View File

@ -43,7 +43,7 @@ func TestWebhook_History(t *testing.T) {
webhook = unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2}) webhook = unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2})
tasks, err = webhook.History(db.DefaultContext, 0) tasks, err = webhook.History(db.DefaultContext, 0)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, tasks, 0) assert.Empty(t, tasks)
} }
func TestWebhook_UpdateEvent(t *testing.T) { func TestWebhook_UpdateEvent(t *testing.T) {
@ -206,7 +206,7 @@ func TestHookTasks(t *testing.T) {
hookTasks, err = HookTasks(db.DefaultContext, unittest.NonexistentID, 1) hookTasks, err = HookTasks(db.DefaultContext, unittest.NonexistentID, 1)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, hookTasks, 0) assert.Empty(t, hookTasks)
} }
func TestCreateHookTask(t *testing.T) { func TestCreateHookTask(t *testing.T) {

View File

@ -8,7 +8,6 @@ import (
"io" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"regexp"
"testing" "testing"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -28,9 +27,9 @@ func TestActivityPubSignedPost(t *testing.T) {
expected := "BODY" expected := "BODY"
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Regexp(t, regexp.MustCompile("^"+setting.Federation.DigestAlgorithm), r.Header.Get("Digest")) assert.Regexp(t, "^"+setting.Federation.DigestAlgorithm, r.Header.Get("Digest"))
assert.Contains(t, r.Header.Get("Signature"), pubID) assert.Contains(t, r.Header.Get("Signature"), pubID)
assert.Equal(t, r.Header.Get("Content-Type"), ActivityStreamsContentType) assert.Equal(t, ActivityStreamsContentType, r.Header.Get("Content-Type"))
body, err := io.ReadAll(r.Body) body, err := io.ReadAll(r.Body)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expected, string(body)) assert.Equal(t, expected, string(body))

View File

@ -58,7 +58,7 @@ func TestLayered(t *testing.T) {
assertRead := func(expected string, expectedErr error, elems ...string) { assertRead := func(expected string, expectedErr error, elems ...string) {
bs, err := assets.ReadFile(elems...) bs, err := assets.ReadFile(elems...)
if err != nil { if err != nil {
assert.ErrorAs(t, err, &expectedErr) assert.ErrorIs(t, err, expectedErr)
} else { } else {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expected, string(bs)) assert.Equal(t, expected, string(bs))

View File

@ -15,5 +15,5 @@ func TestPamAuth(t *testing.T) {
result, err := Auth("gitea", "user1", "false-pwd") result, err := Auth("gitea", "user1", "false-pwd")
assert.Error(t, err) assert.Error(t, err)
assert.EqualError(t, err, "Authentication failure") assert.EqualError(t, err, "Authentication failure")
assert.Len(t, result, 0) assert.Len(t, result)
} }

View File

@ -18,7 +18,7 @@ func TestDummyHasher(t *testing.T) {
password, salt := "password", "ZogKvWdyEx" password, salt := "password", "ZogKvWdyEx"
hash, err := dummy.Hash(password, salt) hash, err := dummy.Hash(password, salt)
assert.Nil(t, err) assert.NoError(t, err)
assert.Equal(t, hash, salt+":"+password) assert.Equal(t, hash, salt+":"+password)
assert.True(t, dummy.VerifyPassword(password, hash, salt)) assert.True(t, dummy.VerifyPassword(password, hash, salt))

View File

@ -99,10 +99,10 @@ func IsComplexEnough(pwd string) bool {
func Generate(n int) (string, error) { func Generate(n int) (string, error) {
NewComplexity() NewComplexity()
buffer := make([]byte, n) buffer := make([]byte, n)
max := big.NewInt(int64(len(validChars))) maxInt := big.NewInt(int64(len(validChars)))
for { for {
for j := 0; j < n; j++ { for j := 0; j < n; j++ {
rnd, err := rand.Int(rand.Reader, max) rnd, err := rand.Int(rand.Reader, maxInt)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -6,7 +6,6 @@ package base
import ( import (
"crypto/sha1" "crypto/sha1"
"fmt" "fmt"
"os"
"testing" "testing"
"time" "time"
@ -157,7 +156,7 @@ func TestStringsToInt64s(t *testing.T) {
testSuccess([]string{"1", "4", "16", "64", "256"}, []int64{1, 4, 16, 64, 256}) testSuccess([]string{"1", "4", "16", "64", "256"}, []int64{1, 4, 16, 64, 256})
ints, err := StringsToInt64s([]string{"-1", "a"}) ints, err := StringsToInt64s([]string{"-1", "a"})
assert.Len(t, ints, 0) assert.Empty(t, ints)
assert.Error(t, err) assert.Error(t, err)
} }
@ -172,9 +171,9 @@ func TestInt64sToStrings(t *testing.T) {
// TODO: Test EntryIcon // TODO: Test EntryIcon
func TestSetupGiteaRoot(t *testing.T) { func TestSetupGiteaRoot(t *testing.T) {
_ = os.Setenv("GITEA_ROOT", "test") t.Setenv("GITEA_ROOT", "test")
assert.Equal(t, "test", SetupGiteaRoot()) assert.Equal(t, "test", SetupGiteaRoot())
_ = os.Setenv("GITEA_ROOT", "") t.Setenv("GITEA_ROOT", "")
assert.NotEqual(t, "test", SetupGiteaRoot()) assert.NotEqual(t, "test", SetupGiteaRoot())
} }

View File

@ -25,7 +25,7 @@ func TestPrepareFileNameAndType(t *testing.T) {
assert.Equal(t, assert.Equal(t,
fmt.Sprintf("outFile=%s, outType=%s", expFile, expType), fmt.Sprintf("outFile=%s, outType=%s", expFile, expType),
fmt.Sprintf("outFile=%s, outType=%s", outFile, outType), fmt.Sprintf("outFile=%s, outType=%s", outFile, outType),
fmt.Sprintf("argFile=%s, argType=%s", argFile, argType), "argFile=%s, argType=%s", argFile, argType,
) )
} }

View File

@ -146,7 +146,7 @@ func TestHasPreviousCommitSha256(t *testing.T) {
parentSHA := MustIDFromString("b0ec7af4547047f12d5093e37ef8f1b3b5415ed8ee17894d43a34d7d34212e9c") parentSHA := MustIDFromString("b0ec7af4547047f12d5093e37ef8f1b3b5415ed8ee17894d43a34d7d34212e9c")
notParentSHA := MustIDFromString("42e334efd04cd36eea6da0599913333c26116e1a537ca76e5b6e4af4dda00236") notParentSHA := MustIDFromString("42e334efd04cd36eea6da0599913333c26116e1a537ca76e5b6e4af4dda00236")
assert.Equal(t, objectFormat, parentSHA.Type()) assert.Equal(t, objectFormat, parentSHA.Type())
assert.Equal(t, objectFormat.Name(), "sha256") assert.Equal(t, "sha256", objectFormat.Name())
haz, err := commit.HasPreviousCommit(parentSHA) haz, err := commit.HasPreviousCommit(parentSHA)
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -343,9 +343,9 @@ func TestGetCommitFileStatusMerges(t *testing.T) {
}, },
} }
assert.Equal(t, commitFileStatus.Added, expected.Added) assert.Equal(t, expected.Added, commitFileStatus.Added)
assert.Equal(t, commitFileStatus.Removed, expected.Removed) assert.Equal(t, expected.Removed, commitFileStatus.Removed)
assert.Equal(t, commitFileStatus.Modified, expected.Modified) assert.Equal(t, expected.Modified, commitFileStatus.Modified)
} }
func Test_GetCommitBranchStart(t *testing.T) { func Test_GetCommitBranchStart(t *testing.T) {

View File

@ -73,9 +73,9 @@ func TestGrepSearch(t *testing.T) {
res, err = GrepSearch(context.Background(), repo, "no-such-content", GrepOptions{}) res, err = GrepSearch(context.Background(), repo, "no-such-content", GrepOptions{})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, res, 0) assert.Empty(t, res)
res, err = GrepSearch(context.Background(), &Repository{Path: "no-such-git-repo"}, "no-such-content", GrepOptions{}) res, err = GrepSearch(context.Background(), &Repository{Path: "no-such-git-repo"}, "no-such-content", GrepOptions{})
assert.Error(t, err) assert.Error(t, err)
assert.Len(t, res, 0) assert.Empty(t, res)
} }

View File

@ -100,5 +100,5 @@ func TestParseTreeEntriesInvalid(t *testing.T) {
// there was a panic: "runtime error: slice bounds out of range" when the input was invalid: #20315 // there was a panic: "runtime error: slice bounds out of range" when the input was invalid: #20315
entries, err := ParseTreeEntries([]byte("100644 blob ea0d83c9081af9500ac9f804101b3fd0a5c293af")) entries, err := ParseTreeEntries([]byte("100644 blob ea0d83c9081af9500ac9f804101b3fd0a5c293af"))
assert.Error(t, err) assert.Error(t, err)
assert.Len(t, entries, 0) assert.Empty(t, entries)
} }

View File

@ -34,7 +34,7 @@ func TestRepository_GetBranches(t *testing.T) {
branches, countAll, err = bareRepo1.GetBranchNames(5, 1) branches, countAll, err = bareRepo1.GetBranchNames(5, 1)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, branches, 0) assert.Empty(t, branches)
assert.EqualValues(t, 3, countAll) assert.EqualValues(t, 3, countAll)
assert.ElementsMatch(t, []string{}, branches) assert.ElementsMatch(t, []string{}, branches)
} }
@ -66,7 +66,7 @@ func TestGetRefsBySha(t *testing.T) {
// do not exist // do not exist
branches, err := bareRepo5.GetRefsBySha("8006ff9adbf0cb94da7dad9e537e53817f9fa5c0", "") branches, err := bareRepo5.GetRefsBySha("8006ff9adbf0cb94da7dad9e537e53817f9fa5c0", "")
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, branches, 0) assert.Empty(t, branches)
// refs/pull/1/head // refs/pull/1/head
branches, err = bareRepo5.GetRefsBySha("c83380d7056593c51a699d12b9c00627bd5743e9", PullPrefix) branches, err = bareRepo5.GetRefsBySha("c83380d7056593c51a699d12b9c00627bd5743e9", PullPrefix)

View File

@ -465,15 +465,15 @@ func (repo *Repository) getBranches(env []string, commitID string, limit int) ([
refs := strings.Split(stdout, "\n") refs := strings.Split(stdout, "\n")
var max int var maxNum int
if len(refs) > limit { if len(refs) > limit {
max = limit maxNum = limit
} else { } else {
max = len(refs) - 1 maxNum = len(refs) - 1
} }
branches := make([]string, max) branches := make([]string, maxNum)
for i, ref := range refs[:max] { for i, ref := range refs[:maxNum] {
parts := strings.Fields(ref) parts := strings.Fields(ref)
branches[i] = parts[len(parts)-1] branches[i] = parts[len(parts)-1]

View File

@ -97,7 +97,7 @@ func TestReadPatch(t *testing.T) {
assert.Empty(t, noFile) assert.Empty(t, noFile)
assert.Empty(t, noCommit) assert.Empty(t, noCommit)
assert.Len(t, oldCommit, 40) assert.Len(t, oldCommit, 40)
assert.True(t, oldCommit == "6e8e2a6f9efd71dbe6917816343ed8415ad696c3") assert.Equal(t, "6e8e2a6f9efd71dbe6917816343ed8415ad696c3", oldCommit)
} }
func TestReadWritePullHead(t *testing.T) { func TestReadWritePullHead(t *testing.T) {
@ -138,7 +138,7 @@ func TestReadWritePullHead(t *testing.T) {
} }
assert.Len(t, headContents, 40) assert.Len(t, headContents, 40)
assert.True(t, headContents == newCommit) assert.Equal(t, headContents, newCommit)
// Remove file after the test // Remove file after the test
err = repo.RemoveReference(PullPrefix + "1/head") err = repo.RemoveReference(PullPrefix + "1/head")

View File

@ -218,13 +218,13 @@ func (g *Manager) ServerDone() {
g.runningServerWaitGroup.Done() g.runningServerWaitGroup.Done()
} }
func (g *Manager) setStateTransition(old, new state) bool { func (g *Manager) setStateTransition(oldState, newState state) bool {
g.lock.Lock() g.lock.Lock()
if g.state != old { if g.state != oldState {
g.lock.Unlock() g.lock.Unlock()
return false return false
} }
g.state = new g.state = newState
g.lock.Unlock() g.lock.Unlock()
return true return true
} }

View File

@ -35,18 +35,18 @@ func BoolFieldQuery(value bool, field string) *query.BoolFieldQuery {
return q return q
} }
func NumericRangeInclusiveQuery(min, max optional.Option[int64], field string) *query.NumericRangeQuery { func NumericRangeInclusiveQuery(minOption, maxOption optional.Option[int64], field string) *query.NumericRangeQuery {
var minF, maxF *float64 var minF, maxF *float64
var minI, maxI *bool var minI, maxI *bool
if min.Has() { if minOption.Has() {
minF = new(float64) minF = new(float64)
*minF = float64(min.Value()) *minF = float64(minOption.Value())
minI = new(bool) minI = new(bool)
*minI = true *minI = true
} }
if max.Has() { if maxOption.Has() {
maxF = new(float64) maxF = new(float64)
*maxF = float64(max.Value()) *maxF = float64(maxOption.Value())
maxI = new(bool) maxI = new(bool)
*maxI = true *maxI = true
} }

View File

@ -10,12 +10,12 @@ import (
) )
// ParsePaginator parses a db.Paginator into a skip and limit // ParsePaginator parses a db.Paginator into a skip and limit
func ParsePaginator(paginator *db.ListOptions, max ...int) (int, int) { func ParsePaginator(paginator *db.ListOptions, maxNums ...int) (int, int) {
// Use a very large number to indicate no limit // Use a very large number to indicate no limit
unlimited := math.MaxInt32 unlimited := math.MaxInt32
if len(max) > 0 { if len(maxNums) > 0 {
// Some indexer engines have a limit on the page size, respect that // Some indexer engines have a limit on the page size, respect that
unlimited = max[0] unlimited = maxNums[0]
} }
if paginator == nil || paginator.IsListAll() { if paginator == nil || paginator.IsListAll() {

View File

@ -113,7 +113,7 @@ var cases = []*testIndexerCase{
}, },
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
assert.Equal(t, len(data), int(result.Total)) assert.Equal(t, len(data), int(result.Total))
}, },
}, },
@ -176,7 +176,7 @@ var cases = []*testIndexerCase{
IsPull: optional.Some(false), IsPull: optional.Some(false),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.False(t, data[v.ID].IsPull) assert.False(t, data[v.ID].IsPull)
} }
@ -192,7 +192,7 @@ var cases = []*testIndexerCase{
IsPull: optional.Some(true), IsPull: optional.Some(true),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.True(t, data[v.ID].IsPull) assert.True(t, data[v.ID].IsPull)
} }
@ -208,7 +208,7 @@ var cases = []*testIndexerCase{
IsClosed: optional.Some(false), IsClosed: optional.Some(false),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.False(t, data[v.ID].IsClosed) assert.False(t, data[v.ID].IsClosed)
} }
@ -224,7 +224,7 @@ var cases = []*testIndexerCase{
IsClosed: optional.Some(true), IsClosed: optional.Some(true),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.True(t, data[v.ID].IsClosed) assert.True(t, data[v.ID].IsClosed)
} }
@ -274,7 +274,7 @@ var cases = []*testIndexerCase{
MilestoneIDs: []int64{1, 2, 6}, MilestoneIDs: []int64{1, 2, 6},
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Contains(t, []int64{1, 2, 6}, data[v.ID].MilestoneID) assert.Contains(t, []int64{1, 2, 6}, data[v.ID].MilestoneID)
} }
@ -292,7 +292,7 @@ var cases = []*testIndexerCase{
MilestoneIDs: []int64{0}, MilestoneIDs: []int64{0},
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Equal(t, int64(0), data[v.ID].MilestoneID) assert.Equal(t, int64(0), data[v.ID].MilestoneID)
} }
@ -310,7 +310,7 @@ var cases = []*testIndexerCase{
ProjectID: optional.Some(int64(1)), ProjectID: optional.Some(int64(1)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Equal(t, int64(1), data[v.ID].ProjectID) assert.Equal(t, int64(1), data[v.ID].ProjectID)
} }
@ -328,7 +328,7 @@ var cases = []*testIndexerCase{
ProjectID: optional.Some(int64(0)), ProjectID: optional.Some(int64(0)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Equal(t, int64(0), data[v.ID].ProjectID) assert.Equal(t, int64(0), data[v.ID].ProjectID)
} }
@ -346,7 +346,7 @@ var cases = []*testIndexerCase{
ProjectColumnID: optional.Some(int64(1)), ProjectColumnID: optional.Some(int64(1)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Equal(t, int64(1), data[v.ID].ProjectColumnID) assert.Equal(t, int64(1), data[v.ID].ProjectColumnID)
} }
@ -364,7 +364,7 @@ var cases = []*testIndexerCase{
ProjectColumnID: optional.Some(int64(0)), ProjectColumnID: optional.Some(int64(0)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Equal(t, int64(0), data[v.ID].ProjectColumnID) assert.Equal(t, int64(0), data[v.ID].ProjectColumnID)
} }
@ -382,7 +382,7 @@ var cases = []*testIndexerCase{
PosterID: optional.Some(int64(1)), PosterID: optional.Some(int64(1)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Equal(t, int64(1), data[v.ID].PosterID) assert.Equal(t, int64(1), data[v.ID].PosterID)
} }
@ -400,7 +400,7 @@ var cases = []*testIndexerCase{
AssigneeID: optional.Some(int64(1)), AssigneeID: optional.Some(int64(1)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Equal(t, int64(1), data[v.ID].AssigneeID) assert.Equal(t, int64(1), data[v.ID].AssigneeID)
} }
@ -418,7 +418,7 @@ var cases = []*testIndexerCase{
AssigneeID: optional.Some(int64(0)), AssigneeID: optional.Some(int64(0)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Equal(t, int64(0), data[v.ID].AssigneeID) assert.Equal(t, int64(0), data[v.ID].AssigneeID)
} }
@ -436,7 +436,7 @@ var cases = []*testIndexerCase{
MentionID: optional.Some(int64(1)), MentionID: optional.Some(int64(1)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Contains(t, data[v.ID].MentionIDs, int64(1)) assert.Contains(t, data[v.ID].MentionIDs, int64(1))
} }
@ -454,7 +454,7 @@ var cases = []*testIndexerCase{
ReviewedID: optional.Some(int64(1)), ReviewedID: optional.Some(int64(1)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Contains(t, data[v.ID].ReviewedIDs, int64(1)) assert.Contains(t, data[v.ID].ReviewedIDs, int64(1))
} }
@ -472,7 +472,7 @@ var cases = []*testIndexerCase{
ReviewRequestedID: optional.Some(int64(1)), ReviewRequestedID: optional.Some(int64(1)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Contains(t, data[v.ID].ReviewRequestedIDs, int64(1)) assert.Contains(t, data[v.ID].ReviewRequestedIDs, int64(1))
} }
@ -490,7 +490,7 @@ var cases = []*testIndexerCase{
SubscriberID: optional.Some(int64(1)), SubscriberID: optional.Some(int64(1)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Contains(t, data[v.ID].SubscriberIDs, int64(1)) assert.Contains(t, data[v.ID].SubscriberIDs, int64(1))
} }
@ -509,7 +509,7 @@ var cases = []*testIndexerCase{
UpdatedBeforeUnix: optional.Some(int64(30)), UpdatedBeforeUnix: optional.Some(int64(30)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.GreaterOrEqual(t, data[v.ID].UpdatedUnix, int64(20)) assert.GreaterOrEqual(t, data[v.ID].UpdatedUnix, int64(20))
assert.LessOrEqual(t, data[v.ID].UpdatedUnix, int64(30)) assert.LessOrEqual(t, data[v.ID].UpdatedUnix, int64(30))

View File

@ -72,7 +72,10 @@ func (c *HTTPClient) batch(ctx context.Context, operation string, objects []Poin
url := fmt.Sprintf("%s/objects/batch", c.endpoint) url := fmt.Sprintf("%s/objects/batch", c.endpoint)
request := &BatchRequest{operation, c.transferNames(), nil, objects} // `ref` is an "optional object describing the server ref that the objects belong to"
// but some (incorrect) lfs servers require it, so maybe adding an empty ref here doesn't break the correct ones.
// https://github.com/git-lfs/git-lfs/blob/a32a02b44bf8a511aa14f047627c49e1a7fd5021/docs/api/batch.md?plain=1#L37
request := &BatchRequest{operation, c.transferNames(), &Reference{}, objects}
payload := new(bytes.Buffer) payload := new(bytes.Buffer)
err := json.NewEncoder(payload).Encode(request) err := json.NewEncoder(payload).Encode(request)
if err != nil { if err != nil {

View File

@ -14,9 +14,12 @@ import (
const ( const (
// MediaType contains the media type for LFS server requests // MediaType contains the media type for LFS server requests
MediaType = "application/vnd.git-lfs+json" MediaType = "application/vnd.git-lfs+json"
// Some LFS servers offer content with other types, so fallback to '*/*' if application/vnd.git-lfs+json cannot be served // AcceptHeader Some LFS servers offer content with other types, so fallback to '*/*' if application/vnd.git-lfs+json cannot be served
AcceptHeader = "application/vnd.git-lfs+json;q=0.9, */*;q=0.8" AcceptHeader = "application/vnd.git-lfs+json;q=0.9, */*;q=0.8"
UserAgentHeader = "git-lfs" // UserAgentHeader Add User-Agent for gitea's self-implemented lfs client,
// and the version is consistent with the latest version of git lfs can be avoided incompatibilities.
// Some lfs servers will check this
UserAgentHeader = "git-lfs/3.6.0 (Gitea)"
) )
// BatchRequest contains multiple requests processed in one batch operation. // BatchRequest contains multiple requests processed in one batch operation.

View File

@ -96,7 +96,7 @@ func TestBasicTransferAdapter(t *testing.T) {
for n, c := range cases { for n, c := range cases {
_, err := a.Download(context.Background(), c.link) _, err := a.Download(context.Background(), c.link)
if len(c.expectederror) > 0 { if len(c.expectederror) > 0 {
assert.True(t, strings.Contains(err.Error(), c.expectederror), "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) assert.Contains(t, err.Error(), c.expectederror, "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror)
} else { } else {
assert.NoError(t, err, "case %d", n) assert.NoError(t, err, "case %d", n)
} }
@ -129,7 +129,7 @@ func TestBasicTransferAdapter(t *testing.T) {
for n, c := range cases { for n, c := range cases {
err := a.Upload(context.Background(), c.link, p, bytes.NewBufferString("dummy")) err := a.Upload(context.Background(), c.link, p, bytes.NewBufferString("dummy"))
if len(c.expectederror) > 0 { if len(c.expectederror) > 0 {
assert.True(t, strings.Contains(err.Error(), c.expectederror), "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) assert.Contains(t, err.Error(), c.expectederror, "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror)
} else { } else {
assert.NoError(t, err, "case %d", n) assert.NoError(t, err, "case %d", n)
} }
@ -162,7 +162,7 @@ func TestBasicTransferAdapter(t *testing.T) {
for n, c := range cases { for n, c := range cases {
err := a.Verify(context.Background(), c.link, p) err := a.Verify(context.Background(), c.link, p)
if len(c.expectederror) > 0 { if len(c.expectederror) > 0 {
assert.True(t, strings.Contains(err.Error(), c.expectederror), "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) assert.Contains(t, err.Error(), c.expectederror, "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror)
} else { } else {
assert.NoError(t, err, "case %d", n) assert.NoError(t, err, "case %d", n)
} }

View File

@ -110,10 +110,10 @@ func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, ms
buf = append(buf, ' ') buf = append(buf, ' ')
} }
if flags&(Ltime|Lmicroseconds) != 0 { if flags&(Ltime|Lmicroseconds) != 0 {
hour, min, sec := t.Clock() hour, minNum, sec := t.Clock()
buf = itoa(buf, hour, 2) buf = itoa(buf, hour, 2)
buf = append(buf, ':') buf = append(buf, ':')
buf = itoa(buf, min, 2) buf = itoa(buf, minNum, 2)
buf = append(buf, ':') buf = append(buf, ':')
buf = itoa(buf, sec, 2) buf = itoa(buf, sec, 2)
if flags&Lmicroseconds != 0 { if flags&Lmicroseconds != 0 {

View File

@ -56,7 +56,7 @@ func TestLogger(t *testing.T) {
logger := NewLoggerWithWriters(context.Background(), "test") logger := NewLoggerWithWriters(context.Background(), "test")
dump := logger.DumpWriters() dump := logger.DumpWriters()
assert.EqualValues(t, 0, len(dump)) assert.Empty(t, dump)
assert.EqualValues(t, NONE, logger.GetLevel()) assert.EqualValues(t, NONE, logger.GetLevel())
assert.False(t, logger.IsEnabled()) assert.False(t, logger.IsEnabled())
@ -69,7 +69,7 @@ func TestLogger(t *testing.T) {
assert.EqualValues(t, DEBUG, logger.GetLevel()) assert.EqualValues(t, DEBUG, logger.GetLevel())
dump = logger.DumpWriters() dump = logger.DumpWriters()
assert.EqualValues(t, 2, len(dump)) assert.Len(t, dump, 2)
logger.Trace("trace-level") // this level is not logged logger.Trace("trace-level") // this level is not logged
logger.Debug("debug-level") logger.Debug("debug-level")

View File

@ -278,12 +278,12 @@ func TestRender_AutoLink(t *testing.T) {
test := func(input, expected string) { test := func(input, expected string) {
var buffer strings.Builder var buffer strings.Builder
err := PostProcessDefault(NewTestRenderContext(localMetas), strings.NewReader(input), &buffer) err := PostProcessDefault(NewTestRenderContext(localMetas), strings.NewReader(input), &buffer)
assert.Equal(t, err, nil) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
buffer.Reset() buffer.Reset()
err = PostProcessDefault(NewTestRenderContext(localMetas), strings.NewReader(input), &buffer) err = PostProcessDefault(NewTestRenderContext(localMetas), strings.NewReader(input), &buffer)
assert.Equal(t, err, nil) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
} }

View File

@ -78,26 +78,23 @@ func (r *GlodmarkRender) Renderer() renderer.Renderer {
func (r *GlodmarkRender) highlightingRenderer(w util.BufWriter, c highlighting.CodeBlockContext, entering bool) { func (r *GlodmarkRender) highlightingRenderer(w util.BufWriter, c highlighting.CodeBlockContext, entering bool) {
if entering { if entering {
language, _ := c.Language() languageBytes, _ := c.Language()
if language == nil { languageStr := giteautil.IfZero(string(languageBytes), "text")
language = []byte("text")
}
languageStr := string(language) preClasses := "code-block"
preClasses := []string{"code-block"}
if languageStr == "mermaid" || languageStr == "math" { if languageStr == "mermaid" || languageStr == "math" {
preClasses = append(preClasses, "is-loading") preClasses += " is-loading"
} }
err := r.ctx.RenderInternal.FormatWithSafeAttrs(w, `<pre class="%s">`, strings.Join(preClasses, " ")) err := r.ctx.RenderInternal.FormatWithSafeAttrs(w, `<pre class="%s">`, preClasses)
if err != nil { if err != nil {
return return
} }
// include language-x class as part of commonmark spec // include language-x class as part of commonmark spec, "chroma" class is used to highlight the code
// the "display" class is used by "js/markup/math.js" to render the code element as a block // the "display" class is used by "js/markup/math.ts" to render the code element as a block
err = r.ctx.RenderInternal.FormatWithSafeAttrs(w, `<code class="chroma language-%s display">`, string(language)) // the "math.ts" strictly depends on the structure: <pre class="code-block is-loading"><code class="language-math display">...</code></pre>
err = r.ctx.RenderInternal.FormatWithSafeAttrs(w, `<code class="chroma language-%s display">`, languageStr)
if err != nil { if err != nil {
return return
} }
@ -128,7 +125,12 @@ func SpecializedMarkdown(ctx *markup.RenderContext) *GlodmarkRender {
), ),
highlighting.WithWrapperRenderer(r.highlightingRenderer), highlighting.WithWrapperRenderer(r.highlightingRenderer),
), ),
math.NewExtension(&ctx.RenderInternal, math.Enabled(setting.Markdown.EnableMath)), math.NewExtension(&ctx.RenderInternal, math.Options{
Enabled: setting.Markdown.EnableMath,
ParseDollarInline: true,
ParseDollarBlock: true,
ParseSquareBlock: true, // TODO: this is a bad syntax, it should be deprecated in the future (by some config options)
}),
meta.Meta, meta.Meta,
), ),
goldmark.WithParserOptions( goldmark.WithParserOptions(

View File

@ -12,31 +12,32 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestMathRender(t *testing.T) {
const nl = "\n" const nl = "\n"
func TestMathRender(t *testing.T) {
testcases := []struct { testcases := []struct {
testcase string testcase string
expected string expected string
}{ }{
{ {
"$a$", "$a$",
`<p><code class="language-math is-loading">a</code></p>` + nl, `<p><code class="language-math">a</code></p>` + nl,
}, },
{ {
"$ a $", "$ a $",
`<p><code class="language-math is-loading">a</code></p>` + nl, `<p><code class="language-math">a</code></p>` + nl,
}, },
{ {
"$a$ $b$", "$a$ $b$",
`<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl, `<p><code class="language-math">a</code> <code class="language-math">b</code></p>` + nl,
}, },
{ {
`\(a\) \(b\)`, `\(a\) \(b\)`,
`<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl, `<p><code class="language-math">a</code> <code class="language-math">b</code></p>` + nl,
}, },
{ {
`$a$.`, `$a$.`,
`<p><code class="language-math is-loading">a</code>.</p>` + nl, `<p><code class="language-math">a</code>.</p>` + nl,
}, },
{ {
`.$a$`, `.$a$`,
@ -64,27 +65,39 @@ func TestMathRender(t *testing.T) {
}, },
{ {
"$a$ ($b$) [$c$] {$d$}", "$a$ ($b$) [$c$] {$d$}",
`<p><code class="language-math is-loading">a</code> (<code class="language-math is-loading">b</code>) [$c$] {$d$}</p>` + nl, `<p><code class="language-math">a</code> (<code class="language-math">b</code>) [$c$] {$d$}</p>` + nl,
}, },
{ {
"$$a$$", "$$a$$",
`<code class="chroma language-math display">a</code>` + nl, `<code class="language-math display">a</code>` + nl,
}, },
{ {
"$$a$$ test", "$$a$$ test",
`<p><code class="language-math display is-loading">a</code> test</p>` + nl, `<p><code class="language-math">a</code> test</p>` + nl,
}, },
{ {
"test $$a$$", "test $$a$$",
`<p>test <code class="language-math display is-loading">a</code></p>` + nl, `<p>test <code class="language-math">a</code></p>` + nl,
}, },
{ {
`foo $x=\$$ bar`, `foo $x=\$$ bar`,
`<p>foo <code class="language-math is-loading">x=\$</code> bar</p>` + nl, `<p>foo <code class="language-math">x=\$</code> bar</p>` + nl,
}, },
{ {
`$\text{$b$}$`, `$\text{$b$}$`,
`<p><code class="language-math is-loading">\text{$b$}</code></p>` + nl, `<p><code class="language-math">\text{$b$}</code></p>` + nl,
},
{
"a$`b`$c",
`<p>a<code class="language-math">b</code>c</p>` + nl,
},
{
"a $`b`$ c",
`<p>a <code class="language-math">b</code> c</p>` + nl,
},
{
"a$``b``$c x$```y```$z",
`<p>a<code class="language-math">b</code>c x<code class="language-math">y</code>z</p>` + nl,
}, },
} }
@ -110,7 +123,7 @@ func TestMathRenderBlockIndent(t *testing.T) {
\alpha \alpha
\] \]
`, `,
`<pre class="code-block is-loading"><code class="chroma language-math display"> `<pre class="code-block is-loading"><code class="language-math display">
\alpha \alpha
</code></pre> </code></pre>
`, `,
@ -122,7 +135,7 @@ func TestMathRenderBlockIndent(t *testing.T) {
\alpha \alpha
\] \]
`, `,
`<pre class="code-block is-loading"><code class="chroma language-math display"> `<pre class="code-block is-loading"><code class="language-math display">
\alpha \alpha
</code></pre> </code></pre>
`, `,
@ -137,7 +150,7 @@ a
d d
\] \]
`, `,
`<pre class="code-block is-loading"><code class="chroma language-math display"> `<pre class="code-block is-loading"><code class="language-math display">
a a
b b
c c
@ -154,7 +167,7 @@ c
c c
\] \]
`, `,
`<pre class="code-block is-loading"><code class="chroma language-math display"> `<pre class="code-block is-loading"><code class="language-math display">
a a
b b
c c
@ -165,7 +178,7 @@ c
"indent-0-oneline", "indent-0-oneline",
`$$ x $$ `$$ x $$
foo`, foo`,
`<code class="chroma language-math display"> x </code> `<code class="language-math display"> x </code>
<p>foo</p> <p>foo</p>
`, `,
}, },
@ -173,7 +186,7 @@ foo`,
"indent-3-oneline", "indent-3-oneline",
` $$ x $$<SPACE> ` $$ x $$<SPACE>
foo`, foo`,
`<code class="chroma language-math display"> x </code> `<code class="language-math display"> x </code>
<p>foo</p> <p>foo</p>
`, `,
}, },
@ -188,10 +201,10 @@ foo`,
> \] > \]
`, `,
`<blockquote> `<blockquote>
<pre class="code-block is-loading"><code class="chroma language-math display"> <pre class="code-block is-loading"><code class="language-math display">
a a
</code></pre> </code></pre>
<pre class="code-block is-loading"><code class="chroma language-math display"> <pre class="code-block is-loading"><code class="language-math display">
b b
</code></pre> </code></pre>
</blockquote> </blockquote>
@ -207,7 +220,7 @@ b
2. b`, 2. b`,
`<ol> `<ol>
<li>a <li>a
<pre class="code-block is-loading"><code class="chroma language-math display"> <pre class="code-block is-loading"><code class="language-math display">
x x
</code></pre> </code></pre>
</li> </li>
@ -215,6 +228,11 @@ x
</ol> </ol>
`, `,
}, },
{
"inline-non-math",
`\[x]`,
`<p>[x]</p>` + nl,
},
} }
for _, test := range testcases { for _, test := range testcases {

View File

@ -16,16 +16,18 @@ import (
type blockParser struct { type blockParser struct {
parseDollars bool parseDollars bool
parseSquare bool
endBytesDollars []byte endBytesDollars []byte
endBytesBracket []byte endBytesSquare []byte
} }
// NewBlockParser creates a new math BlockParser // NewBlockParser creates a new math BlockParser
func NewBlockParser(parseDollarBlocks bool) parser.BlockParser { func NewBlockParser(parseDollars, parseSquare bool) parser.BlockParser {
return &blockParser{ return &blockParser{
parseDollars: parseDollarBlocks, parseDollars: parseDollars,
parseSquare: parseSquare,
endBytesDollars: []byte{'$', '$'}, endBytesDollars: []byte{'$', '$'},
endBytesBracket: []byte{'\\', ']'}, endBytesSquare: []byte{'\\', ']'},
} }
} }
@ -40,7 +42,7 @@ func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Contex
var dollars bool var dollars bool
if b.parseDollars && line[pos] == '$' && line[pos+1] == '$' { if b.parseDollars && line[pos] == '$' && line[pos+1] == '$' {
dollars = true dollars = true
} else if line[pos] == '\\' && line[pos+1] == '[' { } else if b.parseSquare && line[pos] == '\\' && line[pos+1] == '[' {
if len(line[pos:]) >= 3 && line[pos+2] == '!' && bytes.Contains(line[pos:], []byte(`\]`)) { if len(line[pos:]) >= 3 && line[pos+2] == '!' && bytes.Contains(line[pos:], []byte(`\]`)) {
// do not process escaped attention block: "> \[!NOTE\]" // do not process escaped attention block: "> \[!NOTE\]"
return nil, parser.NoChildren return nil, parser.NoChildren
@ -53,10 +55,10 @@ func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Contex
node := NewBlock(dollars, pos) node := NewBlock(dollars, pos)
// Now we need to check if the ending block is on the segment... // Now we need to check if the ending block is on the segment...
endBytes := giteaUtil.Iif(dollars, b.endBytesDollars, b.endBytesBracket) endBytes := giteaUtil.Iif(dollars, b.endBytesDollars, b.endBytesSquare)
idx := bytes.Index(line[pos+2:], endBytes) idx := bytes.Index(line[pos+2:], endBytes)
if idx >= 0 { if idx >= 0 {
// for case $$ ... $$ any other text // for case: "$$ ... $$ any other text" (this case will be handled by the inline parser)
for i := pos + 2 + idx + 2; i < len(line); i++ { for i := pos + 2 + idx + 2; i < len(line); i++ {
if line[i] != ' ' && line[i] != '\n' { if line[i] != ' ' && line[i] != '\n' {
return nil, parser.NoChildren return nil, parser.NoChildren
@ -70,6 +72,13 @@ func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Contex
return node, parser.Close | parser.NoChildren return node, parser.Close | parser.NoChildren
} }
// for case "\[ ... ]" (no close marker on the same line)
for i := pos + 2 + idx + 2; i < len(line); i++ {
if line[i] != ' ' && line[i] != '\n' {
return nil, parser.NoChildren
}
}
segment.Start += pos + 2 segment.Start += pos + 2
node.Lines().Append(segment) node.Lines().Append(segment)
return node, parser.NoChildren return node, parser.NoChildren
@ -85,7 +94,7 @@ func (b *blockParser) Continue(node ast.Node, reader text.Reader, pc parser.Cont
line, segment := reader.PeekLine() line, segment := reader.PeekLine()
w, pos := util.IndentWidth(line, reader.LineOffset()) w, pos := util.IndentWidth(line, reader.LineOffset())
if w < 4 { if w < 4 {
endBytes := giteaUtil.Iif(block.Dollars, b.endBytesDollars, b.endBytesBracket) endBytes := giteaUtil.Iif(block.Dollars, b.endBytesDollars, b.endBytesSquare)
if bytes.HasPrefix(line[pos:], endBytes) && util.IsBlank(line[pos+len(endBytes):]) { if bytes.HasPrefix(line[pos:], endBytes) && util.IsBlank(line[pos+len(endBytes):]) {
if util.IsBlank(line[pos+len(endBytes):]) { if util.IsBlank(line[pos+len(endBytes):]) {
newline := giteaUtil.Iif(line[len(line)-1] != '\n', 0, 1) newline := giteaUtil.Iif(line[len(line)-1] != '\n', 0, 1)

View File

@ -12,6 +12,17 @@ import (
"github.com/yuin/goldmark/util" "github.com/yuin/goldmark/util"
) )
// Block render output:
// <pre class="code-block is-loading"><code class="language-math display">...</code></pre>
//
// Keep in mind that there is another "code block" render in "func (r *GlodmarkRender) highlightingRenderer"
// "highlightingRenderer" outputs the math block with extra "chroma" class:
// <pre class="code-block is-loading"><code class="chroma language-math display">...</code></pre>
//
// Special classes:
// * "is-loading": show a loading indicator
// * "display": used by JS to decide to render as a block, otherwise render as inline
// BlockRenderer represents a renderer for math Blocks // BlockRenderer represents a renderer for math Blocks
type BlockRenderer struct { type BlockRenderer struct {
renderInternal *internal.RenderInternal renderInternal *internal.RenderInternal
@ -38,7 +49,7 @@ func (r *BlockRenderer) writeLines(w util.BufWriter, source []byte, n gast.Node)
func (r *BlockRenderer) renderBlock(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) { func (r *BlockRenderer) renderBlock(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
n := node.(*Block) n := node.(*Block)
if entering { if entering {
code := giteaUtil.Iif(n.Inline, "", `<pre class="code-block is-loading">`) + `<code class="chroma language-math display">` code := giteaUtil.Iif(n.Inline, "", `<pre class="code-block is-loading">`) + `<code class="language-math display">`
_ = r.renderInternal.FormatWithSafeAttrs(w, code) _ = r.renderInternal.FormatWithSafeAttrs(w, code)
r.writeLines(w, source, n) r.writeLines(w, source, n)
} else { } else {

View File

@ -1,31 +0,0 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package math
import (
"github.com/yuin/goldmark/ast"
)
// InlineBlock represents inline math e.g. $$...$$
type InlineBlock struct {
Inline
}
// InlineBlock implements InlineBlock.
func (n *InlineBlock) InlineBlock() {}
// KindInlineBlock is the kind for math inline block
var KindInlineBlock = ast.NewNodeKind("MathInlineBlock")
// Kind returns KindInlineBlock
func (n *InlineBlock) Kind() ast.NodeKind {
return KindInlineBlock
}
// NewInlineBlock creates a new ast math inline block node
func NewInlineBlock() *InlineBlock {
return &InlineBlock{
Inline{},
}
}

View File

@ -8,7 +8,7 @@ import (
"github.com/yuin/goldmark/util" "github.com/yuin/goldmark/util"
) )
// Inline represents inline math e.g. $...$ or \(...\) // Inline struct represents inline math e.g. $...$ or \(...\)
type Inline struct { type Inline struct {
ast.BaseInline ast.BaseInline
} }

View File

@ -12,31 +12,25 @@ import (
) )
type inlineParser struct { type inlineParser struct {
start []byte trigger []byte
end []byte endBytesSingleDollar []byte
endBytesDoubleDollar []byte
endBytesBracket []byte
} }
var defaultInlineDollarParser = &inlineParser{ var defaultInlineDollarParser = &inlineParser{
start: []byte{'$'}, trigger: []byte{'$'},
end: []byte{'$'}, endBytesSingleDollar: []byte{'$'},
} endBytesDoubleDollar: []byte{'$', '$'},
var defaultDualDollarParser = &inlineParser{
start: []byte{'$', '$'},
end: []byte{'$', '$'},
} }
func NewInlineDollarParser() parser.InlineParser { func NewInlineDollarParser() parser.InlineParser {
return defaultInlineDollarParser return defaultInlineDollarParser
} }
func NewInlineDualDollarParser() parser.InlineParser {
return defaultDualDollarParser
}
var defaultInlineBracketParser = &inlineParser{ var defaultInlineBracketParser = &inlineParser{
start: []byte{'\\', '('}, trigger: []byte{'\\', '('},
end: []byte{'\\', ')'}, endBytesBracket: []byte{'\\', ')'},
} }
func NewInlineBracketParser() parser.InlineParser { func NewInlineBracketParser() parser.InlineParser {
@ -45,7 +39,7 @@ func NewInlineBracketParser() parser.InlineParser {
// Trigger triggers this parser on $ or \ // Trigger triggers this parser on $ or \
func (parser *inlineParser) Trigger() []byte { func (parser *inlineParser) Trigger() []byte {
return parser.start return parser.trigger
} }
func isPunctuation(b byte) bool { func isPunctuation(b byte) bool {
@ -64,33 +58,60 @@ func isAlphanumeric(b byte) bool {
func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node { func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
line, _ := block.PeekLine() line, _ := block.PeekLine()
if !bytes.HasPrefix(line, parser.start) { if !bytes.HasPrefix(line, parser.trigger) {
// We'll catch this one on the next time round // We'll catch this one on the next time round
return nil return nil
} }
var startMarkLen int
var stopMark []byte
checkSurrounding := true
if line[0] == '$' {
startMarkLen = 1
stopMark = parser.endBytesSingleDollar
if len(line) > 1 {
if line[1] == '$' {
startMarkLen = 2
stopMark = parser.endBytesDoubleDollar
} else if line[1] == '`' {
pos := 1
for ; pos < len(line) && line[pos] == '`'; pos++ {
}
startMarkLen = pos
stopMark = bytes.Repeat([]byte{'`'}, pos)
stopMark[len(stopMark)-1] = '$'
checkSurrounding = false
}
}
} else {
startMarkLen = 2
stopMark = parser.endBytesBracket
}
if checkSurrounding {
precedingCharacter := block.PrecendingCharacter() precedingCharacter := block.PrecendingCharacter()
if precedingCharacter < 256 && (isAlphanumeric(byte(precedingCharacter)) || isPunctuation(byte(precedingCharacter))) { if precedingCharacter < 256 && (isAlphanumeric(byte(precedingCharacter)) || isPunctuation(byte(precedingCharacter))) {
// need to exclude things like `a$` from being considered a start // need to exclude things like `a$` from being considered a start
return nil return nil
} }
}
// move the opener marker point at the start of the text // move the opener marker point at the start of the text
opener := len(parser.start) opener := startMarkLen
// Now look for an ending line // Now look for an ending line
depth := 0 depth := 0
ender := -1 ender := -1
for i := opener; i < len(line); i++ { for i := opener; i < len(line); i++ {
if depth == 0 && bytes.HasPrefix(line[i:], parser.end) { if depth == 0 && bytes.HasPrefix(line[i:], stopMark) {
succeedingCharacter := byte(0) succeedingCharacter := byte(0)
if i+len(parser.end) < len(line) { if i+len(stopMark) < len(line) {
succeedingCharacter = line[i+len(parser.end)] succeedingCharacter = line[i+len(stopMark)]
} }
// check valid ending character // check valid ending character
isValidEndingChar := isPunctuation(succeedingCharacter) || isBracket(succeedingCharacter) || isValidEndingChar := isPunctuation(succeedingCharacter) || isBracket(succeedingCharacter) ||
succeedingCharacter == ' ' || succeedingCharacter == '\n' || succeedingCharacter == 0 succeedingCharacter == ' ' || succeedingCharacter == '\n' || succeedingCharacter == 0
if !isValidEndingChar { if checkSurrounding && !isValidEndingChar {
break break
} }
ender = i ender = i
@ -112,21 +133,12 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
block.Advance(opener) block.Advance(opener)
_, pos := block.Position() _, pos := block.Position()
var node ast.Node node := NewInline()
if parser == defaultDualDollarParser {
node = NewInlineBlock()
} else {
node = NewInline()
}
segment := pos.WithStop(pos.Start + ender - opener) segment := pos.WithStop(pos.Start + ender - opener)
node.AppendChild(node, ast.NewRawTextSegment(segment)) node.AppendChild(node, ast.NewRawTextSegment(segment))
block.Advance(ender - opener + len(parser.end)) block.Advance(ender - opener + len(stopMark))
trimBlock(node, block)
if parser == defaultDualDollarParser {
trimBlock(&(node.(*InlineBlock)).Inline, block)
} else {
trimBlock(node.(*Inline), block)
}
return node return node
} }

View File

@ -13,6 +13,9 @@ import (
"github.com/yuin/goldmark/util" "github.com/yuin/goldmark/util"
) )
// Inline render output:
// <code class="language-math">...</code>
// InlineRenderer is an inline renderer // InlineRenderer is an inline renderer
type InlineRenderer struct { type InlineRenderer struct {
renderInternal *internal.RenderInternal renderInternal *internal.RenderInternal
@ -25,11 +28,7 @@ func NewInlineRenderer(renderInternal *internal.RenderInternal) renderer.NodeRen
func (r *InlineRenderer) renderInline(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { func (r *InlineRenderer) renderInline(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
if entering { if entering {
extraClass := "" _ = r.renderInternal.FormatWithSafeAttrs(w, `<code class="language-math">`)
if _, ok := n.(*InlineBlock); ok {
extraClass = "display "
}
_ = r.renderInternal.FormatWithSafeAttrs(w, `<code class="language-math %sis-loading">`, extraClass)
for c := n.FirstChild(); c != nil; c = c.NextSibling() { for c := n.FirstChild(); c != nil; c = c.NextSibling() {
segment := c.(*ast.Text).Segment segment := c.(*ast.Text).Segment
value := util.EscapeHTML(segment.Value(source)) value := util.EscapeHTML(segment.Value(source))
@ -51,5 +50,4 @@ func (r *InlineRenderer) renderInline(w util.BufWriter, source []byte, n ast.Nod
// RegisterFuncs registers the renderer for inline math nodes // RegisterFuncs registers the renderer for inline math nodes
func (r *InlineRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { func (r *InlineRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(KindInline, r.renderInline) reg.Register(KindInline, r.renderInline)
reg.Register(KindInlineBlock, r.renderInline)
} }

View File

@ -5,6 +5,7 @@ package math
import ( import (
"code.gitea.io/gitea/modules/markup/internal" "code.gitea.io/gitea/modules/markup/internal"
giteaUtil "code.gitea.io/gitea/modules/util"
"github.com/yuin/goldmark" "github.com/yuin/goldmark"
"github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/parser"
@ -12,70 +13,45 @@ import (
"github.com/yuin/goldmark/util" "github.com/yuin/goldmark/util"
) )
type Options struct {
Enabled bool
ParseDollarInline bool
ParseDollarBlock bool
ParseSquareBlock bool
}
// Extension is a math extension // Extension is a math extension
type Extension struct { type Extension struct {
renderInternal *internal.RenderInternal renderInternal *internal.RenderInternal
enabled bool options Options
parseDollarInline bool
parseDollarBlock bool
}
// Option is the interface Options should implement
type Option interface {
SetOption(e *Extension)
}
type extensionFunc func(e *Extension)
func (fn extensionFunc) SetOption(e *Extension) {
fn(e)
}
// Enabled enables or disables this extension
func Enabled(enable ...bool) Option {
value := true
if len(enable) > 0 {
value = enable[0]
}
return extensionFunc(func(e *Extension) {
e.enabled = value
})
} }
// NewExtension creates a new math extension with the provided options // NewExtension creates a new math extension with the provided options
func NewExtension(renderInternal *internal.RenderInternal, opts ...Option) *Extension { func NewExtension(renderInternal *internal.RenderInternal, opts ...Options) *Extension {
opt := giteaUtil.OptionalArg(opts)
r := &Extension{ r := &Extension{
renderInternal: renderInternal, renderInternal: renderInternal,
enabled: true, options: opt,
parseDollarBlock: true,
parseDollarInline: true,
}
for _, o := range opts {
o.SetOption(r)
} }
return r return r
} }
// Extend extends goldmark with our parsers and renderers // Extend extends goldmark with our parsers and renderers
func (e *Extension) Extend(m goldmark.Markdown) { func (e *Extension) Extend(m goldmark.Markdown) {
if !e.enabled { if !e.options.Enabled {
return return
} }
m.Parser().AddOptions(parser.WithBlockParsers( inlines := []util.PrioritizedValue{util.Prioritized(NewInlineBracketParser(), 501)}
util.Prioritized(NewBlockParser(e.parseDollarBlock), 701), if e.options.ParseDollarInline {
)) inlines = append(inlines, util.Prioritized(NewInlineDollarParser(), 502))
inlines := []util.PrioritizedValue{
util.Prioritized(NewInlineBracketParser(), 501),
}
if e.parseDollarInline {
inlines = append(inlines, util.Prioritized(NewInlineDollarParser(), 503),
util.Prioritized(NewInlineDualDollarParser(), 502))
} }
m.Parser().AddOptions(parser.WithInlineParsers(inlines...)) m.Parser().AddOptions(parser.WithInlineParsers(inlines...))
m.Parser().AddOptions(parser.WithBlockParsers(
util.Prioritized(NewBlockParser(e.options.ParseDollarBlock, e.options.ParseSquareBlock), 701),
))
m.Renderer().AddOptions(renderer.WithNodeRenderers( m.Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(NewBlockRenderer(e.renderInternal), 501), util.Prioritized(NewBlockRenderer(e.renderInternal), 501),
util.Prioritized(NewInlineRenderer(e.renderInternal), 502), util.Prioritized(NewInlineRenderer(e.renderInternal), 502),

View File

@ -40,7 +40,7 @@ class ConanPackageConan(ConanFile):
func TestParseConanfile(t *testing.T) { func TestParseConanfile(t *testing.T) {
metadata, err := ParseConanfile(strings.NewReader(contentConanfile)) metadata, err := ParseConanfile(strings.NewReader(contentConanfile))
assert.Nil(t, err) assert.NoError(t, err)
assert.Equal(t, license, metadata.License) assert.Equal(t, license, metadata.License)
assert.Equal(t, author, metadata.Author) assert.Equal(t, author, metadata.Author)
assert.Equal(t, homepage, metadata.ProjectURL) assert.Equal(t, homepage, metadata.ProjectURL)

View File

@ -50,7 +50,7 @@ const (
func TestParseConaninfo(t *testing.T) { func TestParseConaninfo(t *testing.T) {
info, err := ParseConaninfo(strings.NewReader(contentConaninfo)) info, err := ParseConaninfo(strings.NewReader(contentConaninfo))
assert.NotNil(t, info) assert.NotNil(t, info)
assert.Nil(t, err) assert.NoError(t, err)
assert.Equal( assert.Equal(
t, t,
map[string]string{ map[string]string{

View File

@ -46,10 +46,10 @@ func testQueueBasic(t *testing.T, newFn func(cfg *BaseConfig) (baseQueue, error)
assert.NoError(t, err) assert.NoError(t, err)
if !isUnique { if !isUnique {
assert.EqualValues(t, 2, cnt) assert.EqualValues(t, 2, cnt)
assert.EqualValues(t, false, has) // non-unique queues don't check for duplicates assert.False(t, has) // non-unique queues don't check for duplicates
} else { } else {
assert.EqualValues(t, 1, cnt) assert.EqualValues(t, 1, cnt)
assert.EqualValues(t, true, has) assert.True(t, has)
} }
// push another item // push another item
@ -101,7 +101,7 @@ func testQueueBasic(t *testing.T, newFn func(cfg *BaseConfig) (baseQueue, error)
pushBlockTime = 30 * time.Millisecond pushBlockTime = 30 * time.Millisecond
err = q.PushItem(ctx, []byte("item-full")) err = q.PushItem(ctx, []byte("item-full"))
assert.ErrorIs(t, err, context.DeadlineExceeded) assert.ErrorIs(t, err, context.DeadlineExceeded)
assert.True(t, time.Since(timeStart) >= pushBlockTime*2/3) assert.GreaterOrEqual(t, time.Since(timeStart), pushBlockTime*2/3)
pushBlockTime = oldPushBlockTime pushBlockTime = oldPushBlockTime
// remove all // remove all

View File

@ -172,8 +172,8 @@ func testWorkerPoolQueuePersistence(t *testing.T, queueSetting setting.QueueSett
q2() // restart the queue to continue to execute the tasks in it q2() // restart the queue to continue to execute the tasks in it
assert.NotZero(t, len(tasksQ1)) assert.NotEmpty(t, tasksQ1)
assert.NotZero(t, len(tasksQ2)) assert.NotEmpty(t, tasksQ2)
assert.EqualValues(t, testCount, len(tasksQ1)+len(tasksQ2)) assert.EqualValues(t, testCount, len(tasksQ1)+len(tasksQ2))
} }

View File

@ -164,9 +164,9 @@ func newKeywords() {
}) })
} }
func doNewKeywords(close, reopen []string) { func doNewKeywords(closeKeywords, reopenKeywords []string) {
issueCloseKeywordsPat = makeKeywordsPat(close) issueCloseKeywordsPat = makeKeywordsPat(closeKeywords)
issueReopenKeywordsPat = makeKeywordsPat(reopen) issueReopenKeywordsPat = makeKeywordsPat(reopenKeywords)
} }
// getGiteaHostName returns a normalized string with the local host name, with no scheme or port information // getGiteaHostName returns a normalized string with the local host name, with no scheme or port information

View File

@ -526,7 +526,7 @@ func TestCustomizeCloseKeywords(t *testing.T) {
func TestParseCloseKeywords(t *testing.T) { func TestParseCloseKeywords(t *testing.T) {
// Test parsing of CloseKeywords and ReopenKeywords // Test parsing of CloseKeywords and ReopenKeywords
assert.Len(t, parseKeywords([]string{""}), 0) assert.Empty(t, parseKeywords([]string{""}))
assert.Len(t, parseKeywords([]string{" aa ", " bb ", "99", "#", "", "this is", "cc"}), 3) assert.Len(t, parseKeywords([]string{" aa ", " bb ", "99", "#", "", "this is", "cc"}), 3)
for _, test := range []struct { for _, test := range []struct {

View File

@ -9,14 +9,22 @@ import (
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
) )
func CanUserForkBetweenOwners(id1, id2 int64) bool {
if id1 != id2 {
return true
}
return setting.Repository.AllowForkIntoSameOwner
}
// CanUserForkRepo returns true if specified user can fork repository. // CanUserForkRepo returns true if specified user can fork repository.
func CanUserForkRepo(ctx context.Context, user *user_model.User, repo *repo_model.Repository) (bool, error) { func CanUserForkRepo(ctx context.Context, user *user_model.User, repo *repo_model.Repository) (bool, error) {
if user == nil { if user == nil {
return false, nil return false, nil
} }
if repo.OwnerID != user.ID && !repo_model.HasForkedRepo(ctx, user.ID, repo.ID) { if CanUserForkBetweenOwners(repo.OwnerID, user.ID) && !repo_model.HasForkedRepo(ctx, user.ID, repo.ID) {
return true, nil return true, nil
} }
ownedOrgs, err := organization.GetOrgsCanCreateRepoByUserID(ctx, user.ID) ownedOrgs, err := organization.GetOrgsCanCreateRepoByUserID(ctx, user.ID)

View File

@ -0,0 +1,25 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repository
import (
"testing"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
)
func TestCanUserForkBetweenOwners(t *testing.T) {
defer test.MockVariableValue(&setting.Repository.AllowForkIntoSameOwner)
setting.Repository.AllowForkIntoSameOwner = true
assert.True(t, CanUserForkBetweenOwners(1, 1))
assert.True(t, CanUserForkBetweenOwners(1, 2))
setting.Repository.AllowForkIntoSameOwner = false
assert.False(t, CanUserForkBetweenOwners(1, 1))
assert.True(t, CanUserForkBetweenOwners(1, 2))
}

View File

@ -62,15 +62,15 @@ func Test_calcSync(t *testing.T) {
} }
inserts, deletes, updates := calcSync(gitTags, dbReleases) inserts, deletes, updates := calcSync(gitTags, dbReleases)
if assert.EqualValues(t, 1, len(inserts), "inserts") { if assert.Len(t, inserts, 1, "inserts") {
assert.EqualValues(t, *gitTags[2], *inserts[0], "inserts equal") assert.EqualValues(t, *gitTags[2], *inserts[0], "inserts equal")
} }
if assert.EqualValues(t, 1, len(deletes), "deletes") { if assert.Len(t, deletes, 1, "deletes") {
assert.EqualValues(t, 1, deletes[0], "deletes equal") assert.EqualValues(t, 1, deletes[0], "deletes equal")
} }
if assert.EqualValues(t, 1, len(updates), "updates") { if assert.Len(t, updates, 1, "updates") {
assert.EqualValues(t, *gitTags[1], *updates[0], "updates equal") assert.EqualValues(t, *gitTags[1], *updates[0], "updates equal")
} }
} }

View File

@ -38,6 +38,6 @@ EXTEND = true
_, err = getCronSettings(cfg, "test", extended) _, err = getCronSettings(cfg, "test", extended)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, extended.Base) assert.True(t, extended.Base)
assert.EqualValues(t, extended.Second, "white rabbit") assert.EqualValues(t, "white rabbit", extended.Second)
assert.True(t, extended.Extend) assert.True(t, extended.Extend)
} }

View File

@ -74,5 +74,5 @@ DEFAULT_APPLICATIONS = tea
DEFAULT_APPLICATIONS = DEFAULT_APPLICATIONS =
`) `)
loadOAuth2From(cfg) loadOAuth2From(cfg)
assert.Nil(t, nil, OAuth2.DefaultApplications) assert.Nil(t, OAuth2.DefaultApplications)
} }

View File

@ -53,6 +53,7 @@ var (
AllowDeleteOfUnadoptedRepositories bool AllowDeleteOfUnadoptedRepositories bool
DisableDownloadSourceArchives bool DisableDownloadSourceArchives bool
AllowForkWithoutMaximumLimit bool AllowForkWithoutMaximumLimit bool
AllowForkIntoSameOwner bool
// Repository editor settings // Repository editor settings
Editor struct { Editor struct {

View File

@ -447,7 +447,7 @@ MINIO_USE_SSL = true
assert.NoError(t, loadRepoArchiveFrom(cfg)) assert.NoError(t, loadRepoArchiveFrom(cfg))
assert.EqualValues(t, "my_access_key", RepoArchive.Storage.MinioConfig.AccessKeyID) assert.EqualValues(t, "my_access_key", RepoArchive.Storage.MinioConfig.AccessKeyID)
assert.EqualValues(t, "my_secret_key", RepoArchive.Storage.MinioConfig.SecretAccessKey) assert.EqualValues(t, "my_secret_key", RepoArchive.Storage.MinioConfig.SecretAccessKey)
assert.EqualValues(t, true, RepoArchive.Storage.MinioConfig.UseSSL) assert.True(t, RepoArchive.Storage.MinioConfig.UseSSL)
assert.EqualValues(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) assert.EqualValues(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath)
} }
@ -464,7 +464,7 @@ MINIO_BASE_PATH = /prefix
assert.NoError(t, loadRepoArchiveFrom(cfg)) assert.NoError(t, loadRepoArchiveFrom(cfg))
assert.EqualValues(t, "my_access_key", RepoArchive.Storage.MinioConfig.AccessKeyID) assert.EqualValues(t, "my_access_key", RepoArchive.Storage.MinioConfig.AccessKeyID)
assert.EqualValues(t, "my_secret_key", RepoArchive.Storage.MinioConfig.SecretAccessKey) assert.EqualValues(t, "my_secret_key", RepoArchive.Storage.MinioConfig.SecretAccessKey)
assert.EqualValues(t, true, RepoArchive.Storage.MinioConfig.UseSSL) assert.True(t, RepoArchive.Storage.MinioConfig.UseSSL)
assert.EqualValues(t, "/prefix/repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) assert.EqualValues(t, "/prefix/repo-archive/", RepoArchive.Storage.MinioConfig.BasePath)
cfg, err = NewConfigProviderFromData(` cfg, err = NewConfigProviderFromData(`
@ -477,7 +477,7 @@ MINIO_BASE_PATH = /prefix
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, loadRepoArchiveFrom(cfg)) assert.NoError(t, loadRepoArchiveFrom(cfg))
assert.EqualValues(t, "127.0.0.1", RepoArchive.Storage.MinioConfig.IamEndpoint) assert.EqualValues(t, "127.0.0.1", RepoArchive.Storage.MinioConfig.IamEndpoint)
assert.EqualValues(t, true, RepoArchive.Storage.MinioConfig.UseSSL) assert.True(t, RepoArchive.Storage.MinioConfig.UseSSL)
assert.EqualValues(t, "/prefix/repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) assert.EqualValues(t, "/prefix/repo-archive/", RepoArchive.Storage.MinioConfig.BasePath)
cfg, err = NewConfigProviderFromData(` cfg, err = NewConfigProviderFromData(`
@ -495,7 +495,7 @@ MINIO_BASE_PATH = /lfs
assert.NoError(t, loadLFSFrom(cfg)) assert.NoError(t, loadLFSFrom(cfg))
assert.EqualValues(t, "my_access_key", LFS.Storage.MinioConfig.AccessKeyID) assert.EqualValues(t, "my_access_key", LFS.Storage.MinioConfig.AccessKeyID)
assert.EqualValues(t, "my_secret_key", LFS.Storage.MinioConfig.SecretAccessKey) assert.EqualValues(t, "my_secret_key", LFS.Storage.MinioConfig.SecretAccessKey)
assert.EqualValues(t, true, LFS.Storage.MinioConfig.UseSSL) assert.True(t, LFS.Storage.MinioConfig.UseSSL)
assert.EqualValues(t, "/lfs", LFS.Storage.MinioConfig.BasePath) assert.EqualValues(t, "/lfs", LFS.Storage.MinioConfig.BasePath)
cfg, err = NewConfigProviderFromData(` cfg, err = NewConfigProviderFromData(`
@ -513,7 +513,7 @@ MINIO_BASE_PATH = /lfs
assert.NoError(t, loadLFSFrom(cfg)) assert.NoError(t, loadLFSFrom(cfg))
assert.EqualValues(t, "my_access_key", LFS.Storage.MinioConfig.AccessKeyID) assert.EqualValues(t, "my_access_key", LFS.Storage.MinioConfig.AccessKeyID)
assert.EqualValues(t, "my_secret_key", LFS.Storage.MinioConfig.SecretAccessKey) assert.EqualValues(t, "my_secret_key", LFS.Storage.MinioConfig.SecretAccessKey)
assert.EqualValues(t, true, LFS.Storage.MinioConfig.UseSSL) assert.True(t, LFS.Storage.MinioConfig.UseSSL)
assert.EqualValues(t, "/lfs", LFS.Storage.MinioConfig.BasePath) assert.EqualValues(t, "/lfs", LFS.Storage.MinioConfig.BasePath)
} }

View File

@ -17,6 +17,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"reflect"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -33,9 +34,26 @@ import (
gossh "golang.org/x/crypto/ssh" gossh "golang.org/x/crypto/ssh"
) )
type contextKey string // The ssh auth overall works like this:
// NewServerConn:
// serverHandshake+serverAuthenticate:
// PublicKeyCallback:
// PublicKeyHandler (our code):
// reset(ctx.Permissions) and set ctx.Permissions.giteaKeyID = keyID
// pubKey.Verify
// return ctx.Permissions // only reaches here, the pub key is really authenticated
// set conn.Permissions from serverAuthenticate
// sessionHandler(conn)
//
// Then sessionHandler should only use the "verified keyID" from the original ssh conn, but not the ctx one.
// Otherwise, if a user provides 2 keys A (a correct one) and B (public key matches but no private key),
// then only A succeeds to authenticate, sessionHandler will see B's keyID
//
// After x/crypto >= 0.31.0 (fix CVE-2024-45337), the PublicKeyCallback will be called again for the verified key,
// it mitigates the misuse for most cases, it's still good for us to make sure we don't rely on that mitigation
// and do not misuse the PublicKeyCallback: we should only use the verified keyID from the verified ssh conn.
const giteaKeyID = contextKey("gitea-key-id") const giteaPermissionExtensionKeyID = "gitea-perm-ext-key-id"
func getExitStatusFromError(err error) int { func getExitStatusFromError(err error) int {
if err == nil { if err == nil {
@ -61,8 +79,32 @@ func getExitStatusFromError(err error) int {
return waitStatus.ExitStatus() return waitStatus.ExitStatus()
} }
// sessionPartial is the private struct from "gliderlabs/ssh/session.go"
// We need to read the original "conn" field from "ssh.Session interface" which contains the "*session pointer"
// https://github.com/gliderlabs/ssh/blob/d137aad99cd6f2d9495bfd98c755bec4e5dffb8c/session.go#L109-L113
// If upstream fixes the problem and/or changes the struct, we need to follow.
// If the struct mismatches, the builtin ssh server will fail during integration tests.
type sessionPartial struct {
sync.Mutex
gossh.Channel
conn *gossh.ServerConn
}
func ptr[T any](intf any) *T {
// https://pkg.go.dev/unsafe#Pointer
// (1) Conversion of a *T1 to Pointer to *T2.
// Provided that T2 is no larger than T1 and that the two share an equivalent memory layout,
// this conversion allows reinterpreting data of one type as data of another type.
v := reflect.ValueOf(intf)
p := v.UnsafePointer()
return (*T)(p)
}
func sessionHandler(session ssh.Session) { func sessionHandler(session ssh.Session) {
keyID := fmt.Sprintf("%d", session.Context().Value(giteaKeyID).(int64)) // here can't use session.Permissions() because it only uses the value from ctx, which might not be the authenticated one.
// so we must use the original ssh conn, which always contains the correct (verified) keyID.
sshSession := ptr[sessionPartial](session)
keyID := sshSession.conn.Permissions.Extensions[giteaPermissionExtensionKeyID]
command := session.RawCommand() command := session.RawCommand()
@ -164,6 +206,20 @@ func sessionHandler(session ssh.Session) {
} }
func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
// The publicKeyHandler (PublicKeyCallback) only helps to provide the candidate keys to authenticate,
// It does NOT really verify here, so we could only record the related information here.
// After authentication (Verify), the "Permissions" will be assigned to the ssh conn,
// then we can use it in the "session handler"
// first, reset the ctx permissions (just like https://github.com/gliderlabs/ssh/pull/243 does)
// it shouldn't be reused across different ssh conn (sessions), each pub key should have its own "Permissions"
ctx.Permissions().Permissions = &gossh.Permissions{}
setPermExt := func(keyID int64) {
ctx.Permissions().Permissions.Extensions = map[string]string{
giteaPermissionExtensionKeyID: fmt.Sprint(keyID),
}
}
if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary
log.Debug("Handle Public Key: Fingerprint: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr()) log.Debug("Handle Public Key: Fingerprint: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr())
} }
@ -238,8 +294,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary
log.Debug("Successfully authenticated: %s Certificate Fingerprint: %s Principal: %s", ctx.RemoteAddr(), gossh.FingerprintSHA256(key), principal) log.Debug("Successfully authenticated: %s Certificate Fingerprint: %s Principal: %s", ctx.RemoteAddr(), gossh.FingerprintSHA256(key), principal)
} }
ctx.SetValue(giteaKeyID, pkey.ID) setPermExt(pkey.ID)
return true return true
} }
@ -266,8 +321,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary
log.Debug("Successfully authenticated: %s Public Key Fingerprint: %s", ctx.RemoteAddr(), gossh.FingerprintSHA256(key)) log.Debug("Successfully authenticated: %s Public Key Fingerprint: %s", ctx.RemoteAddr(), gossh.FingerprintSHA256(key))
} }
ctx.SetValue(giteaKeyID, pkey.ID) setPermExt(pkey.ID)
return true return true
} }

View File

@ -53,8 +53,8 @@ func parseLegacy(datetime string) time.Time {
return t return t
} }
func anyToTime(any any) (t time.Time, isZero bool) { func anyToTime(value any) (t time.Time, isZero bool) {
switch v := any.(type) { switch v := value.(type) {
case nil: case nil:
// it is zero // it is zero
case *time.Time: case *time.Time:
@ -72,7 +72,7 @@ func anyToTime(any any) (t time.Time, isZero bool) {
case int64: case int64:
t = timeutil.TimeStamp(v).AsTime() t = timeutil.TimeStamp(v).AsTime()
default: default:
panic(fmt.Sprintf("Unsupported time type %T", any)) panic(fmt.Sprintf("Unsupported time type %T", value))
} }
return t, t.IsZero() || t.Unix() == 0 return t, t.IsZero() || t.Unix() == 0
} }

View File

@ -53,10 +53,14 @@ func (su *StringUtils) Cut(s, sep string) []any {
return []any{before, after, found} return []any{before, after, found}
} }
func (su *StringUtils) EllipsisString(s string, max int) string { func (su *StringUtils) EllipsisString(s string, maxLength int) string {
return base.EllipsisString(s, max) return base.EllipsisString(s, maxLength)
} }
func (su *StringUtils) ToUpper(s string) string { func (su *StringUtils) ToUpper(s string) string {
return strings.ToUpper(s) return strings.ToUpper(s)
} }
func (su *StringUtils) TrimPrefix(s, prefix string) string {
return strings.TrimPrefix(s, prefix)
}

View File

@ -4,7 +4,6 @@
package user package user
import ( import (
"os"
"os/exec" "os/exec"
"runtime" "runtime"
"strings" "strings"
@ -36,7 +35,7 @@ func TestCurrentUsername(t *testing.T) {
if user != whoami { if user != whoami {
t.Errorf("expected %s as user, got: %s", whoami, user) t.Errorf("expected %s as user, got: %s", whoami, user)
} }
os.Setenv("USER", "spoofed") t.Setenv("USER", "spoofed")
user = CurrentUsername() user = CurrentUsername()
if user != whoami { if user != whoami {
t.Errorf("expected %s as user, got: %s", whoami, user) t.Errorf("expected %s as user, got: %s", whoami, user)

View File

@ -27,9 +27,9 @@ func Test_HexToRBGColor(t *testing.T) {
} }
for n, c := range cases { for n, c := range cases {
r, g, b := HexToRBGColor(c.colorString) r, g, b := HexToRBGColor(c.colorString)
assert.Equal(t, c.expectedR, r, "case %d: error R should match: expected %f, but get %f", n, c.expectedR, r) assert.InDelta(t, c.expectedR, r, 0, "case %d: error R should match: expected %f, but get %f", n, c.expectedR, r)
assert.Equal(t, c.expectedG, g, "case %d: error G should match: expected %f, but get %f", n, c.expectedG, g) assert.InDelta(t, c.expectedG, g, 0, "case %d: error G should match: expected %f, but get %f", n, c.expectedG, g)
assert.Equal(t, c.expectedB, b, "case %d: error B should match: expected %f, but get %f", n, c.expectedB, b) assert.InDelta(t, c.expectedB, b, 0, "case %d: error B should match: expected %f, but get %f", n, c.expectedB, b)
} }
} }

View File

@ -10,7 +10,6 @@ import (
"crypto/sha256" "crypto/sha256"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"regexp"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -23,8 +22,8 @@ func TestKeygen(t *testing.T) {
assert.NotEmpty(t, priv) assert.NotEmpty(t, priv)
assert.NotEmpty(t, pub) assert.NotEmpty(t, pub)
assert.Regexp(t, regexp.MustCompile("^-----BEGIN RSA PRIVATE KEY-----.*"), priv) assert.Regexp(t, "^-----BEGIN RSA PRIVATE KEY-----.*", priv)
assert.Regexp(t, regexp.MustCompile("^-----BEGIN PUBLIC KEY-----.*"), pub) assert.Regexp(t, "^-----BEGIN PUBLIC KEY-----.*", pub)
} }
func TestSignUsingKeys(t *testing.T) { func TestSignUsingKeys(t *testing.T) {

View File

@ -27,9 +27,9 @@ func TestTimeStr(t *testing.T) {
t.Run(test.input, func(t *testing.T) { t.Run(test.input, func(t *testing.T) {
output, err := TimeEstimateParse(test.input) output, err := TimeEstimateParse(test.input)
if test.err { if test.err {
assert.NotNil(t, err) assert.Error(t, err)
} else { } else {
assert.Nil(t, err) assert.NoError(t, err)
} }
assert.Equal(t, test.output, output) assert.Equal(t, test.output, output)
}) })

View File

@ -122,8 +122,8 @@ func Test_NormalizeEOL(t *testing.T) {
func Test_RandomInt(t *testing.T) { func Test_RandomInt(t *testing.T) {
randInt, err := CryptoRandomInt(255) randInt, err := CryptoRandomInt(255)
assert.True(t, randInt >= 0) assert.GreaterOrEqual(t, randInt, int64(0))
assert.True(t, randInt <= 255) assert.LessOrEqual(t, randInt, int64(255))
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -223,22 +223,22 @@ func BenchmarkToUpper(b *testing.B) {
} }
func TestToTitleCase(t *testing.T) { func TestToTitleCase(t *testing.T) {
assert.Equal(t, ToTitleCase(`foo bar baz`), `Foo Bar Baz`) assert.Equal(t, `Foo Bar Baz`, ToTitleCase(`foo bar baz`))
assert.Equal(t, ToTitleCase(`FOO BAR BAZ`), `Foo Bar Baz`) assert.Equal(t, `Foo Bar Baz`, ToTitleCase(`FOO BAR BAZ`))
} }
func TestToPointer(t *testing.T) { func TestToPointer(t *testing.T) {
assert.Equal(t, "abc", *ToPointer("abc")) assert.Equal(t, "abc", *ToPointer("abc"))
assert.Equal(t, 123, *ToPointer(123)) assert.Equal(t, 123, *ToPointer(123))
abc := "abc" abc := "abc"
assert.False(t, &abc == ToPointer(abc)) assert.NotSame(t, &abc, ToPointer(abc))
val123 := 123 val123 := 123
assert.False(t, &val123 == ToPointer(val123)) assert.NotSame(t, &val123, ToPointer(val123))
} }
func TestReserveLineBreakForTextarea(t *testing.T) { func TestReserveLineBreakForTextarea(t *testing.T) {
assert.Equal(t, ReserveLineBreakForTextarea("test\r\ndata"), "test\ndata") assert.Equal(t, "test\ndata", ReserveLineBreakForTextarea("test\r\ndata"))
assert.Equal(t, ReserveLineBreakForTextarea("test\r\ndata\r\n"), "test\ndata\n") assert.Equal(t, "test\ndata\n", ReserveLineBreakForTextarea("test\r\ndata\r\n"))
} }
func TestOptionalArg(t *testing.T) { func TestOptionalArg(t *testing.T) {

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