mirror of
https://github.com/go-gitea/gitea
synced 2024-12-21 17:07:56 +01:00
Merge branch 'main' into lunny/refactor_diff_funcs
This commit is contained in:
commit
f77c40e924
@ -19,6 +19,8 @@ linters:
|
||||
- revive
|
||||
- staticcheck
|
||||
- stylecheck
|
||||
- tenv
|
||||
- testifylint
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unused
|
||||
@ -34,6 +36,10 @@ output:
|
||||
show-stats: true
|
||||
|
||||
linters-settings:
|
||||
testifylint:
|
||||
disable:
|
||||
- go-require
|
||||
- require-error
|
||||
stylecheck:
|
||||
checks: ["all", "-ST1005", "-ST1003"]
|
||||
nakedret:
|
||||
|
@ -46,7 +46,6 @@ Wim <wim@42.be> (@42wim)
|
||||
Jason Song <i@wolfogre.com> (@wolfogre)
|
||||
Yarden Shoham <git@yardenshoham.com> (@yardenshoham)
|
||||
Yu Tian <zettat123@gmail.com> (@Zettat123)
|
||||
Eddie Yang <576951401@qq.com> (@yp05327)
|
||||
Dong Ge <gedong_1994@163.com> (@sillyguodong)
|
||||
Xinyi Gong <hestergong@gmail.com> (@HesterG)
|
||||
wxiaoguang <wxiaoguang@gmail.com> (@wxiaoguang)
|
||||
|
10
Makefile
10
Makefile
@ -28,7 +28,7 @@ XGO_VERSION := go-1.23.x
|
||||
AIR_PACKAGE ?= github.com/air-verse/air@v1
|
||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.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
|
||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.5.1
|
||||
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
|
||||
lint-js: node_modules
|
||||
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES)
|
||||
# npx vue-tsc
|
||||
npx vue-tsc
|
||||
|
||||
.PHONY: lint-js-fix
|
||||
lint-js-fix: node_modules
|
||||
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix
|
||||
# npx vue-tsc
|
||||
npx vue-tsc
|
||||
|
||||
.PHONY: lint-css
|
||||
lint-css: node_modules
|
||||
@ -451,10 +451,6 @@ lint-templates: .venv node_modules
|
||||
lint-yaml: .venv
|
||||
@poetry run yamllint .
|
||||
|
||||
.PHONY: tsc
|
||||
tsc:
|
||||
npx vue-tsc
|
||||
|
||||
.PHONY: watch
|
||||
watch:
|
||||
@bash tools/watch.sh
|
||||
|
@ -1040,9 +1040,13 @@ LEVEL = Info
|
||||
;; Don't allow download source archive files from UI
|
||||
;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 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]
|
||||
|
2
go.mod
2
go.mod
@ -48,7 +48,7 @@ require (
|
||||
github.com/ethantkoenig/rupture v1.0.1
|
||||
github.com/felixge/fgprof v0.9.5
|
||||
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/jsonld v0.0.0-20221030091449-f2a191312c73
|
||||
github.com/go-chi/chi/v5 v5.1.0
|
||||
|
4
go.sum
4
go.sum
@ -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/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/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
||||
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
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/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
||||
github.com/go-ap/activitypub v0.0.0-20240910141749-b4b8c8aa484c h1:82lzmsy5Nr6JA6HcLRVxGfbdSoWfW45C6jnY3zFS7Ks=
|
||||
|
@ -137,7 +137,7 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
run.Status = aggregateJobStatus(jobs)
|
||||
run.Status = AggregateJobStatus(jobs)
|
||||
if run.Started.IsZero() && run.Status.IsRunning() {
|
||||
run.Started = timeutil.TimeStampNow()
|
||||
}
|
||||
@ -152,29 +152,35 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
|
||||
return affected, nil
|
||||
}
|
||||
|
||||
func aggregateJobStatus(jobs []*ActionRunJob) Status {
|
||||
allDone := true
|
||||
allWaiting := true
|
||||
hasFailure := false
|
||||
func AggregateJobStatus(jobs []*ActionRunJob) Status {
|
||||
allSuccessOrSkipped := len(jobs) != 0
|
||||
allSkipped := len(jobs) != 0
|
||||
var hasFailure, hasCancelled, hasWaiting, hasRunning, hasBlocked bool
|
||||
for _, job := range jobs {
|
||||
if !job.Status.IsDone() {
|
||||
allDone = false
|
||||
}
|
||||
if job.Status != StatusWaiting && !job.Status.IsDone() {
|
||||
allWaiting = false
|
||||
}
|
||||
if job.Status == StatusFailure || job.Status == StatusCancelled {
|
||||
hasFailure = true
|
||||
}
|
||||
allSuccessOrSkipped = allSuccessOrSkipped && (job.Status == StatusSuccess || job.Status == StatusSkipped)
|
||||
allSkipped = allSkipped && job.Status == StatusSkipped
|
||||
hasFailure = hasFailure || job.Status == StatusFailure
|
||||
hasCancelled = hasCancelled || job.Status == StatusCancelled
|
||||
hasWaiting = hasWaiting || job.Status == StatusWaiting
|
||||
hasRunning = hasRunning || job.Status == StatusRunning
|
||||
hasBlocked = hasBlocked || job.Status == StatusBlocked
|
||||
}
|
||||
if allDone {
|
||||
if hasFailure {
|
||||
return StatusFailure
|
||||
}
|
||||
switch {
|
||||
case allSkipped:
|
||||
return StatusSkipped
|
||||
case allSuccessOrSkipped:
|
||||
return StatusSuccess
|
||||
}
|
||||
if allWaiting {
|
||||
case hasCancelled:
|
||||
return StatusCancelled
|
||||
case hasFailure:
|
||||
return StatusFailure
|
||||
case hasRunning:
|
||||
return StatusRunning
|
||||
case hasWaiting:
|
||||
return StatusWaiting
|
||||
case hasBlocked:
|
||||
return StatusBlocked
|
||||
default:
|
||||
return StatusUnknown // it shouldn't happen
|
||||
}
|
||||
return StatusRunning
|
||||
}
|
||||
|
85
models/actions/run_job_status_test.go
Normal file
85
models/actions/run_job_status_test.go
Normal 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)
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ func TestGetLatestRunnerToken(t *testing.T) {
|
||||
token := unittest.AssertExistsAndLoadBean(t, &ActionRunnerToken{ID: 3})
|
||||
expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, token, expectedToken)
|
||||
assert.EqualValues(t, expectedToken, token)
|
||||
}
|
||||
|
||||
func TestNewRunnerToken(t *testing.T) {
|
||||
@ -26,7 +26,7 @@ func TestNewRunnerToken(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, token, expectedToken)
|
||||
assert.EqualValues(t, expectedToken, token)
|
||||
}
|
||||
|
||||
func TestUpdateRunnerToken(t *testing.T) {
|
||||
@ -36,5 +36,5 @@ func TestUpdateRunnerToken(t *testing.T) {
|
||||
assert.NoError(t, UpdateRunnerToken(db.DefaultContext, token))
|
||||
expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, token, expectedToken)
|
||||
assert.EqualValues(t, expectedToken, token)
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
package activities_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -91,11 +90,11 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, actions, contributions, "invalid action count: did the test data became too old?")
|
||||
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
|
||||
jsonData, err := json.Marshal(heatmap)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.JSONResult, string(jsonData))
|
||||
assert.JSONEq(t, tc.JSONResult, string(jsonData))
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ func TestOAuth2Application_GenerateClientSecret(t *testing.T) {
|
||||
app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
|
||||
secret, err := app.GenerateClientSecret(db.DefaultContext)
|
||||
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})
|
||||
}
|
||||
|
||||
@ -165,7 +165,7 @@ func TestOAuth2Grant_GenerateNewAuthorizationCode(t *testing.T) {
|
||||
code, err := grant.GenerateNewAuthorizationCode(db.DefaultContext, "https://example2.com/callback", "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg", "S256")
|
||||
assert.NoError(t, err)
|
||||
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) {
|
||||
|
@ -38,8 +38,6 @@ func TestIterate(t *testing.T) {
|
||||
if !has {
|
||||
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
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
@ -36,3 +36,41 @@
|
||||
updated: 1683636626
|
||||
need_approval: 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
|
||||
|
@ -26,3 +26,46 @@
|
||||
status: 1
|
||||
started: 1683636528
|
||||
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
|
||||
|
@ -57,3 +57,63 @@
|
||||
log_length: 707
|
||||
log_size: 90179
|
||||
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
|
||||
|
20
models/fixtures/action_task_output.yml
Normal file
20
models/fixtures/action_task_output.yml
Normal 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
|
@ -81,3 +81,15 @@
|
||||
is_deleted: false
|
||||
deleted_by_id: 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
|
||||
|
@ -34,7 +34,7 @@ func TestGetCommitStatuses(t *testing.T) {
|
||||
SHA: sha1,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(maxResults), 5)
|
||||
assert.Equal(t, 5, int(maxResults))
|
||||
assert.Len(t, statuses, 5)
|
||||
|
||||
assert.Equal(t, "ci/awesomeness", statuses[0].Context)
|
||||
@ -63,7 +63,7 @@ func TestGetCommitStatuses(t *testing.T) {
|
||||
SHA: sha1,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(maxResults), 5)
|
||||
assert.Equal(t, 5, int(maxResults))
|
||||
assert.Empty(t, statuses)
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
@ -76,7 +75,7 @@ func TestBranchRuleMatch(t *testing.T) {
|
||||
infact = " not"
|
||||
}
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ func TestFetchCodeComments(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("nonsense"))
|
||||
assert.Equal(t, issues_model.CommentTypeComment, issues_model.AsCommentType("comment"))
|
||||
|
@ -125,8 +125,11 @@ type Issue struct {
|
||||
IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not.
|
||||
PullRequest *PullRequest `xorm:"-"`
|
||||
NumComments int
|
||||
Ref string
|
||||
PinOrder int `xorm:"DEFAULT 0"`
|
||||
|
||||
// TODO: RemoveIssueRef: see "repo/issue/branch_selector_field.tmpl"
|
||||
Ref string
|
||||
|
||||
PinOrder int `xorm:"DEFAULT 0"`
|
||||
|
||||
DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"`
|
||||
|
||||
|
@ -18,12 +18,12 @@ func RecalculateIssueIndexForRepo(ctx context.Context, repoID int64) error {
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
var max int64
|
||||
if _, err = db.GetEngine(ctx).Select(" MAX(`index`)").Table("issue").Where("repo_id=?", repoID).Get(&max); err != nil {
|
||||
var maxIndex int64
|
||||
if _, err = db.GetEngine(ctx).Select(" MAX(`index`)").Table("issue").Where("repo_id=?", repoID).Get(&maxIndex); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -434,7 +434,7 @@ func assertCreateIssues(t *testing.T, isPull bool) {
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{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{
|
||||
Type: "heart",
|
||||
UserID: owner.ID,
|
||||
|
@ -48,17 +48,17 @@ func TestGetIssueWatchers(t *testing.T) {
|
||||
iws, err := issues_model.GetIssueWatchers(db.DefaultContext, 1, db.ListOptions{})
|
||||
assert.NoError(t, err)
|
||||
// Watcher is inactive, thus 0
|
||||
assert.Len(t, iws, 0)
|
||||
assert.Empty(t, iws)
|
||||
|
||||
iws, err = issues_model.GetIssueWatchers(db.DefaultContext, 2, db.ListOptions{})
|
||||
assert.NoError(t, err)
|
||||
// Watcher is explicit not watching
|
||||
assert.Len(t, iws, 0)
|
||||
assert.Empty(t, iws)
|
||||
|
||||
iws, err = issues_model.GetIssueWatchers(db.DefaultContext, 5, db.ListOptions{})
|
||||
assert.NoError(t, err)
|
||||
// Issue has no Watchers
|
||||
assert.Len(t, iws, 0)
|
||||
assert.Empty(t, iws)
|
||||
|
||||
iws, err = issues_model.GetIssueWatchers(db.DefaultContext, 7, db.ListOptions{})
|
||||
assert.NoError(t, err)
|
||||
|
@ -31,12 +31,12 @@ func TestLabel_LoadSelectedLabelsAfterClick(t *testing.T) {
|
||||
// First test : with negative and scope
|
||||
label.LoadSelectedLabelsAfterClick([]int64{1, -8}, []string{"", "scope"})
|
||||
assert.Equal(t, "1", label.QueryString)
|
||||
assert.Equal(t, true, label.IsSelected)
|
||||
assert.True(t, label.IsSelected)
|
||||
|
||||
// Second test : with duplicates
|
||||
label.LoadSelectedLabelsAfterClick([]int64{1, 7, 1, 7, 7}, []string{"", "scope", "", "scope", "scope"})
|
||||
assert.Equal(t, "1,8", label.QueryString)
|
||||
assert.Equal(t, false, label.IsSelected)
|
||||
assert.False(t, label.IsSelected)
|
||||
|
||||
// Third test : empty set
|
||||
label.LoadSelectedLabelsAfterClick([]int64{}, []string{})
|
||||
@ -248,7 +248,7 @@ func TestGetLabelsByIssueID(t *testing.T) {
|
||||
|
||||
labels, err = issues_model.GetLabelsByIssueID(db.DefaultContext, unittest.NonexistentID)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, labels, 0)
|
||||
assert.Empty(t, labels)
|
||||
}
|
||||
|
||||
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.Name, newLabel.Name)
|
||||
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{})
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ func TestGetMilestonesByRepoID(t *testing.T) {
|
||||
IsClosed: optional.Some(false),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, milestones, 0)
|
||||
assert.Empty(t, milestones)
|
||||
}
|
||||
|
||||
func TestGetMilestones(t *testing.T) {
|
||||
|
@ -40,7 +40,7 @@ func TestPullRequestList_LoadReviewCommentsCounts(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, reviewComments, 2)
|
||||
for _, pr := range prs {
|
||||
assert.EqualValues(t, reviewComments[pr.IssueID], 1)
|
||||
assert.EqualValues(t, 1, reviewComments[pr.IssueID])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ func TestLoadRequestedReviewers(t *testing.T) {
|
||||
assert.NoError(t, pull.LoadIssue(db.DefaultContext))
|
||||
issue := pull.Issue
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
|
@ -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})
|
||||
|
||||
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) {
|
||||
|
@ -50,7 +50,7 @@ func TestGetTrackedTimes(t *testing.T) {
|
||||
|
||||
times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{IssueID: -1})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, times, 0)
|
||||
assert.Empty(t, times)
|
||||
|
||||
// by User
|
||||
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})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, times, 0)
|
||||
assert.Empty(t, times)
|
||||
|
||||
// by Repo
|
||||
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)
|
||||
issue, err := issues_model.GetIssueByID(db.DefaultContext, times[0].IssueID)
|
||||
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})
|
||||
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})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, times, 0)
|
||||
assert.Empty(t, times)
|
||||
}
|
||||
|
||||
func TestTotalTimesForEachUser(t *testing.T) {
|
||||
|
@ -56,8 +56,8 @@ func Test_AddRepoIDForAttachment(t *testing.T) {
|
||||
err := x.Table("attachment").Where("issue_id > 0").Find(&issueAttachments)
|
||||
assert.NoError(t, err)
|
||||
for _, attach := range issueAttachments {
|
||||
assert.Greater(t, attach.RepoID, int64(0))
|
||||
assert.Greater(t, attach.IssueID, int64(0))
|
||||
assert.Positive(t, attach.RepoID)
|
||||
assert.Positive(t, attach.IssueID)
|
||||
var issue Issue
|
||||
has, err := x.ID(attach.IssueID).Get(&issue)
|
||||
assert.NoError(t, err)
|
||||
@ -69,8 +69,8 @@ func Test_AddRepoIDForAttachment(t *testing.T) {
|
||||
err = x.Table("attachment").Where("release_id > 0").Find(&releaseAttachments)
|
||||
assert.NoError(t, err)
|
||||
for _, attach := range releaseAttachments {
|
||||
assert.Greater(t, attach.RepoID, int64(0))
|
||||
assert.Greater(t, attach.ReleaseID, int64(0))
|
||||
assert.Positive(t, attach.RepoID)
|
||||
assert.Positive(t, attach.ReleaseID)
|
||||
var release Release
|
||||
has, err := x.ID(attach.ReleaseID).Get(&release)
|
||||
assert.NoError(t, err)
|
||||
|
@ -107,12 +107,12 @@ func Test_RepositoryFormat(t *testing.T) {
|
||||
repo = new(Repository)
|
||||
ok, err := x.ID(2).Get(repo)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, true, ok)
|
||||
assert.True(t, ok)
|
||||
assert.EqualValues(t, "sha1", repo.ObjectFormatName)
|
||||
|
||||
repo = new(Repository)
|
||||
ok, err = x.ID(id).Get(repo)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, true, ok)
|
||||
assert.True(t, ok)
|
||||
assert.EqualValues(t, "sha256", repo.ObjectFormatName)
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ func Test_AddUniqueIndexForProjectIssue(t *testing.T) {
|
||||
|
||||
tables, err := x.DBMetas()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(tables))
|
||||
assert.Len(t, tables, 1)
|
||||
found := false
|
||||
for _, index := range tables[0].Indexes {
|
||||
if index.Type == schemas.UniqueType {
|
||||
|
@ -40,7 +40,7 @@ func TestFindOrgs(t *testing.T) {
|
||||
IncludePrivate: false,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, orgs, 0)
|
||||
assert.Empty(t, orgs)
|
||||
|
||||
total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
||||
UserID: 4,
|
||||
|
@ -283,7 +283,7 @@ func TestGetOrgUsersByOrgID(t *testing.T) {
|
||||
OrgID: unittest.NonexistentID,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, orgUsers, 0)
|
||||
assert.Empty(t, orgUsers)
|
||||
}
|
||||
|
||||
func TestChangeOrgUserStatus(t *testing.T) {
|
||||
|
@ -15,7 +15,7 @@ func TestAccessMode(t *testing.T) {
|
||||
m := ParseAccessMode(name)
|
||||
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, AccessModeNone, ParseAccessMode("owner"))
|
||||
assert.Equal(t, AccessModeNone, ParseAccessMode("invalid"))
|
||||
|
@ -5,7 +5,6 @@ package project
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
@ -66,7 +65,7 @@ func Test_moveIssuesToAnotherColumn(t *testing.T) {
|
||||
|
||||
issues, err = column1.GetIssues(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, issues, 0)
|
||||
assert.Empty(t, issues)
|
||||
|
||||
issues, err = column2.GetIssues(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
@ -123,5 +122,5 @@ func Test_NewColumn(t *testing.T) {
|
||||
ProjectID: project1.ID,
|
||||
})
|
||||
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")
|
||||
}
|
||||
|
@ -144,8 +144,8 @@ func TestGetRepositoryByURL(t *testing.T) {
|
||||
assert.NotNil(t, repo)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, repo.ID, int64(2))
|
||||
assert.Equal(t, repo.OwnerID, int64(2))
|
||||
assert.Equal(t, int64(2), repo.ID)
|
||||
assert.Equal(t, int64(2), repo.OwnerID)
|
||||
}
|
||||
|
||||
test(t, "https://try.gitea.io/user2/repo2")
|
||||
@ -159,8 +159,8 @@ func TestGetRepositoryByURL(t *testing.T) {
|
||||
assert.NotNil(t, repo)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, repo.ID, int64(2))
|
||||
assert.Equal(t, repo.OwnerID, int64(2))
|
||||
assert.Equal(t, int64(2), repo.ID)
|
||||
assert.Equal(t, int64(2), repo.OwnerID)
|
||||
}
|
||||
|
||||
test(t, "git+ssh://sshuser@try.gitea.io/user2/repo2")
|
||||
@ -177,8 +177,8 @@ func TestGetRepositoryByURL(t *testing.T) {
|
||||
assert.NotNil(t, repo)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, repo.ID, int64(2))
|
||||
assert.Equal(t, repo.OwnerID, int64(2))
|
||||
assert.Equal(t, int64(2), repo.ID)
|
||||
assert.Equal(t, int64(2), repo.OwnerID)
|
||||
}
|
||||
|
||||
test(t, "sshuser@try.gitea.io:user2/repo2")
|
||||
|
@ -52,7 +52,7 @@ func TestRepository_GetStargazers2(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
|
||||
gazers, err := repo_model.GetStargazers(db.DefaultContext, repo, db.ListOptions{Page: 0})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, gazers, 0)
|
||||
assert.Empty(t, gazers)
|
||||
}
|
||||
|
||||
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})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, gazers, 0)
|
||||
assert.Empty(t, gazers)
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ func TestRepoAssignees(t *testing.T) {
|
||||
users, err := repo_model.GetRepoAssignees(db.DefaultContext, repo2)
|
||||
assert.NoError(t, err)
|
||||
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})
|
||||
users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21)
|
||||
|
@ -41,7 +41,7 @@ func TestGetWatchers(t *testing.T) {
|
||||
|
||||
watches, err = repo_model.GetWatchers(db.DefaultContext, unittest.NonexistentID)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, watches, 0)
|
||||
assert.Empty(t, watches)
|
||||
}
|
||||
|
||||
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})
|
||||
watchers, err = repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, watchers, 0)
|
||||
assert.Empty(t, watchers)
|
||||
}
|
||||
|
||||
func TestWatchIfAuto(t *testing.T) {
|
||||
|
@ -79,7 +79,7 @@ func AssertExistsAndLoadMap(t assert.TestingT, table string, conditions ...any)
|
||||
e := db.GetEngine(db.DefaultContext).Table(table)
|
||||
res, err := whereOrderConditions(e, conditions).Query()
|
||||
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",
|
||||
table, conditions, len(res),
|
||||
)
|
||||
|
@ -97,8 +97,7 @@ func TestListEmails(t *testing.T) {
|
||||
}
|
||||
emails, count, err := user_model.SearchEmails(db.DefaultContext, opts)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, int64(0), count)
|
||||
assert.True(t, count > 5)
|
||||
assert.Greater(t, count, int64(5))
|
||||
|
||||
contains := func(match func(s *user_model.SearchEmailResult) bool) bool {
|
||||
for _, v := range emails {
|
||||
|
@ -56,5 +56,5 @@ func TestSettings(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
settings, err = user_model.GetUserAllSettings(db.DefaultContext, 99)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, settings, 0)
|
||||
assert.Empty(t, settings)
|
||||
}
|
||||
|
@ -201,7 +201,7 @@ func TestNewGitSig(t *testing.T) {
|
||||
assert.NotContains(t, sig.Name, "<")
|
||||
assert.NotContains(t, sig.Name, ">")
|
||||
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 {
|
||||
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.Len(t, 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)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, results, 2)
|
||||
if len(results) > 2 {
|
||||
assert.Equal(t, results[0].ID, 1)
|
||||
assert.Equal(t, results[1].ID, 4)
|
||||
assert.Equal(t, 1, results[0].ID)
|
||||
assert.Equal(t, 4, results[1].ID)
|
||||
}
|
||||
}
|
||||
|
||||
@ -499,7 +499,7 @@ func Test_ValidateUser(t *testing.T) {
|
||||
{ID: 2, Visibility: structs.VisibleTypePrivate}: true,
|
||||
}
|
||||
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})
|
||||
|
||||
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
|
||||
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() {
|
||||
assert.False(t, user_model.IsFeatureDisabledWithLoginType(user, f))
|
||||
}
|
||||
@ -600,5 +600,5 @@ func TestGetInactiveUsers(t *testing.T) {
|
||||
interval := time.Now().Unix() - 1730468968 + 3600*24
|
||||
users, err = user_model.GetInactiveUsers(db.DefaultContext, time.Duration(interval*int64(time.Second)))
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, users, 0)
|
||||
assert.Empty(t, users)
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ func TestWebhook_History(t *testing.T) {
|
||||
webhook = unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2})
|
||||
tasks, err = webhook.History(db.DefaultContext, 0)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, tasks, 0)
|
||||
assert.Empty(t, tasks)
|
||||
}
|
||||
|
||||
func TestWebhook_UpdateEvent(t *testing.T) {
|
||||
@ -206,7 +206,7 @@ func TestHookTasks(t *testing.T) {
|
||||
|
||||
hookTasks, err = HookTasks(db.DefaultContext, unittest.NonexistentID, 1)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, hookTasks, 0)
|
||||
assert.Empty(t, hookTasks)
|
||||
}
|
||||
|
||||
func TestCreateHookTask(t *testing.T) {
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
@ -28,9 +27,9 @@ func TestActivityPubSignedPost(t *testing.T) {
|
||||
|
||||
expected := "BODY"
|
||||
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.Equal(t, r.Header.Get("Content-Type"), ActivityStreamsContentType)
|
||||
assert.Equal(t, ActivityStreamsContentType, r.Header.Get("Content-Type"))
|
||||
body, err := io.ReadAll(r.Body)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, string(body))
|
||||
|
@ -58,7 +58,7 @@ func TestLayered(t *testing.T) {
|
||||
assertRead := func(expected string, expectedErr error, elems ...string) {
|
||||
bs, err := assets.ReadFile(elems...)
|
||||
if err != nil {
|
||||
assert.ErrorAs(t, err, &expectedErr)
|
||||
assert.ErrorIs(t, err, expectedErr)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, string(bs))
|
||||
|
@ -15,5 +15,5 @@ func TestPamAuth(t *testing.T) {
|
||||
result, err := Auth("gitea", "user1", "false-pwd")
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, err, "Authentication failure")
|
||||
assert.Len(t, result, 0)
|
||||
assert.Len(t, result)
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ func TestDummyHasher(t *testing.T) {
|
||||
password, salt := "password", "ZogKvWdyEx"
|
||||
|
||||
hash, err := dummy.Hash(password, salt)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, hash, salt+":"+password)
|
||||
|
||||
assert.True(t, dummy.VerifyPassword(password, hash, salt))
|
||||
|
@ -99,10 +99,10 @@ func IsComplexEnough(pwd string) bool {
|
||||
func Generate(n int) (string, error) {
|
||||
NewComplexity()
|
||||
buffer := make([]byte, n)
|
||||
max := big.NewInt(int64(len(validChars)))
|
||||
maxInt := big.NewInt(int64(len(validChars)))
|
||||
for {
|
||||
for j := 0; j < n; j++ {
|
||||
rnd, err := rand.Int(rand.Reader, max)
|
||||
rnd, err := rand.Int(rand.Reader, maxInt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ package base
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -157,7 +156,7 @@ func TestStringsToInt64s(t *testing.T) {
|
||||
testSuccess([]string{"1", "4", "16", "64", "256"}, []int64{1, 4, 16, 64, 256})
|
||||
|
||||
ints, err := StringsToInt64s([]string{"-1", "a"})
|
||||
assert.Len(t, ints, 0)
|
||||
assert.Empty(t, ints)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
@ -172,9 +171,9 @@ func TestInt64sToStrings(t *testing.T) {
|
||||
// TODO: Test EntryIcon
|
||||
|
||||
func TestSetupGiteaRoot(t *testing.T) {
|
||||
_ = os.Setenv("GITEA_ROOT", "test")
|
||||
t.Setenv("GITEA_ROOT", "test")
|
||||
assert.Equal(t, "test", SetupGiteaRoot())
|
||||
_ = os.Setenv("GITEA_ROOT", "")
|
||||
t.Setenv("GITEA_ROOT", "")
|
||||
assert.NotEqual(t, "test", SetupGiteaRoot())
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ func TestPrepareFileNameAndType(t *testing.T) {
|
||||
assert.Equal(t,
|
||||
fmt.Sprintf("outFile=%s, outType=%s", expFile, expType),
|
||||
fmt.Sprintf("outFile=%s, outType=%s", outFile, outType),
|
||||
fmt.Sprintf("argFile=%s, argType=%s", argFile, argType),
|
||||
"argFile=%s, argType=%s", argFile, argType,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -146,7 +146,7 @@ func TestHasPreviousCommitSha256(t *testing.T) {
|
||||
parentSHA := MustIDFromString("b0ec7af4547047f12d5093e37ef8f1b3b5415ed8ee17894d43a34d7d34212e9c")
|
||||
notParentSHA := MustIDFromString("42e334efd04cd36eea6da0599913333c26116e1a537ca76e5b6e4af4dda00236")
|
||||
assert.Equal(t, objectFormat, parentSHA.Type())
|
||||
assert.Equal(t, objectFormat.Name(), "sha256")
|
||||
assert.Equal(t, "sha256", objectFormat.Name())
|
||||
|
||||
haz, err := commit.HasPreviousCommit(parentSHA)
|
||||
assert.NoError(t, err)
|
||||
|
@ -343,9 +343,9 @@ func TestGetCommitFileStatusMerges(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, commitFileStatus.Added, expected.Added)
|
||||
assert.Equal(t, commitFileStatus.Removed, expected.Removed)
|
||||
assert.Equal(t, commitFileStatus.Modified, expected.Modified)
|
||||
assert.Equal(t, expected.Added, commitFileStatus.Added)
|
||||
assert.Equal(t, expected.Removed, commitFileStatus.Removed)
|
||||
assert.Equal(t, expected.Modified, commitFileStatus.Modified)
|
||||
}
|
||||
|
||||
func Test_GetCommitBranchStart(t *testing.T) {
|
||||
|
@ -73,9 +73,9 @@ func TestGrepSearch(t *testing.T) {
|
||||
|
||||
res, err = GrepSearch(context.Background(), repo, "no-such-content", GrepOptions{})
|
||||
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{})
|
||||
assert.Error(t, err)
|
||||
assert.Len(t, res, 0)
|
||||
assert.Empty(t, res)
|
||||
}
|
||||
|
@ -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
|
||||
entries, err := ParseTreeEntries([]byte("100644 blob ea0d83c9081af9500ac9f804101b3fd0a5c293af"))
|
||||
assert.Error(t, err)
|
||||
assert.Len(t, entries, 0)
|
||||
assert.Empty(t, entries)
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ func TestRepository_GetBranches(t *testing.T) {
|
||||
branches, countAll, err = bareRepo1.GetBranchNames(5, 1)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, branches, 0)
|
||||
assert.Empty(t, branches)
|
||||
assert.EqualValues(t, 3, countAll)
|
||||
assert.ElementsMatch(t, []string{}, branches)
|
||||
}
|
||||
@ -66,7 +66,7 @@ func TestGetRefsBySha(t *testing.T) {
|
||||
// do not exist
|
||||
branches, err := bareRepo5.GetRefsBySha("8006ff9adbf0cb94da7dad9e537e53817f9fa5c0", "")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, branches, 0)
|
||||
assert.Empty(t, branches)
|
||||
|
||||
// refs/pull/1/head
|
||||
branches, err = bareRepo5.GetRefsBySha("c83380d7056593c51a699d12b9c00627bd5743e9", PullPrefix)
|
||||
|
@ -465,15 +465,15 @@ func (repo *Repository) getBranches(env []string, commitID string, limit int) ([
|
||||
|
||||
refs := strings.Split(stdout, "\n")
|
||||
|
||||
var max int
|
||||
var maxNum int
|
||||
if len(refs) > limit {
|
||||
max = limit
|
||||
maxNum = limit
|
||||
} else {
|
||||
max = len(refs) - 1
|
||||
maxNum = len(refs) - 1
|
||||
}
|
||||
|
||||
branches := make([]string, max)
|
||||
for i, ref := range refs[:max] {
|
||||
branches := make([]string, maxNum)
|
||||
for i, ref := range refs[:maxNum] {
|
||||
parts := strings.Fields(ref)
|
||||
|
||||
branches[i] = parts[len(parts)-1]
|
||||
|
@ -97,7 +97,7 @@ func TestReadPatch(t *testing.T) {
|
||||
assert.Empty(t, noFile)
|
||||
assert.Empty(t, noCommit)
|
||||
assert.Len(t, oldCommit, 40)
|
||||
assert.True(t, oldCommit == "6e8e2a6f9efd71dbe6917816343ed8415ad696c3")
|
||||
assert.Equal(t, "6e8e2a6f9efd71dbe6917816343ed8415ad696c3", oldCommit)
|
||||
}
|
||||
|
||||
func TestReadWritePullHead(t *testing.T) {
|
||||
@ -138,7 +138,7 @@ func TestReadWritePullHead(t *testing.T) {
|
||||
}
|
||||
|
||||
assert.Len(t, headContents, 40)
|
||||
assert.True(t, headContents == newCommit)
|
||||
assert.Equal(t, headContents, newCommit)
|
||||
|
||||
// Remove file after the test
|
||||
err = repo.RemoveReference(PullPrefix + "1/head")
|
||||
|
@ -218,13 +218,13 @@ func (g *Manager) ServerDone() {
|
||||
g.runningServerWaitGroup.Done()
|
||||
}
|
||||
|
||||
func (g *Manager) setStateTransition(old, new state) bool {
|
||||
func (g *Manager) setStateTransition(oldState, newState state) bool {
|
||||
g.lock.Lock()
|
||||
if g.state != old {
|
||||
if g.state != oldState {
|
||||
g.lock.Unlock()
|
||||
return false
|
||||
}
|
||||
g.state = new
|
||||
g.state = newState
|
||||
g.lock.Unlock()
|
||||
return true
|
||||
}
|
||||
|
@ -35,18 +35,18 @@ func BoolFieldQuery(value bool, field string) *query.BoolFieldQuery {
|
||||
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 minI, maxI *bool
|
||||
if min.Has() {
|
||||
if minOption.Has() {
|
||||
minF = new(float64)
|
||||
*minF = float64(min.Value())
|
||||
*minF = float64(minOption.Value())
|
||||
minI = new(bool)
|
||||
*minI = true
|
||||
}
|
||||
if max.Has() {
|
||||
if maxOption.Has() {
|
||||
maxF = new(float64)
|
||||
*maxF = float64(max.Value())
|
||||
*maxF = float64(maxOption.Value())
|
||||
maxI = new(bool)
|
||||
*maxI = true
|
||||
}
|
||||
|
@ -10,12 +10,12 @@ import (
|
||||
)
|
||||
|
||||
// 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
|
||||
unlimited := math.MaxInt32
|
||||
if len(max) > 0 {
|
||||
if len(maxNums) > 0 {
|
||||
// Some indexer engines have a limit on the page size, respect that
|
||||
unlimited = max[0]
|
||||
unlimited = maxNums[0]
|
||||
}
|
||||
|
||||
if paginator == nil || paginator.IsListAll() {
|
||||
|
@ -113,7 +113,7 @@ var cases = []*testIndexerCase{
|
||||
},
|
||||
},
|
||||
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))
|
||||
},
|
||||
},
|
||||
@ -176,7 +176,7 @@ var cases = []*testIndexerCase{
|
||||
IsPull: optional.Some(false),
|
||||
},
|
||||
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 {
|
||||
assert.False(t, data[v.ID].IsPull)
|
||||
}
|
||||
@ -192,7 +192,7 @@ var cases = []*testIndexerCase{
|
||||
IsPull: optional.Some(true),
|
||||
},
|
||||
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 {
|
||||
assert.True(t, data[v.ID].IsPull)
|
||||
}
|
||||
@ -208,7 +208,7 @@ var cases = []*testIndexerCase{
|
||||
IsClosed: optional.Some(false),
|
||||
},
|
||||
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 {
|
||||
assert.False(t, data[v.ID].IsClosed)
|
||||
}
|
||||
@ -224,7 +224,7 @@ var cases = []*testIndexerCase{
|
||||
IsClosed: optional.Some(true),
|
||||
},
|
||||
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 {
|
||||
assert.True(t, data[v.ID].IsClosed)
|
||||
}
|
||||
@ -274,7 +274,7 @@ var cases = []*testIndexerCase{
|
||||
MilestoneIDs: []int64{1, 2, 6},
|
||||
},
|
||||
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 {
|
||||
assert.Contains(t, []int64{1, 2, 6}, data[v.ID].MilestoneID)
|
||||
}
|
||||
@ -292,7 +292,7 @@ var cases = []*testIndexerCase{
|
||||
MilestoneIDs: []int64{0},
|
||||
},
|
||||
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 {
|
||||
assert.Equal(t, int64(0), data[v.ID].MilestoneID)
|
||||
}
|
||||
@ -310,7 +310,7 @@ var cases = []*testIndexerCase{
|
||||
ProjectID: optional.Some(int64(1)),
|
||||
},
|
||||
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 {
|
||||
assert.Equal(t, int64(1), data[v.ID].ProjectID)
|
||||
}
|
||||
@ -328,7 +328,7 @@ var cases = []*testIndexerCase{
|
||||
ProjectID: optional.Some(int64(0)),
|
||||
},
|
||||
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 {
|
||||
assert.Equal(t, int64(0), data[v.ID].ProjectID)
|
||||
}
|
||||
@ -346,7 +346,7 @@ var cases = []*testIndexerCase{
|
||||
ProjectColumnID: optional.Some(int64(1)),
|
||||
},
|
||||
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 {
|
||||
assert.Equal(t, int64(1), data[v.ID].ProjectColumnID)
|
||||
}
|
||||
@ -364,7 +364,7 @@ var cases = []*testIndexerCase{
|
||||
ProjectColumnID: optional.Some(int64(0)),
|
||||
},
|
||||
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 {
|
||||
assert.Equal(t, int64(0), data[v.ID].ProjectColumnID)
|
||||
}
|
||||
@ -382,7 +382,7 @@ var cases = []*testIndexerCase{
|
||||
PosterID: optional.Some(int64(1)),
|
||||
},
|
||||
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 {
|
||||
assert.Equal(t, int64(1), data[v.ID].PosterID)
|
||||
}
|
||||
@ -400,7 +400,7 @@ var cases = []*testIndexerCase{
|
||||
AssigneeID: optional.Some(int64(1)),
|
||||
},
|
||||
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 {
|
||||
assert.Equal(t, int64(1), data[v.ID].AssigneeID)
|
||||
}
|
||||
@ -418,7 +418,7 @@ var cases = []*testIndexerCase{
|
||||
AssigneeID: optional.Some(int64(0)),
|
||||
},
|
||||
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 {
|
||||
assert.Equal(t, int64(0), data[v.ID].AssigneeID)
|
||||
}
|
||||
@ -436,7 +436,7 @@ var cases = []*testIndexerCase{
|
||||
MentionID: optional.Some(int64(1)),
|
||||
},
|
||||
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 {
|
||||
assert.Contains(t, data[v.ID].MentionIDs, int64(1))
|
||||
}
|
||||
@ -454,7 +454,7 @@ var cases = []*testIndexerCase{
|
||||
ReviewedID: optional.Some(int64(1)),
|
||||
},
|
||||
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 {
|
||||
assert.Contains(t, data[v.ID].ReviewedIDs, int64(1))
|
||||
}
|
||||
@ -472,7 +472,7 @@ var cases = []*testIndexerCase{
|
||||
ReviewRequestedID: optional.Some(int64(1)),
|
||||
},
|
||||
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 {
|
||||
assert.Contains(t, data[v.ID].ReviewRequestedIDs, int64(1))
|
||||
}
|
||||
@ -490,7 +490,7 @@ var cases = []*testIndexerCase{
|
||||
SubscriberID: optional.Some(int64(1)),
|
||||
},
|
||||
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 {
|
||||
assert.Contains(t, data[v.ID].SubscriberIDs, int64(1))
|
||||
}
|
||||
@ -509,7 +509,7 @@ var cases = []*testIndexerCase{
|
||||
UpdatedBeforeUnix: optional.Some(int64(30)),
|
||||
},
|
||||
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 {
|
||||
assert.GreaterOrEqual(t, data[v.ID].UpdatedUnix, int64(20))
|
||||
assert.LessOrEqual(t, data[v.ID].UpdatedUnix, int64(30))
|
||||
|
@ -72,7 +72,10 @@ func (c *HTTPClient) batch(ctx context.Context, operation string, objects []Poin
|
||||
|
||||
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)
|
||||
err := json.NewEncoder(payload).Encode(request)
|
||||
if err != nil {
|
||||
|
@ -14,9 +14,12 @@ import (
|
||||
const (
|
||||
// MediaType contains the media type for LFS server requests
|
||||
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 = "application/vnd.git-lfs+json;q=0.9, */*;q=0.8"
|
||||
UserAgentHeader = "git-lfs"
|
||||
// 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"
|
||||
// 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.
|
||||
|
@ -96,7 +96,7 @@ func TestBasicTransferAdapter(t *testing.T) {
|
||||
for n, c := range cases {
|
||||
_, err := a.Download(context.Background(), c.link)
|
||||
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 {
|
||||
assert.NoError(t, err, "case %d", n)
|
||||
}
|
||||
@ -129,7 +129,7 @@ func TestBasicTransferAdapter(t *testing.T) {
|
||||
for n, c := range cases {
|
||||
err := a.Upload(context.Background(), c.link, p, bytes.NewBufferString("dummy"))
|
||||
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 {
|
||||
assert.NoError(t, err, "case %d", n)
|
||||
}
|
||||
@ -162,7 +162,7 @@ func TestBasicTransferAdapter(t *testing.T) {
|
||||
for n, c := range cases {
|
||||
err := a.Verify(context.Background(), c.link, p)
|
||||
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 {
|
||||
assert.NoError(t, err, "case %d", n)
|
||||
}
|
||||
|
@ -110,10 +110,10 @@ func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, ms
|
||||
buf = append(buf, ' ')
|
||||
}
|
||||
if flags&(Ltime|Lmicroseconds) != 0 {
|
||||
hour, min, sec := t.Clock()
|
||||
hour, minNum, sec := t.Clock()
|
||||
buf = itoa(buf, hour, 2)
|
||||
buf = append(buf, ':')
|
||||
buf = itoa(buf, min, 2)
|
||||
buf = itoa(buf, minNum, 2)
|
||||
buf = append(buf, ':')
|
||||
buf = itoa(buf, sec, 2)
|
||||
if flags&Lmicroseconds != 0 {
|
||||
|
@ -56,7 +56,7 @@ func TestLogger(t *testing.T) {
|
||||
logger := NewLoggerWithWriters(context.Background(), "test")
|
||||
|
||||
dump := logger.DumpWriters()
|
||||
assert.EqualValues(t, 0, len(dump))
|
||||
assert.Empty(t, dump)
|
||||
assert.EqualValues(t, NONE, logger.GetLevel())
|
||||
assert.False(t, logger.IsEnabled())
|
||||
|
||||
@ -69,7 +69,7 @@ func TestLogger(t *testing.T) {
|
||||
assert.EqualValues(t, DEBUG, logger.GetLevel())
|
||||
|
||||
dump = logger.DumpWriters()
|
||||
assert.EqualValues(t, 2, len(dump))
|
||||
assert.Len(t, dump, 2)
|
||||
|
||||
logger.Trace("trace-level") // this level is not logged
|
||||
logger.Debug("debug-level")
|
||||
|
@ -278,12 +278,12 @@ func TestRender_AutoLink(t *testing.T) {
|
||||
test := func(input, expected string) {
|
||||
var buffer strings.Builder
|
||||
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()))
|
||||
|
||||
buffer.Reset()
|
||||
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()))
|
||||
}
|
||||
|
||||
|
@ -78,26 +78,23 @@ func (r *GlodmarkRender) Renderer() renderer.Renderer {
|
||||
|
||||
func (r *GlodmarkRender) highlightingRenderer(w util.BufWriter, c highlighting.CodeBlockContext, entering bool) {
|
||||
if entering {
|
||||
language, _ := c.Language()
|
||||
if language == nil {
|
||||
language = []byte("text")
|
||||
}
|
||||
languageBytes, _ := c.Language()
|
||||
languageStr := giteautil.IfZero(string(languageBytes), "text")
|
||||
|
||||
languageStr := string(language)
|
||||
|
||||
preClasses := []string{"code-block"}
|
||||
preClasses := "code-block"
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
||||
// include language-x class as part of commonmark spec
|
||||
// the "display" class is used by "js/markup/math.js" to render the code element as a block
|
||||
err = r.ctx.RenderInternal.FormatWithSafeAttrs(w, `<code class="chroma language-%s display">`, string(language))
|
||||
// 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.ts" to render the code element as a block
|
||||
// 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 {
|
||||
return
|
||||
}
|
||||
@ -128,7 +125,12 @@ func SpecializedMarkdown(ctx *markup.RenderContext) *GlodmarkRender {
|
||||
),
|
||||
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,
|
||||
),
|
||||
goldmark.WithParserOptions(
|
||||
|
@ -12,31 +12,32 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const nl = "\n"
|
||||
|
||||
func TestMathRender(t *testing.T) {
|
||||
const nl = "\n"
|
||||
testcases := []struct {
|
||||
testcase string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"$a$",
|
||||
`<p><code class="language-math is-loading">a</code></p>` + nl,
|
||||
`<p><code class="language-math">a</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
"$ a $",
|
||||
`<p><code class="language-math is-loading">a</code></p>` + nl,
|
||||
`<p><code class="language-math">a</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
"$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\)`,
|
||||
`<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$.`,
|
||||
`<p><code class="language-math is-loading">a</code>.</p>` + nl,
|
||||
`<p><code class="language-math">a</code>.</p>` + nl,
|
||||
},
|
||||
{
|
||||
`.$a$`,
|
||||
@ -64,27 +65,39 @@ func TestMathRender(t *testing.T) {
|
||||
},
|
||||
{
|
||||
"$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$$",
|
||||
`<code class="chroma language-math display">a</code>` + nl,
|
||||
`<code class="language-math display">a</code>` + nl,
|
||||
},
|
||||
{
|
||||
"$$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$$",
|
||||
`<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`,
|
||||
`<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$}$`,
|
||||
`<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
|
||||
\]
|
||||
`,
|
||||
`<pre class="code-block is-loading"><code class="chroma language-math display">
|
||||
`<pre class="code-block is-loading"><code class="language-math display">
|
||||
\alpha
|
||||
</code></pre>
|
||||
`,
|
||||
@ -122,7 +135,7 @@ func TestMathRenderBlockIndent(t *testing.T) {
|
||||
\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
|
||||
</code></pre>
|
||||
`,
|
||||
@ -137,7 +150,7 @@ a
|
||||
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
|
||||
b
|
||||
c
|
||||
@ -154,7 +167,7 @@ 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
|
||||
b
|
||||
c
|
||||
@ -165,7 +178,7 @@ c
|
||||
"indent-0-oneline",
|
||||
`$$ x $$
|
||||
foo`,
|
||||
`<code class="chroma language-math display"> x </code>
|
||||
`<code class="language-math display"> x </code>
|
||||
<p>foo</p>
|
||||
`,
|
||||
},
|
||||
@ -173,7 +186,7 @@ foo`,
|
||||
"indent-3-oneline",
|
||||
` $$ x $$<SPACE>
|
||||
foo`,
|
||||
`<code class="chroma language-math display"> x </code>
|
||||
`<code class="language-math display"> x </code>
|
||||
<p>foo</p>
|
||||
`,
|
||||
},
|
||||
@ -188,10 +201,10 @@ foo`,
|
||||
> \]
|
||||
`,
|
||||
`<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
|
||||
</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
|
||||
</code></pre>
|
||||
</blockquote>
|
||||
@ -207,7 +220,7 @@ b
|
||||
2. b`,
|
||||
`<ol>
|
||||
<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
|
||||
</code></pre>
|
||||
</li>
|
||||
@ -215,6 +228,11 @@ x
|
||||
</ol>
|
||||
`,
|
||||
},
|
||||
{
|
||||
"inline-non-math",
|
||||
`\[x]`,
|
||||
`<p>[x]</p>` + nl,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testcases {
|
||||
|
@ -16,16 +16,18 @@ import (
|
||||
|
||||
type blockParser struct {
|
||||
parseDollars bool
|
||||
parseSquare bool
|
||||
endBytesDollars []byte
|
||||
endBytesBracket []byte
|
||||
endBytesSquare []byte
|
||||
}
|
||||
|
||||
// NewBlockParser creates a new math BlockParser
|
||||
func NewBlockParser(parseDollarBlocks bool) parser.BlockParser {
|
||||
func NewBlockParser(parseDollars, parseSquare bool) parser.BlockParser {
|
||||
return &blockParser{
|
||||
parseDollars: parseDollarBlocks,
|
||||
parseDollars: parseDollars,
|
||||
parseSquare: parseSquare,
|
||||
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
|
||||
if b.parseDollars && line[pos] == '$' && line[pos+1] == '$' {
|
||||
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(`\]`)) {
|
||||
// do not process escaped attention block: "> \[!NOTE\]"
|
||||
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)
|
||||
|
||||
// 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)
|
||||
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++ {
|
||||
if line[i] != ' ' && line[i] != '\n' {
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
node.Lines().Append(segment)
|
||||
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()
|
||||
w, pos := util.IndentWidth(line, reader.LineOffset())
|
||||
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 util.IsBlank(line[pos+len(endBytes):]) {
|
||||
newline := giteaUtil.Iif(line[len(line)-1] != '\n', 0, 1)
|
||||
|
@ -12,6 +12,17 @@ import (
|
||||
"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
|
||||
type BlockRenderer struct {
|
||||
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) {
|
||||
n := node.(*Block)
|
||||
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.writeLines(w, source, n)
|
||||
} else {
|
||||
|
@ -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{},
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ import (
|
||||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
|
||||
// Inline represents inline math e.g. $...$ or \(...\)
|
||||
// Inline struct represents inline math e.g. $...$ or \(...\)
|
||||
type Inline struct {
|
||||
ast.BaseInline
|
||||
}
|
||||
|
@ -12,31 +12,25 @@ import (
|
||||
)
|
||||
|
||||
type inlineParser struct {
|
||||
start []byte
|
||||
end []byte
|
||||
trigger []byte
|
||||
endBytesSingleDollar []byte
|
||||
endBytesDoubleDollar []byte
|
||||
endBytesBracket []byte
|
||||
}
|
||||
|
||||
var defaultInlineDollarParser = &inlineParser{
|
||||
start: []byte{'$'},
|
||||
end: []byte{'$'},
|
||||
}
|
||||
|
||||
var defaultDualDollarParser = &inlineParser{
|
||||
start: []byte{'$', '$'},
|
||||
end: []byte{'$', '$'},
|
||||
trigger: []byte{'$'},
|
||||
endBytesSingleDollar: []byte{'$'},
|
||||
endBytesDoubleDollar: []byte{'$', '$'},
|
||||
}
|
||||
|
||||
func NewInlineDollarParser() parser.InlineParser {
|
||||
return defaultInlineDollarParser
|
||||
}
|
||||
|
||||
func NewInlineDualDollarParser() parser.InlineParser {
|
||||
return defaultDualDollarParser
|
||||
}
|
||||
|
||||
var defaultInlineBracketParser = &inlineParser{
|
||||
start: []byte{'\\', '('},
|
||||
end: []byte{'\\', ')'},
|
||||
trigger: []byte{'\\', '('},
|
||||
endBytesBracket: []byte{'\\', ')'},
|
||||
}
|
||||
|
||||
func NewInlineBracketParser() parser.InlineParser {
|
||||
@ -45,7 +39,7 @@ func NewInlineBracketParser() parser.InlineParser {
|
||||
|
||||
// Trigger triggers this parser on $ or \
|
||||
func (parser *inlineParser) Trigger() []byte {
|
||||
return parser.start
|
||||
return parser.trigger
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
return nil
|
||||
}
|
||||
|
||||
precedingCharacter := block.PrecendingCharacter()
|
||||
if precedingCharacter < 256 && (isAlphanumeric(byte(precedingCharacter)) || isPunctuation(byte(precedingCharacter))) {
|
||||
// need to exclude things like `a$` from being considered a start
|
||||
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()
|
||||
if precedingCharacter < 256 && (isAlphanumeric(byte(precedingCharacter)) || isPunctuation(byte(precedingCharacter))) {
|
||||
// need to exclude things like `a$` from being considered a start
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// move the opener marker point at the start of the text
|
||||
opener := len(parser.start)
|
||||
opener := startMarkLen
|
||||
|
||||
// Now look for an ending line
|
||||
depth := 0
|
||||
ender := -1
|
||||
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)
|
||||
if i+len(parser.end) < len(line) {
|
||||
succeedingCharacter = line[i+len(parser.end)]
|
||||
if i+len(stopMark) < len(line) {
|
||||
succeedingCharacter = line[i+len(stopMark)]
|
||||
}
|
||||
// check valid ending character
|
||||
isValidEndingChar := isPunctuation(succeedingCharacter) || isBracket(succeedingCharacter) ||
|
||||
succeedingCharacter == ' ' || succeedingCharacter == '\n' || succeedingCharacter == 0
|
||||
if !isValidEndingChar {
|
||||
if checkSurrounding && !isValidEndingChar {
|
||||
break
|
||||
}
|
||||
ender = i
|
||||
@ -112,21 +133,12 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
|
||||
|
||||
block.Advance(opener)
|
||||
_, pos := block.Position()
|
||||
var node ast.Node
|
||||
if parser == defaultDualDollarParser {
|
||||
node = NewInlineBlock()
|
||||
} else {
|
||||
node = NewInline()
|
||||
}
|
||||
node := NewInline()
|
||||
|
||||
segment := pos.WithStop(pos.Start + ender - opener)
|
||||
node.AppendChild(node, ast.NewRawTextSegment(segment))
|
||||
block.Advance(ender - opener + len(parser.end))
|
||||
|
||||
if parser == defaultDualDollarParser {
|
||||
trimBlock(&(node.(*InlineBlock)).Inline, block)
|
||||
} else {
|
||||
trimBlock(node.(*Inline), block)
|
||||
}
|
||||
block.Advance(ender - opener + len(stopMark))
|
||||
trimBlock(node, block)
|
||||
return node
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,9 @@ import (
|
||||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
|
||||
// Inline render output:
|
||||
// <code class="language-math">...</code>
|
||||
|
||||
// InlineRenderer is an inline renderer
|
||||
type InlineRenderer struct {
|
||||
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) {
|
||||
if entering {
|
||||
extraClass := ""
|
||||
if _, ok := n.(*InlineBlock); ok {
|
||||
extraClass = "display "
|
||||
}
|
||||
_ = r.renderInternal.FormatWithSafeAttrs(w, `<code class="language-math %sis-loading">`, extraClass)
|
||||
_ = r.renderInternal.FormatWithSafeAttrs(w, `<code class="language-math">`)
|
||||
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
|
||||
segment := c.(*ast.Text).Segment
|
||||
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
|
||||
func (r *InlineRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||
reg.Register(KindInline, r.renderInline)
|
||||
reg.Register(KindInlineBlock, r.renderInline)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ package math
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/markup/internal"
|
||||
giteaUtil "code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
@ -12,70 +13,45 @@ import (
|
||||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Enabled bool
|
||||
ParseDollarInline bool
|
||||
ParseDollarBlock bool
|
||||
ParseSquareBlock bool
|
||||
}
|
||||
|
||||
// Extension is a math extension
|
||||
type Extension struct {
|
||||
renderInternal *internal.RenderInternal
|
||||
enabled bool
|
||||
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
|
||||
})
|
||||
renderInternal *internal.RenderInternal
|
||||
options 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{
|
||||
renderInternal: renderInternal,
|
||||
enabled: true,
|
||||
parseDollarBlock: true,
|
||||
parseDollarInline: true,
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o.SetOption(r)
|
||||
renderInternal: renderInternal,
|
||||
options: opt,
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Extend extends goldmark with our parsers and renderers
|
||||
func (e *Extension) Extend(m goldmark.Markdown) {
|
||||
if !e.enabled {
|
||||
if !e.options.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
m.Parser().AddOptions(parser.WithBlockParsers(
|
||||
util.Prioritized(NewBlockParser(e.parseDollarBlock), 701),
|
||||
))
|
||||
|
||||
inlines := []util.PrioritizedValue{
|
||||
util.Prioritized(NewInlineBracketParser(), 501),
|
||||
}
|
||||
if e.parseDollarInline {
|
||||
inlines = append(inlines, util.Prioritized(NewInlineDollarParser(), 503),
|
||||
util.Prioritized(NewInlineDualDollarParser(), 502))
|
||||
inlines := []util.PrioritizedValue{util.Prioritized(NewInlineBracketParser(), 501)}
|
||||
if e.options.ParseDollarInline {
|
||||
inlines = append(inlines, util.Prioritized(NewInlineDollarParser(), 502))
|
||||
}
|
||||
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(
|
||||
util.Prioritized(NewBlockRenderer(e.renderInternal), 501),
|
||||
util.Prioritized(NewInlineRenderer(e.renderInternal), 502),
|
||||
|
@ -40,7 +40,7 @@ class ConanPackageConan(ConanFile):
|
||||
|
||||
func TestParseConanfile(t *testing.T) {
|
||||
metadata, err := ParseConanfile(strings.NewReader(contentConanfile))
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, license, metadata.License)
|
||||
assert.Equal(t, author, metadata.Author)
|
||||
assert.Equal(t, homepage, metadata.ProjectURL)
|
||||
|
@ -50,7 +50,7 @@ const (
|
||||
func TestParseConaninfo(t *testing.T) {
|
||||
info, err := ParseConaninfo(strings.NewReader(contentConaninfo))
|
||||
assert.NotNil(t, info)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(
|
||||
t,
|
||||
map[string]string{
|
||||
|
@ -46,10 +46,10 @@ func testQueueBasic(t *testing.T, newFn func(cfg *BaseConfig) (baseQueue, error)
|
||||
assert.NoError(t, err)
|
||||
if !isUnique {
|
||||
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 {
|
||||
assert.EqualValues(t, 1, cnt)
|
||||
assert.EqualValues(t, true, has)
|
||||
assert.True(t, has)
|
||||
}
|
||||
|
||||
// push another item
|
||||
@ -101,7 +101,7 @@ func testQueueBasic(t *testing.T, newFn func(cfg *BaseConfig) (baseQueue, error)
|
||||
pushBlockTime = 30 * time.Millisecond
|
||||
err = q.PushItem(ctx, []byte("item-full"))
|
||||
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
|
||||
|
||||
// remove all
|
||||
|
@ -172,8 +172,8 @@ func testWorkerPoolQueuePersistence(t *testing.T, queueSetting setting.QueueSett
|
||||
|
||||
q2() // restart the queue to continue to execute the tasks in it
|
||||
|
||||
assert.NotZero(t, len(tasksQ1))
|
||||
assert.NotZero(t, len(tasksQ2))
|
||||
assert.NotEmpty(t, tasksQ1)
|
||||
assert.NotEmpty(t, tasksQ2)
|
||||
assert.EqualValues(t, testCount, len(tasksQ1)+len(tasksQ2))
|
||||
}
|
||||
|
||||
|
@ -164,9 +164,9 @@ func newKeywords() {
|
||||
})
|
||||
}
|
||||
|
||||
func doNewKeywords(close, reopen []string) {
|
||||
issueCloseKeywordsPat = makeKeywordsPat(close)
|
||||
issueReopenKeywordsPat = makeKeywordsPat(reopen)
|
||||
func doNewKeywords(closeKeywords, reopenKeywords []string) {
|
||||
issueCloseKeywordsPat = makeKeywordsPat(closeKeywords)
|
||||
issueReopenKeywordsPat = makeKeywordsPat(reopenKeywords)
|
||||
}
|
||||
|
||||
// getGiteaHostName returns a normalized string with the local host name, with no scheme or port information
|
||||
|
@ -526,7 +526,7 @@ func TestCustomizeCloseKeywords(t *testing.T) {
|
||||
|
||||
func TestParseCloseKeywords(t *testing.T) {
|
||||
// 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)
|
||||
|
||||
for _, test := range []struct {
|
||||
|
@ -9,14 +9,22 @@ import (
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
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.
|
||||
func CanUserForkRepo(ctx context.Context, user *user_model.User, repo *repo_model.Repository) (bool, error) {
|
||||
if user == 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
|
||||
}
|
||||
ownedOrgs, err := organization.GetOrgsCanCreateRepoByUserID(ctx, user.ID)
|
||||
|
25
modules/repository/fork_test.go
Normal file
25
modules/repository/fork_test.go
Normal 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))
|
||||
}
|
@ -62,15 +62,15 @@ func Test_calcSync(t *testing.T) {
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
if assert.EqualValues(t, 1, len(deletes), "deletes") {
|
||||
if assert.Len(t, deletes, 1, "deletes") {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,6 @@ EXTEND = true
|
||||
_, err = getCronSettings(cfg, "test", extended)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, extended.Base)
|
||||
assert.EqualValues(t, extended.Second, "white rabbit")
|
||||
assert.EqualValues(t, "white rabbit", extended.Second)
|
||||
assert.True(t, extended.Extend)
|
||||
}
|
||||
|
@ -74,5 +74,5 @@ DEFAULT_APPLICATIONS = tea
|
||||
DEFAULT_APPLICATIONS =
|
||||
`)
|
||||
loadOAuth2From(cfg)
|
||||
assert.Nil(t, nil, OAuth2.DefaultApplications)
|
||||
assert.Nil(t, OAuth2.DefaultApplications)
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ var (
|
||||
AllowDeleteOfUnadoptedRepositories bool
|
||||
DisableDownloadSourceArchives bool
|
||||
AllowForkWithoutMaximumLimit bool
|
||||
AllowForkIntoSameOwner bool
|
||||
|
||||
// Repository editor settings
|
||||
Editor struct {
|
||||
|
@ -447,7 +447,7 @@ MINIO_USE_SSL = true
|
||||
assert.NoError(t, loadRepoArchiveFrom(cfg))
|
||||
assert.EqualValues(t, "my_access_key", RepoArchive.Storage.MinioConfig.AccessKeyID)
|
||||
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)
|
||||
}
|
||||
|
||||
@ -464,7 +464,7 @@ MINIO_BASE_PATH = /prefix
|
||||
assert.NoError(t, loadRepoArchiveFrom(cfg))
|
||||
assert.EqualValues(t, "my_access_key", RepoArchive.Storage.MinioConfig.AccessKeyID)
|
||||
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)
|
||||
|
||||
cfg, err = NewConfigProviderFromData(`
|
||||
@ -477,7 +477,7 @@ MINIO_BASE_PATH = /prefix
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, loadRepoArchiveFrom(cfg))
|
||||
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)
|
||||
|
||||
cfg, err = NewConfigProviderFromData(`
|
||||
@ -495,7 +495,7 @@ MINIO_BASE_PATH = /lfs
|
||||
assert.NoError(t, loadLFSFrom(cfg))
|
||||
assert.EqualValues(t, "my_access_key", LFS.Storage.MinioConfig.AccessKeyID)
|
||||
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)
|
||||
|
||||
cfg, err = NewConfigProviderFromData(`
|
||||
@ -513,7 +513,7 @@ MINIO_BASE_PATH = /lfs
|
||||
assert.NoError(t, loadLFSFrom(cfg))
|
||||
assert.EqualValues(t, "my_access_key", LFS.Storage.MinioConfig.AccessKeyID)
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -33,9 +34,26 @@ import (
|
||||
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 {
|
||||
if err == nil {
|
||||
@ -61,8 +79,32 @@ func getExitStatusFromError(err error) int {
|
||||
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) {
|
||||
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()
|
||||
|
||||
@ -164,6 +206,20 @@ func sessionHandler(session ssh.Session) {
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
@ -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
|
||||
log.Debug("Successfully authenticated: %s Public Key Fingerprint: %s", ctx.RemoteAddr(), gossh.FingerprintSHA256(key))
|
||||
}
|
||||
ctx.SetValue(giteaKeyID, pkey.ID)
|
||||
|
||||
setPermExt(pkey.ID)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -53,8 +53,8 @@ func parseLegacy(datetime string) time.Time {
|
||||
return t
|
||||
}
|
||||
|
||||
func anyToTime(any any) (t time.Time, isZero bool) {
|
||||
switch v := any.(type) {
|
||||
func anyToTime(value any) (t time.Time, isZero bool) {
|
||||
switch v := value.(type) {
|
||||
case nil:
|
||||
// it is zero
|
||||
case *time.Time:
|
||||
@ -72,7 +72,7 @@ func anyToTime(any any) (t time.Time, isZero bool) {
|
||||
case int64:
|
||||
t = timeutil.TimeStamp(v).AsTime()
|
||||
default:
|
||||
panic(fmt.Sprintf("Unsupported time type %T", any))
|
||||
panic(fmt.Sprintf("Unsupported time type %T", value))
|
||||
}
|
||||
return t, t.IsZero() || t.Unix() == 0
|
||||
}
|
||||
|
@ -53,10 +53,14 @@ func (su *StringUtils) Cut(s, sep string) []any {
|
||||
return []any{before, after, found}
|
||||
}
|
||||
|
||||
func (su *StringUtils) EllipsisString(s string, max int) string {
|
||||
return base.EllipsisString(s, max)
|
||||
func (su *StringUtils) EllipsisString(s string, maxLength int) string {
|
||||
return base.EllipsisString(s, maxLength)
|
||||
}
|
||||
|
||||
func (su *StringUtils) ToUpper(s string) string {
|
||||
return strings.ToUpper(s)
|
||||
}
|
||||
|
||||
func (su *StringUtils) TrimPrefix(s, prefix string) string {
|
||||
return strings.TrimPrefix(s, prefix)
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
@ -36,7 +35,7 @@ func TestCurrentUsername(t *testing.T) {
|
||||
if user != whoami {
|
||||
t.Errorf("expected %s as user, got: %s", whoami, user)
|
||||
}
|
||||
os.Setenv("USER", "spoofed")
|
||||
t.Setenv("USER", "spoofed")
|
||||
user = CurrentUsername()
|
||||
if user != whoami {
|
||||
t.Errorf("expected %s as user, got: %s", whoami, user)
|
||||
|
@ -27,9 +27,9 @@ func Test_HexToRBGColor(t *testing.T) {
|
||||
}
|
||||
for n, c := range cases {
|
||||
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.Equal(t, c.expectedG, g, "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.expectedR, r, 0, "case %d: error R should match: expected %f, but get %f", n, c.expectedR, r)
|
||||
assert.InDelta(t, c.expectedG, g, 0, "case %d: error G should match: expected %f, but get %f", n, c.expectedG, g)
|
||||
assert.InDelta(t, c.expectedB, b, 0, "case %d: error B should match: expected %f, but get %f", n, c.expectedB, b)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -23,8 +22,8 @@ func TestKeygen(t *testing.T) {
|
||||
assert.NotEmpty(t, priv)
|
||||
assert.NotEmpty(t, pub)
|
||||
|
||||
assert.Regexp(t, regexp.MustCompile("^-----BEGIN RSA PRIVATE KEY-----.*"), priv)
|
||||
assert.Regexp(t, regexp.MustCompile("^-----BEGIN PUBLIC KEY-----.*"), pub)
|
||||
assert.Regexp(t, "^-----BEGIN RSA PRIVATE KEY-----.*", priv)
|
||||
assert.Regexp(t, "^-----BEGIN PUBLIC KEY-----.*", pub)
|
||||
}
|
||||
|
||||
func TestSignUsingKeys(t *testing.T) {
|
||||
|
@ -27,9 +27,9 @@ func TestTimeStr(t *testing.T) {
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
output, err := TimeEstimateParse(test.input)
|
||||
if test.err {
|
||||
assert.NotNil(t, err)
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, test.output, output)
|
||||
})
|
||||
|
@ -122,8 +122,8 @@ func Test_NormalizeEOL(t *testing.T) {
|
||||
|
||||
func Test_RandomInt(t *testing.T) {
|
||||
randInt, err := CryptoRandomInt(255)
|
||||
assert.True(t, randInt >= 0)
|
||||
assert.True(t, randInt <= 255)
|
||||
assert.GreaterOrEqual(t, randInt, int64(0))
|
||||
assert.LessOrEqual(t, randInt, int64(255))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@ -223,22 +223,22 @@ func BenchmarkToUpper(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestToTitleCase(t *testing.T) {
|
||||
assert.Equal(t, ToTitleCase(`foo bar baz`), `Foo Bar Baz`)
|
||||
assert.Equal(t, ToTitleCase(`FOO BAR BAZ`), `Foo Bar Baz`)
|
||||
assert.Equal(t, `Foo Bar Baz`, ToTitleCase(`foo bar baz`))
|
||||
assert.Equal(t, `Foo Bar Baz`, ToTitleCase(`FOO BAR BAZ`))
|
||||
}
|
||||
|
||||
func TestToPointer(t *testing.T) {
|
||||
assert.Equal(t, "abc", *ToPointer("abc"))
|
||||
assert.Equal(t, 123, *ToPointer(123))
|
||||
abc := "abc"
|
||||
assert.False(t, &abc == ToPointer(abc))
|
||||
assert.NotSame(t, &abc, ToPointer(abc))
|
||||
val123 := 123
|
||||
assert.False(t, &val123 == ToPointer(val123))
|
||||
assert.NotSame(t, &val123, ToPointer(val123))
|
||||
}
|
||||
|
||||
func TestReserveLineBreakForTextarea(t *testing.T) {
|
||||
assert.Equal(t, ReserveLineBreakForTextarea("test\r\ndata"), "test\ndata")
|
||||
assert.Equal(t, ReserveLineBreakForTextarea("test\r\ndata\r\n"), "test\ndata\n")
|
||||
assert.Equal(t, "test\ndata", ReserveLineBreakForTextarea("test\r\ndata"))
|
||||
assert.Equal(t, "test\ndata\n", ReserveLineBreakForTextarea("test\r\ndata\r\n"))
|
||||
}
|
||||
|
||||
func TestOptionalArg(t *testing.T) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user