0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-05-06 23:38:48 +02:00

Merge remote-tracking branch 'origin/main' into dropdowncss

* origin/main: (34 commits)
  Fine tune diff highlighting (#36592)
  Add code editor setting dropdowns (#36534)
  Update to go 1.26.0 and golangci-lint 2.9.0 (#36588)
  Improve diff highlighting (#36583)
  Fix markup code block layout (#36578)
  Remove striped tables in UI (#36509)
  Fix vertical alignment of `.commit-sign-badge` children (#36570)
  Fix mirror sync parser and fix mirror messages (#36504)
  Update JS and PY deps (#36576)
  Add viewer controller for mermaid (zoom, drag) (#36557)
  Misc typescript tweaks (#36523)
  Use full-file highlighting for diff sections (#36561)
  fix(diff): reprocess htmx content after loading more files (#36568)
  [skip ci] Updated translations via Crowdin
  Add wrap to runner label list (#36565)
  fix: add dnf5 command for Fedora in RPM package instructions (#36527)
  Enable pagination on GiteaDownloader.getIssueReactions() (#36549)
  Refactor merge conan and container auth preserve actions taskID (#36560)
  Fix assignee sidebar links and empty placeholder after #32465 refactor (#36559)
  Fix various version parsing problems (#36553)
  ...
This commit is contained in:
silverwind 2026-02-12 18:28:41 +01:00
commit a4d46ca1b4
No known key found for this signature in database
GPG Key ID: 2E62B41C93869443
201 changed files with 3127 additions and 2037 deletions

2
.gitignore vendored
View File

@ -121,8 +121,6 @@ prime/
/.goosehints
/.windsurfrules
/.github/copilot-instructions.md
/AGENT.md
/CLAUDE.md
/llms.txt
# Ignore worktrees when working on multiple branches

View File

@ -1,8 +1,6 @@
# Instructions for agents
- Use `make help` to find available development targets
- Use the latest Golang stable release when working on Go code
- Use the latest Node.js LTS release when working on TypeScript code
- Before committing `.go` changes, run `make fmt` to format, and run `make lint-go` to lint
- Before committing `.ts` changes, run `make lint-js` to lint
- Before committing `go.mod` changes, run `make tidy`

1
CLAUDE.md Normal file
View File

@ -0,0 +1 @@
@AGENTS.md

View File

@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1
# Build stage
FROM docker.io/library/golang:1.25-alpine3.23 AS build-env
FROM docker.io/library/golang:1.26-alpine3.23 AS build-env
ARG GOPROXY=direct

View File

@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1
# Build stage
FROM docker.io/library/golang:1.25-alpine3.23 AS build-env
FROM docker.io/library/golang:1.26-alpine3.23 AS build-env
ARG GOPROXY=direct

View File

@ -32,7 +32,7 @@ XGO_VERSION := go-1.25.x
AIR_PACKAGE ?= github.com/air-verse/air@v1
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.2
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.8.0
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.9.0
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.7.0
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1

File diff suppressed because one or more lines are too long

View File

@ -134,7 +134,7 @@ func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
}
log.Trace(" currentNumReleases is %d, running SyncReleasesWithTags", oldnum)
if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
if _, err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
log.Warn(" SyncReleasesWithTags: %v", err)
gitRepo.Close()
continue

View File

@ -342,7 +342,7 @@ export default defineConfig([
'import-x/first': [2],
'import-x/group-exports': [0],
'import-x/max-dependencies': [0],
'import-x/named': [2],
'import-x/named': [0],
'import-x/namespace': [0],
'import-x/newline-after-import': [0],
'import-x/no-absolute-path': [0],
@ -987,7 +987,7 @@ export default defineConfig([
'vitest/require-to-throw-message': [0],
'vitest/require-top-level-describe': [0],
'vitest/valid-describe-callback': [2],
'vitest/valid-expect': [2],
'vitest/valid-expect': [2, {maxArgs: 2}],
'vitest/valid-title': [2],
},
},

120
go.mod
View File

@ -1,8 +1,6 @@
module code.gitea.io/gitea
go 1.25.0
toolchain go1.25.6
go 1.26.0
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
// But some CAs use negative serial number, just relax the check. related:
@ -11,9 +9,9 @@ godebug x509negativeserial=1
require (
code.gitea.io/actions-proto-go v0.4.1
code.gitea.io/sdk/gitea v0.22.0
code.gitea.io/sdk/gitea v0.23.2
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
connectrpc.com/connect v1.18.1
connectrpc.com/connect v1.19.1
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed
gitea.com/go-chi/cache v0.2.1
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
@ -26,22 +24,22 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
github.com/ProtonMail/go-crypto v1.3.0
github.com/PuerkitoBio/goquery v1.10.3
github.com/PuerkitoBio/goquery v1.11.0
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0
github.com/alecthomas/chroma/v2 v2.23.1
github.com/aws/aws-sdk-go-v2/credentials v1.18.10
github.com/aws/aws-sdk-go-v2/service/codecommit v1.32.2
github.com/aws/aws-sdk-go-v2/credentials v1.19.7
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.8
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
github.com/blevesearch/bleve/v2 v2.5.3
github.com/blevesearch/bleve/v2 v2.5.7
github.com/bohde/codel v0.2.0
github.com/buildkite/terminal-to-html/v3 v3.16.8
github.com/caddyserver/certmagic v0.24.0
github.com/caddyserver/certmagic v0.25.1
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21
github.com/chi-middleware/proxy v1.1.1
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
github.com/dustin/go-humanize v1.0.1
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3
github.com/editorconfig/editorconfig-core-go/v2 v2.6.4
github.com/emersion/go-imap v1.2.1
github.com/emirpasic/gods v1.18.1
github.com/ethantkoenig/rupture v1.0.1
@ -50,43 +48,43 @@ require (
github.com/gliderlabs/ssh v0.3.8
github.com/go-ap/activitypub v0.0.0-20250810115208-cb73b20a1742
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
github.com/go-chi/chi/v5 v5.2.4
github.com/go-chi/chi/v5 v5.2.5
github.com/go-chi/cors v1.2.2
github.com/go-co-op/gocron v1.37.0
github.com/go-enry/go-enry/v2 v2.9.4
github.com/go-git/go-billy/v5 v5.6.2
github.com/go-git/go-git/v5 v5.16.3
github.com/go-ldap/ldap/v3 v3.4.11
github.com/go-redsync/redsync/v4 v4.13.0
github.com/go-git/go-billy/v5 v5.7.0
github.com/go-git/go-git/v5 v5.16.4
github.com/go-ldap/ldap/v3 v3.4.12
github.com/go-redsync/redsync/v4 v4.15.0
github.com/go-sql-driver/mysql v1.9.3
github.com/go-webauthn/webauthn v0.13.4
github.com/goccy/go-json v0.10.5
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
github.com/golang-jwt/jwt/v5 v5.3.0
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/go-github/v74 v74.0.0
github.com/google/licenseclassifier/v2 v2.0.0
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6
github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef
github.com/google/uuid v1.6.0
github.com/gorilla/feeds v1.2.0
github.com/gorilla/sessions v1.4.0
github.com/hashicorp/go-version v1.7.0
github.com/hashicorp/go-version v1.8.0
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/huandu/xstrings v1.5.0
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056
github.com/jhillyerd/enmime v1.3.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.18.0
github.com/klauspost/compress v1.18.3
github.com/klauspost/cpuid/v2 v2.3.0
github.com/lib/pq v1.10.9
github.com/lib/pq v1.11.1
github.com/markbates/goth v1.82.0
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-sqlite3 v1.14.32
github.com/meilisearch/meilisearch-go v0.33.2
github.com/mholt/archives v0.0.0-20251009205813-e30ac6010726
github.com/mattn/go-sqlite3 v1.14.33
github.com/meilisearch/meilisearch-go v0.36.0
github.com/mholt/archives v0.1.5
github.com/microcosm-cc/bluemonday v1.0.27
github.com/microsoft/go-mssqldb v1.9.3
github.com/minio/minio-go/v7 v7.0.95
github.com/microsoft/go-mssqldb v1.9.6
github.com/minio/minio-go/v7 v7.0.98
github.com/msteinert/pam v1.2.0
github.com/nektos/act v0.2.63
github.com/niklasfasching/go-org v1.9.1
@ -94,9 +92,9 @@ require (
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1
github.com/pquerna/otp v1.5.0
github.com/prometheus/client_golang v1.23.0
github.com/prometheus/client_golang v1.23.2
github.com/quasoft/websspi v1.1.2
github.com/redis/go-redis/v9 v9.12.1
github.com/redis/go-redis/v9 v9.17.3
github.com/robfig/cron/v3 v3.0.1
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
github.com/sassoftware/go-rpmutils v0.4.0
@ -114,25 +112,25 @@ require (
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0
gitlab.com/gitlab-org/api/client-go v0.142.4
golang.org/x/crypto v0.45.0
golang.org/x/image v0.30.0
golang.org/x/net v0.47.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.18.0
golang.org/x/sys v0.38.0
golang.org/x/text v0.31.0
google.golang.org/grpc v1.75.0
google.golang.org/protobuf v1.36.8
gopkg.in/ini.v1 v1.67.0
golang.org/x/crypto v0.47.0
golang.org/x/image v0.35.0
golang.org/x/net v0.49.0
golang.org/x/oauth2 v0.34.0
golang.org/x/sync v0.19.0
golang.org/x/sys v0.40.0
golang.org/x/text v0.33.0
google.golang.org/grpc v1.78.0
google.golang.org/protobuf v1.36.11
gopkg.in/ini.v1 v1.67.1
gopkg.in/yaml.v3 v3.0.1
mvdan.cc/xurls/v2 v2.6.0
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
xorm.io/builder v0.3.13
xorm.io/xorm v1.3.10
xorm.io/xorm v1.3.11
)
require (
cloud.google.com/go/compute/metadata v0.8.0 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
code.gitea.io/gitea-vet v0.2.3 // indirect
dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
@ -145,20 +143,20 @@ require (
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aws/aws-sdk-go-v2 v1.38.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 // indirect
github.com/aws/smithy-go v1.23.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect
github.com/aws/smithy-go v1.24.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.24.0 // indirect
github.com/blevesearch/bleve_index_api v1.2.9 // indirect
github.com/blevesearch/bleve_index_api v1.2.11 // indirect
github.com/blevesearch/geo v0.2.4 // indirect
github.com/blevesearch/go-faiss v1.0.25 // indirect
github.com/blevesearch/go-faiss v1.0.26 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
github.com/blevesearch/gtreap v0.1.1 // indirect
github.com/blevesearch/mmap-go v1.0.4 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.3.11 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.3.13 // indirect
github.com/blevesearch/segment v0.9.1 // indirect
github.com/blevesearch/snowballstem v0.9.0 // indirect
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
@ -168,14 +166,14 @@ require (
github.com/blevesearch/zapx/v13 v13.4.2 // indirect
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
github.com/blevesearch/zapx/v16 v16.2.4 // indirect
github.com/blevesearch/zapx/v16 v16.2.8 // indirect
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/caddyserver/zerossl v0.1.4 // indirect
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
@ -199,11 +197,9 @@ require (
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-webauthn/x v0.1.24 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/flatbuffers v25.2.10+incompatible // indirect
@ -220,6 +216,7 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect
github.com/klauspost/crc32 v1.3.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/libdns/libdns v1.1.1 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
@ -227,8 +224,8 @@ require (
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect
github.com/mholt/acmez/v3 v3.1.2 // indirect
github.com/miekg/dns v1.1.68 // indirect
github.com/mholt/acmez/v3 v3.1.4 // indirect
github.com/miekg/dns v1.1.69 // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/crc64nvme v1.1.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
@ -251,18 +248,19 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/rhysd/actionlint v1.7.7 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/tinylib/msgp v1.4.0 // indirect
github.com/tinylib/msgp v1.6.1 // indirect
github.com/unknwon/com v1.0.1 // indirect
github.com/valyala/fastjson v1.6.4 // indirect
github.com/x448/float16 v0.8.4 // indirect
@ -275,14 +273,16 @@ require (
go.etcd.io/bbolt v1.4.3 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/mod v0.31.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.38.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect
golang.org/x/tools v0.40.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
@ -294,8 +294,6 @@ ignore (
replace github.com/jaytaylor/html2text => github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
replace github.com/nektos/act => gitea.com/gitea/act v0.261.7-0.20251003180512-ac6e4b751763
replace git.sr.ht/~mariusor/go-xsd-duration => gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078

270
go.sum
View File

@ -9,8 +9,8 @@ cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6T
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA=
cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
@ -20,12 +20,12 @@ code.gitea.io/actions-proto-go v0.4.1 h1:l0EYhjsgpUe/1VABo2eK7zcoNX2W44WOnb0MSLr
code.gitea.io/actions-proto-go v0.4.1/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas=
code.gitea.io/gitea-vet v0.2.3 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI=
code.gitea.io/gitea-vet v0.2.3/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
code.gitea.io/sdk/gitea v0.22.0 h1:HCKq7bX/HQ85Nw7c/HAhWgRye+vBp5nQOE8Md1+9Ef0=
code.gitea.io/sdk/gitea v0.22.0/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM=
code.gitea.io/sdk/gitea v0.23.2 h1:iJB1FDmLegwfwjX8gotBDHdPSbk/ZR8V9VmEJaVsJYg=
code.gitea.io/sdk/gitea v0.23.2/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM=
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY=
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM=
connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw=
connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14=
connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
@ -53,8 +53,6 @@ github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs=
github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM=
github.com/42wim/sshsig v0.0.0-20250502153856-5100632e8920 h1:mWAVGlovzUfREJBhm0GwJnDNu21yRrL9QH9NIzAU3rg=
github.com/42wim/sshsig v0.0.0-20250502153856-5100632e8920/go.mod h1:zWxcT7BIWOe05xVJL0VMvO/PJ6RpoCux10heb77H6Q8=
github.com/6543/go-version v1.3.1 h1:HvOp+Telns7HWJ2Xo/05YXQSB2bE0WmVgbHqwMPZT4U=
github.com/6543/go-version v1.3.1/go.mod h1:oqFAHCwtLVUTLdhQmVZWYvaHXTdsbB4SY85at64SQEo=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 h1:ci6Yd6nysBRLEodoziB6ah1+YOzZbZk+NYneoA6q+6E=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
@ -85,8 +83,8 @@ github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347 h1:3JhDl+JysaO8nh
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347/go.mod h1:2ErI0aycD43Ufr6CFK5lT/NrHGmoZuVbn1nlPThw69o=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
github.com/RoaringBitmap/roaring v0.7.1/go.mod h1:jdT9ykXwHFNdJbEtxePexlFYH9LXucApeS0/+/g+p1I=
github.com/RoaringBitmap/roaring/v2 v2.10.0 h1:HbJ8Cs71lfCJyvmSptxeMX2PtvOC8yonlU0GQcy2Ak0=
@ -103,8 +101,8 @@ github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9c
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
@ -114,18 +112,18 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aws/aws-sdk-go-v2 v1.38.3 h1:B6cV4oxnMs45fql4yRH+/Po/YU+597zgWqvDpYMturk=
github.com/aws/aws-sdk-go-v2 v1.38.3/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
github.com/aws/aws-sdk-go-v2/credentials v1.18.10 h1:xdJnXCouCx8Y0NncgoptztUocIYLKeQxrCgN6x9sdhg=
github.com/aws/aws-sdk-go-v2/credentials v1.18.10/go.mod h1:7tQk08ntj914F/5i9jC4+2HQTAuJirq7m1vZVIhEkWs=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 h1:uF68eJA6+S9iVr9WgX1NaRGyQ/6MdIyc4JNUo6TN1FA=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6/go.mod h1:qlPeVZCGPiobx8wb1ft0GHT5l+dc6ldnwInDFaMvC7Y=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 h1:pa1DEC6JoI0zduhZePp3zmhWvk/xxm4NB8Hy/Tlsgos=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6/go.mod h1:gxEjPebnhWGJoaDdtDkA0JX46VRg1wcTHYe63OfX5pE=
github.com/aws/aws-sdk-go-v2/service/codecommit v1.32.2 h1:qIySgaSYDLcInLpY0e7HPCi+AVeD/LTsl9EL1b692oA=
github.com/aws/aws-sdk-go-v2/service/codecommit v1.32.2/go.mod h1:SobWM1535Mn1WuThoIVLiLa/C1rRbxbbq5PZW2QFCIM=
github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE=
github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU=
github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
github.com/aws/aws-sdk-go-v2/credentials v1.19.7 h1:tHK47VqqtJxOymRrNtUXN5SP/zUTvZKeLx4tH6PGQc8=
github.com/aws/aws-sdk-go-v2/credentials v1.19.7/go.mod h1:qOZk8sPDrxhf+4Wf4oT2urYJrYt3RejHSzgAquYeppw=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM=
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.8 h1:KxKGfYvkVOe/U/Z4yAd0ZySRJHavuL31VOC+fn7WEAs=
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.8/go.mod h1:cznnFD3BzYY+NB+4WoQ7SxdTACOsMqGCbQ5QaByPz4w=
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@ -138,15 +136,15 @@ github.com/bits-and-blooms/bitset v1.24.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/blevesearch/bleve/v2 v2.0.5/go.mod h1:ZjWibgnbRX33c+vBRgla9QhPb4QOjD6fdVJ+R1Bk8LM=
github.com/blevesearch/bleve/v2 v2.5.3 h1:9l1xtKaETv64SZc1jc4Sy0N804laSa/LeMbYddq1YEM=
github.com/blevesearch/bleve/v2 v2.5.3/go.mod h1:Z/e8aWjiq8HeX+nW8qROSxiE0830yQA071dwR3yoMzw=
github.com/blevesearch/bleve/v2 v2.5.7 h1:2d9YrL5zrX5EBBW++GOaEKjE+NPWeZGaX77IM26m1Z8=
github.com/blevesearch/bleve/v2 v2.5.7/go.mod h1:yj0NlS7ocGC4VOSAedqDDMktdh2935v2CSWOCDMHdSA=
github.com/blevesearch/bleve_index_api v1.0.0/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4=
github.com/blevesearch/bleve_index_api v1.2.9 h1:WqD3kvYwnlYLv8sTdH+AF7n/L4v969Cek68+wZnYj4Q=
github.com/blevesearch/bleve_index_api v1.2.9/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
github.com/blevesearch/bleve_index_api v1.2.11 h1:bXQ54kVuwP8hdrXUSOnvTQfgK0KI1+f9A0ITJT8tX1s=
github.com/blevesearch/bleve_index_api v1.2.11/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
github.com/blevesearch/geo v0.2.4 h1:ECIGQhw+QALCZaDcogRTNSJYQXRtC8/m8IKiA706cqk=
github.com/blevesearch/geo v0.2.4/go.mod h1:K56Q33AzXt2YExVHGObtmRSFYZKYGv0JEN5mdacJJR8=
github.com/blevesearch/go-faiss v1.0.25 h1:lel1rkOUGbT1CJ0YgzKwC7k+XH0XVBHnCVWahdCXk4U=
github.com/blevesearch/go-faiss v1.0.25/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
github.com/blevesearch/go-faiss v1.0.26 h1:4dRLolFgjPyjkaXwff4NfbZFdE/dfywbzDqporeQvXI=
github.com/blevesearch/go-faiss v1.0.26/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
@ -155,8 +153,8 @@ github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
github.com/blevesearch/scorch_segment_api/v2 v2.0.1/go.mod h1:lq7yK2jQy1yQjtjTfU931aVqz7pYxEudHaDwOt1tXfU=
github.com/blevesearch/scorch_segment_api/v2 v2.3.11 h1:bYuEgsyGqgU/gy0/Vk6g1eCUqGBs2r+3bRCv+Cnq2kc=
github.com/blevesearch/scorch_segment_api/v2 v2.3.11/go.mod h1:aAWoeQ3DdoZ3Z5138jXVSd1T/klGwvg11z0pSxrJSEk=
github.com/blevesearch/scorch_segment_api/v2 v2.3.13 h1:ZPjv/4VwWvHJZKeMSgScCapOy8+DdmsmRyLmSB88UoY=
github.com/blevesearch/scorch_segment_api/v2 v2.3.13/go.mod h1:ENk2LClTehOuMS8XzN3UxBEErYmtwkE7MAArFTXs9Vc=
github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
@ -184,8 +182,8 @@ github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXm
github.com/blevesearch/zapx/v15 v15.2.0/go.mod h1:MmQceLpWfME4n1WrBFIwplhWmaQbQqLQARpaKUEOs/A=
github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k=
github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
github.com/blevesearch/zapx/v16 v16.2.4 h1:tGgfvleXTAkwsD5mEzgM3zCS/7pgocTCnO1oyAUjlww=
github.com/blevesearch/zapx/v16 v16.2.4/go.mod h1:Rti/REtuuMmzwsI8/C/qIzRaEoSK/wiFYw5e5ctUKKs=
github.com/blevesearch/zapx/v16 v16.2.8 h1:SlnzF0YGtSlrsOE3oE7EgEX6BIepGpeqxs1IjMbHLQI=
github.com/blevesearch/zapx/v16 v16.2.8/go.mod h1:murSoCJPCk25MqURrcJaBQ1RekuqSCSfMjXH4rHyA14=
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q=
@ -208,10 +206,10 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/buildkite/terminal-to-html/v3 v3.16.8 h1:QN/daUob6cmK8GcdKnwn9+YTlPr1vNj+oeAIiJK6fPc=
github.com/buildkite/terminal-to-html/v3 v3.16.8/go.mod h1:+k1KVKROZocrTLsEQ9PEf9A+8+X8uaVV5iO1ZIOwKYM=
github.com/caddyserver/certmagic v0.24.0 h1:EfXTWpxHAUKgDfOj6MHImJN8Jm4AMFfMT6ITuKhrDF0=
github.com/caddyserver/certmagic v0.24.0/go.mod h1:xPT7dC1DuHHnS2yuEQCEyks+b89sUkMENh8dJF+InLE=
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/caddyserver/certmagic v0.25.1 h1:4sIKKbOt5pg6+sL7tEwymE1x2bj6CHr80da1CRRIPbY=
github.com/caddyserver/certmagic v0.25.1/go.mod h1:VhyvndxtVton/Fo/wKhRoC46Rbw1fmjvQ3GjHYSQTEY=
github.com/caddyserver/zerossl v0.1.4 h1:CVJOE3MZeFisCERZjkxIcsqIH4fnFdlYWnPYeFtBHRw=
github.com/caddyserver/zerossl v0.1.4/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
@ -271,8 +269,8 @@ github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdf
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/dvyukov/go-fuzz v0.0.0-20210429054444-fca39067bc72/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3 h1:XVUp6qW3BIkmM3/1EkrHpa6bL56APOynfXcZEmIgOhs=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3/go.mod h1:ThHVc+hqbUsmE1wmK/MASpQEhCleWu1JDJDNhUOMy0c=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.4 h1:CHwUbBVVyKWRX9kt5A/OtwhYUJB32DrFp9xzmjR6cac=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.4/go.mod h1:JWRVKHdVW+dkv6F8p+xGCa6a+TyMrqsFbFkSs/aQkrQ=
github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
@ -316,8 +314,8 @@ github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73/go.mod h1:jyveZeGw5La
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.2.4 h1:WtFKPHwlywe8Srng8j2BhOD9312j9cGUxG1SP4V2cR4=
github.com/go-chi/chi/v5 v5.2.4/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
@ -330,26 +328,26 @@ github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e h1:oRq/fiirun5Hql
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM=
github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8=
github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y=
github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4=
github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI=
github.com/go-redis/redis/v7 v7.4.1/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-redsync/redsync/v4 v4.13.0 h1:49X6GJfnbLGaIpBBREM/zA4uIMDXKAh1NDkvQ1EkZKA=
github.com/go-redsync/redsync/v4 v4.13.0/go.mod h1:HMW4Q224GZQz6x1Xc7040Yfgacukdzu7ifTDAKiyErQ=
github.com/go-redsync/redsync/v4 v4.15.0 h1:KH/XymuxSV7vyKs6z1Cxxj+N+N18JlPxgXeP6x4JY54=
github.com/go-redsync/redsync/v4 v4.15.0/go.mod h1:qNp+lLs3vkfZbtA/aM/OjlZHfEr5YTAYhRktFPKHC7s=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
@ -368,10 +366,8 @@ github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7w
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0=
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
@ -402,8 +398,8 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8l
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
github.com/gomodule/redigo v1.9.3 h1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8=
github.com/gomodule/redigo v1.9.3/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
@ -436,8 +432,8 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef h1:xpF9fUHpoIrrjX24DURVKiwHcFpw19ndIs+FwTSMbno=
github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@ -476,6 +472,8 @@ github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVU
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
@ -520,12 +518,14 @@ github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PW
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/kljensen/snowball v0.6.0/go.mod h1:27N7E8fVU5H68RlUmnWwZCfxgt4POBJfENGMvNRhldw=
@ -541,8 +541,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.11.1 h1:wuChtj2hfsGmmx3nf1m7xC2XpK6OtelS2shMY+bGMtI=
github.com/lib/pq v1.11.1/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@ -561,28 +561,28 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/meilisearch/meilisearch-go v0.33.2 h1:YgsQSLYhAkRN2ias6I1KNRTjdYCN5w2uHbLUQ+xgrws=
github.com/meilisearch/meilisearch-go v0.33.2/go.mod h1:6eOPcQ+OAuwXvnONlfSgfgvr7TIAWM/6OdhcVHg8cF0=
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/mholt/archives v0.0.0-20251009205813-e30ac6010726 h1:narluFTg20M5KBwKxedpFiSMkdjQRRNUlpY4uAsKMwk=
github.com/mholt/archives v0.0.0-20251009205813-e30ac6010726/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0=
github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/meilisearch/meilisearch-go v0.36.0 h1:N1etykTektXt5KPcSbhBO0d5Xx5NaKj4pJWEM7WA5dI=
github.com/meilisearch/meilisearch-go v0.36.0/go.mod h1:HBfHzKMxcSbTOvqdfuRA/yf6Vk9IivcwKocWRuW7W78=
github.com/mholt/acmez/v3 v3.1.4 h1:DyzZe/RnAzT3rpZj/2Ii5xZpiEvvYk3cQEN/RmqxwFQ=
github.com/mholt/acmez/v3 v3.1.4/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/microsoft/go-mssqldb v1.9.3 h1:hy4p+LDC8LIGvI3JATnLVmBOLMJbmn5X400mr5j0lPs=
github.com/microsoft/go-mssqldb v1.9.3/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLYN3iFL90lQ+PA=
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
github.com/microsoft/go-mssqldb v1.9.6 h1:1MNQg5UiSsokiPz3++K2KPx4moKrwIqly1wv+RyCKTw=
github.com/microsoft/go-mssqldb v1.9.6/go.mod h1:yYMPDufyoF2vVuVCUGtZARr06DKFIhMrluTcgWlXpr4=
github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc=
github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g=
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.95 h1:ywOUPg+PebTMTzn9VDsoFJy32ZuARN9zhB+K3IYEvYU=
github.com/minio/minio-go/v7 v7.0.95/go.mod h1:wOOX3uxS334vImCNRVyIDdXX9OsXDm89ToynKgqUKlo=
github.com/minio/minio-go/v7 v7.0.98 h1:MeAVKjLVz+XJ28zFcuYyImNSAh8Mq725uNW4beRisi0=
github.com/minio/minio-go/v7 v7.0.98/go.mod h1:cY0Y+W7yozf0mdIclrttzo1Iiu7mEf9y7nk2uXqMOvM=
github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A=
github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@ -603,6 +603,8 @@ github.com/msteinert/pam v1.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE=
github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/niklasfasching/go-org v1.9.1 h1:/3s4uTPOF06pImGa2Yvlp24yKXZoTYM+nsIlMzfpg/0=
github.com/niklasfasching/go-org v1.9.1/go.mod h1:ZAGFFkWvUQcpazmi/8nHqwvARpr1xpb+Es67oUGX/48=
github.com/nwaples/rardecode/v2 v2.2.0 h1:4ufPGHiNe1rYJxYfehALLjup4Ls3ck42CWwjKiOqu0A=
@ -653,24 +655,26 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/quasoft/websspi v1.1.2 h1:/mA4w0LxWlE3novvsoEL6BBA1WnjJATbjkh1kFrTidw=
github.com/quasoft/websspi v1.1.2/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk=
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/redis/go-redis/v9 v9.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg=
github.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo=
github.com/redis/rueidis v1.0.19/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4=
github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
github.com/redis/rueidis v1.0.69 h1:WlUefRhuDekji5LsD387ys3UCJtSFeBVf0e5yI0B8b4=
github.com/redis/rueidis v1.0.69/go.mod h1:Lkhr2QTgcoYBhxARU7kJRO8SyVlgUuEkcJO1Y8MCluA=
github.com/redis/rueidis/rueidiscompat v1.0.69 h1:IWVYY9lXdjNO3do2VpJT7aDFi8zbCUuQxZB6E2Grahs=
github.com/redis/rueidis/rueidiscompat v1.0.69/go.mod h1:iC4Y8DoN0Uth0Uezg9e2trvNRC7QAgGeuP2OPLb5ccI=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rhysd/actionlint v1.7.7 h1:0KgkoNTrYY7vmOCs9BW2AHxLvvpoY9nEUzgBHiPUr0k=
github.com/rhysd/actionlint v1.7.7/go.mod h1:AE6I6vJEkNaIfWqC2GNE5spIJNhxf8NCtLEKU4NnUXg=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@ -698,6 +702,8 @@ github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLS
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@ -746,8 +752,8 @@ github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKN
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tinylib/msgp v1.4.0 h1:SYOeDRiydzOw9kSiwdYp9UcBgPFtLU2WDHaJXyHruf8=
github.com/tinylib/msgp v1.4.0/go.mod h1:cvjFkb4RiC8qSBOPMGPSzSAx47nAsfhLVTCZZNuHv5o=
github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY=
github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
github.com/tstranex/u2f v1.0.0 h1:HhJkSzDDlVSVIVt7pDJwCHQj67k7A5EeBgPmeD+pVsQ=
github.com/tstranex/u2f v1.0.0/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGBSayo=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
@ -815,10 +821,14 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -835,8 +845,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -849,8 +859,8 @@ golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4=
golang.org/x/image v0.30.0/go.mod h1:SAEUTxCCMWSrJcCy/4HwavEsfZZJlYxeHLc6tTiAe/c=
golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I=
golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -873,8 +883,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -903,15 +913,15 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -927,8 +937,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -970,8 +980,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -982,8 +992,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -997,8 +1007,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
@ -1034,8 +1044,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -1066,8 +1076,8 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -1075,16 +1085,16 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -1092,8 +1102,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k=
gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
@ -1120,20 +1130,20 @@ modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE=
modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
pgregory.net/rapid v0.4.2 h1:lsi9jhvZTYvzVpeG93WWgimPRmiJQfGFRNTEZh1dtY0=
@ -1145,5 +1155,5 @@ strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 h1:mUcz5b3
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:FJGmPh3vz9jSos1L/F91iAgnC/aejc0wIIrF2ZwJxdY=
xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo=
xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/xorm v1.3.10 h1:yR83hTT4mKIPyA/lvWFTzS35xjLwkiYnwdw0Qupeh0o=
xorm.io/xorm v1.3.10/go.mod h1:Lo7hmsFF0F0GbDE7ubX5ZKa+eCf0eCuiJAUG3oI5cxQ=
xorm.io/xorm v1.3.11 h1:i4tlVUASogb0ZZFJHA7dZqoRU2pUpUsutnNdaOlFyMI=
xorm.io/xorm v1.3.11/go.mod h1:cs0ePc8O4a0jD78cNvD+0VFwhqotTvLQZv372QsDw7Q=

View File

@ -11,7 +11,6 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/packet"
@ -398,7 +397,7 @@ epiDVQ==
func TestTryGetKeyIDFromSignature(t *testing.T) {
assert.Empty(t, TryGetKeyIDFromSignature(&packet.Signature{}))
assert.Equal(t, "038D1A3EADDBEA9C", TryGetKeyIDFromSignature(&packet.Signature{
IssuerKeyId: util.ToPointer(uint64(0x38D1A3EADDBEA9C)),
IssuerKeyId: new(uint64(0x38D1A3EADDBEA9C)),
}))
assert.Equal(t, "038D1A3EADDBEA9C", TryGetKeyIDFromSignature(&packet.Signature{
IssuerFingerprint: []uint8{0xb, 0x23, 0x24, 0xc7, 0xe6, 0xfe, 0x4f, 0x3a, 0x6, 0x26, 0xc1, 0x21, 0x3, 0x8d, 0x1a, 0x3e, 0xad, 0xdb, 0xea, 0x9c},
@ -419,7 +418,7 @@ func TestParseGPGKey(t *testing.T) {
// then revoke the key
for _, id := range e.Identities {
id.Revocations = append(id.Revocations, &packet.Signature{RevocationReason: util.ToPointer(packet.KeyCompromised)})
id.Revocations = append(id.Revocations, &packet.Signature{RevocationReason: new(packet.KeyCompromised)})
}
k, err = parseGPGKey(t.Context(), 1, e, true)
require.NoError(t, err)

View File

@ -490,12 +490,25 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
opts.CommitAfterUnix = time.Now().Add(-time.Hour * 2).Unix()
}
baseBranch, err := GetBranch(ctx, opts.BaseRepo.ID, opts.BaseRepo.DefaultBranch)
var ignoredCommitIDs []string
baseDefaultBranch, err := GetBranch(ctx, opts.BaseRepo.ID, opts.BaseRepo.DefaultBranch)
if err != nil {
return nil, err
log.Warn("GetBranch:DefaultBranch: %v", err)
} else {
ignoredCommitIDs = append(ignoredCommitIDs, baseDefaultBranch.CommitID)
}
// find all related branches, these branches may already created PRs, we will check later
baseDefaultTargetBranchName := opts.BaseRepo.MustGetUnit(ctx, unit.TypePullRequests).PullRequestsConfig().DefaultTargetBranch
if baseDefaultTargetBranchName != "" && baseDefaultTargetBranchName != opts.BaseRepo.DefaultBranch {
baseDefaultTargetBranch, err := GetBranch(ctx, opts.BaseRepo.ID, baseDefaultTargetBranchName)
if err != nil {
log.Warn("GetBranch:DefaultTargetBranch: %v", err)
} else {
ignoredCommitIDs = append(ignoredCommitIDs, baseDefaultTargetBranch.CommitID)
}
}
// find all related branches, these branches may already have PRs, we will check later
var branches []*Branch
if err := db.GetEngine(ctx).
Where(builder.And(
@ -506,7 +519,7 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
builder.Gte{"commit_time": opts.CommitAfterUnix},
builder.In("repo_id", repoIDs),
// newly created branch have no changes, so skip them
builder.Neq{"commit_id": baseBranch.CommitID},
builder.NotIn("commit_id", ignoredCommitIDs),
)).
OrderBy(db.SearchOrderByRecentUpdated.String()).
Find(&branches); err != nil {
@ -514,10 +527,8 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
}
newBranches := make([]*RecentlyPushedNewBranch, 0, len(branches))
if opts.MaxCount == 0 {
// by default we display 2 recently pushed new branch
opts.MaxCount = 2
}
opts.MaxCount = util.IfZero(opts.MaxCount, 2) // by default, we display 2 recently pushed new branch
baseTargetBranchName := opts.BaseRepo.GetPullRequestTargetBranch(ctx)
for _, branch := range branches {
// whether the branch is protected
protected, err := IsBranchProtected(ctx, branch.RepoID, branch.Name)
@ -555,7 +566,7 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
BranchDisplayName: branchDisplayName,
BranchName: branch.Name,
BranchLink: fmt.Sprintf("%s/src/branch/%s", branch.Repo.Link(), util.PathEscapeSegments(branch.Name)),
BranchCompareURL: branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, branch.Name),
BranchCompareURL: branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, baseTargetBranchName, branch.Name),
CommitTime: branch.CommitTime,
})
}

View File

@ -682,7 +682,7 @@ func (issue *Issue) GetParticipantIDsByIssue(ctx context.Context) ([]int64, erro
}
// BlockedByDependencies finds all Dependencies an issue is blocked by
func (issue *Issue) BlockedByDependencies(ctx context.Context, opts db.ListOptions) (issueDeps []*DependencyInfo, err error) {
func (issue *Issue) BlockedByDependencies(ctx context.Context, opts db.ListOptions) (issueDeps []*DependencyInfo, total int64, err error) {
sess := db.GetEngine(ctx).
Table("issue").
Join("INNER", "repository", "repository.id = issue.repo_id").
@ -693,13 +693,13 @@ func (issue *Issue) BlockedByDependencies(ctx context.Context, opts db.ListOptio
if opts.Page > 0 {
sess = db.SetSessionPagination(sess, &opts)
}
err = sess.Find(&issueDeps)
total, err = sess.FindAndCount(&issueDeps)
for _, depInfo := range issueDeps {
depInfo.Issue.Repo = &depInfo.Repository
}
return issueDeps, err
return issueDeps, total, err
}
// BlockingDependencies returns all blocking dependencies, aka all other issues a given issue blocks

View File

@ -658,12 +658,18 @@ func (pr *PullRequest) IsWorkInProgress(ctx context.Context) bool {
// HasWorkInProgressPrefix determines if the given PR title has a Work In Progress prefix
func HasWorkInProgressPrefix(title string) bool {
_, ok := CutWorkInProgressPrefix(title)
return ok
}
func CutWorkInProgressPrefix(title string) (origTitle string, ok bool) {
for _, prefix := range setting.Repository.PullRequest.WorkInProgressPrefixes {
if strings.HasPrefix(strings.ToUpper(title), strings.ToUpper(prefix)) {
return true
prefixLen := len(prefix)
if prefixLen <= len(title) && util.AsciiEqualFold(title[:prefixLen], prefix) {
return title[len(prefix):], true
}
}
return false
return title, false
}
// IsFilesConflicted determines if the Pull Request has changes conflicting with the target branch.

View File

@ -0,0 +1,16 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"context"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/util"
)
func (repo *Repository) GetPullRequestTargetBranch(ctx context.Context) string {
unitPRConfig := repo.MustGetUnit(ctx, unit.TypePullRequests).PullRequestsConfig()
return util.IfZero(unitPRConfig.DefaultTargetBranch, repo.DefaultBranch)
}

View File

@ -0,0 +1,32 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"testing"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest"
"github.com/stretchr/testify/assert"
)
func TestDefaultTargetBranchSelection(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
ctx := t.Context()
repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1})
assert.Equal(t, repo.DefaultBranch, repo.GetPullRequestTargetBranch(ctx))
repo.Units = nil
prUnit, err := repo.GetUnit(ctx, unit.TypePullRequests)
assert.NoError(t, err)
prConfig := prUnit.PullRequestsConfig()
prConfig.DefaultTargetBranch = "branch2"
prUnit.Config = prConfig
assert.NoError(t, UpdateRepoUnit(ctx, prUnit))
repo.Units = nil
assert.Equal(t, "branch2", repo.GetPullRequestTargetBranch(ctx))
}

View File

@ -613,16 +613,13 @@ func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) strin
return fmt.Sprintf("%s/%s/compare/%s...%s", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name), util.PathEscapeSegments(oldCommitID), util.PathEscapeSegments(newCommitID))
}
func (repo *Repository) ComposeBranchCompareURL(baseRepo *Repository, branchName string) string {
if baseRepo == nil {
baseRepo = repo
}
func (repo *Repository) ComposeBranchCompareURL(baseRepo *Repository, baseBranch, branchName string) string {
var cmpBranchEscaped string
if repo.ID != baseRepo.ID {
cmpBranchEscaped = fmt.Sprintf("%s/%s:", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name))
}
cmpBranchEscaped = fmt.Sprintf("%s%s", cmpBranchEscaped, util.PathEscapeSegments(branchName))
return fmt.Sprintf("%s/compare/%s...%s", baseRepo.Link(), util.PathEscapeSegments(baseRepo.DefaultBranch), cmpBranchEscaped)
return fmt.Sprintf("%s/compare/%s...%s", baseRepo.Link(), util.PathEscapeSegments(baseBranch), cmpBranchEscaped)
}
// IsOwnedBy returns true when user owns this repository

View File

@ -131,6 +131,7 @@ type PullRequestsConfig struct {
DefaultDeleteBranchAfterMerge bool
DefaultMergeStyle MergeStyle
DefaultAllowMaintainerEdit bool
DefaultTargetBranch string
}
// FromDB fills up a PullRequestsConfig from serialized format.

View File

@ -9,19 +9,32 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/optional"
"xorm.io/builder"
)
// GetSystemOrDefaultWebhooks returns webhooks by given argument or all if argument is missing.
func GetSystemOrDefaultWebhooks(ctx context.Context, isSystemWebhook optional.Option[bool]) ([]*Webhook, error) {
webhooks := make([]*Webhook, 0, 5)
if !isSystemWebhook.Has() {
return webhooks, db.GetEngine(ctx).Where("repo_id=? AND owner_id=?", 0, 0).
Find(&webhooks)
}
// ListSystemWebhookOptions options for listing system or default webhooks
type ListSystemWebhookOptions struct {
db.ListOptions
IsActive optional.Option[bool]
IsSystem optional.Option[bool]
}
return webhooks, db.GetEngine(ctx).
Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, isSystemWebhook.Value()).
Find(&webhooks)
func (opts ListSystemWebhookOptions) ToConds() builder.Cond {
cond := builder.NewCond()
cond = cond.And(builder.Eq{"webhook.repo_id": 0}, builder.Eq{"webhook.owner_id": 0})
if opts.IsActive.Has() {
cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.Value()})
}
if opts.IsSystem.Has() {
cond = cond.And(builder.Eq{"is_system_webhook": opts.IsSystem.Value()})
}
return cond
}
// GetGlobalWebhooks returns global (default and/or system) webhooks
func GetGlobalWebhooks(ctx context.Context, opts *ListSystemWebhookOptions) ([]*Webhook, int64, error) {
return db.FindAndCount[Webhook](ctx, opts)
}
// GetDefaultWebhooks returns all admin-default webhooks.

View File

@ -12,23 +12,24 @@ import (
"github.com/stretchr/testify/assert"
)
func TestGetSystemOrDefaultWebhooks(t *testing.T) {
func TestListSystemWebhookOptions(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hooks, err := GetSystemOrDefaultWebhooks(t.Context(), optional.None[bool]())
opts := ListSystemWebhookOptions{IsSystem: optional.None[bool]()}
hooks, _, err := GetGlobalWebhooks(t.Context(), &opts)
assert.NoError(t, err)
if assert.Len(t, hooks, 2) {
assert.Equal(t, int64(5), hooks[0].ID)
assert.Equal(t, int64(6), hooks[1].ID)
}
hooks, err = GetSystemOrDefaultWebhooks(t.Context(), optional.Some(true))
opts.IsSystem = optional.Some(true)
hooks, _, err = GetGlobalWebhooks(t.Context(), &opts)
assert.NoError(t, err)
if assert.Len(t, hooks, 1) {
assert.Equal(t, int64(5), hooks[0].ID)
}
hooks, err = GetSystemOrDefaultWebhooks(t.Context(), optional.Some(false))
opts.IsSystem = optional.Some(false)
hooks, _, err = GetGlobalWebhooks(t.Context(), &opts)
assert.NoError(t, err)
if assert.Len(t, hooks, 1) {
assert.Equal(t, int64(6), hooks[0].ID)

View File

@ -12,11 +12,15 @@ import (
"math"
"strconv"
"strings"
"sync/atomic"
"time"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log"
)
var catFileBatchDebugWaitClose atomic.Int64
type catFileBatchCommunicator struct {
cancel context.CancelFunc
reqWriter io.Writer
@ -36,7 +40,14 @@ func newCatFileBatch(ctx context.Context, repoPath string, cmdCatFile *gitcmd.Co
ctx, ctxCancel := context.WithCancelCause(ctx)
// We often want to feed the commits in order into cat-file --batch, followed by their trees and subtrees as necessary.
stdinWriter, stdoutReader, pipeClose := cmdCatFile.MakeStdinStdoutPipe()
stdinWriter, stdoutReader, stdPipeClose := cmdCatFile.MakeStdinStdoutPipe()
pipeClose := func() {
if delay := catFileBatchDebugWaitClose.Load(); delay > 0 {
time.Sleep(time.Duration(delay)) // for testing purpose only
}
stdPipeClose()
}
ret = &catFileBatchCommunicator{
debugGitCmd: cmdCatFile,
cancel: func() { ctxCancel(nil) },

View File

@ -5,9 +5,11 @@ package git
import (
"io"
"os"
"path/filepath"
"sync"
"testing"
"time"
"code.gitea.io/gitea/modules/test"
@ -37,6 +39,45 @@ func testCatFileBatch(t *testing.T) {
require.Error(t, err)
})
simulateQueryTerminated := func(pipeCloseDelay, pipeReadDelay time.Duration) (errRead error) {
catFileBatchDebugWaitClose.Store(int64(pipeCloseDelay))
defer catFileBatchDebugWaitClose.Store(0)
batch, err := NewBatch(t.Context(), filepath.Join(testReposDir, "repo1_bare"))
require.NoError(t, err)
defer batch.Close()
_, _ = batch.QueryInfo("e2129701f1a4d54dc44f03c93bca0a2aec7c5449")
var c *catFileBatchCommunicator
switch b := batch.(type) {
case *catFileBatchLegacy:
c = b.batchCheck
_, _ = c.reqWriter.Write([]byte("in-complete-line-"))
case *catFileBatchCommand:
c = b.batch
_, _ = c.reqWriter.Write([]byte("info"))
default:
t.FailNow()
}
wg := sync.WaitGroup{}
wg.Go(func() {
time.Sleep(pipeReadDelay)
var n int
n, errRead = c.respReader.Read(make([]byte, 100))
assert.Zero(t, n)
})
time.Sleep(10 * time.Millisecond)
c.debugGitCmd.DebugKill()
wg.Wait()
return errRead
}
t.Run("QueryTerminated", func(t *testing.T) {
err := simulateQueryTerminated(0, 20*time.Millisecond)
assert.ErrorIs(t, err, os.ErrClosed) // pipes are closed faster
err = simulateQueryTerminated(40*time.Millisecond, 20*time.Millisecond)
assert.ErrorIs(t, err, io.EOF) // reader is faster
})
batch, err := NewBatch(t.Context(), filepath.Join(testReposDir, "repo1_bare"))
require.NoError(t, err)
defer batch.Close()
@ -60,30 +101,4 @@ func testCatFileBatch(t *testing.T) {
require.NoError(t, err)
require.Equal(t, "file1\n", string(content))
})
t.Run("QueryTerminated", func(t *testing.T) {
var c *catFileBatchCommunicator
switch b := batch.(type) {
case *catFileBatchLegacy:
c = b.batchCheck
_, _ = c.reqWriter.Write([]byte("in-complete-line-"))
case *catFileBatchCommand:
c = b.batch
_, _ = c.reqWriter.Write([]byte("info"))
default:
t.FailNow()
return
}
wg := sync.WaitGroup{}
wg.Go(func() {
buf := make([]byte, 100)
_, _ = c.respReader.Read(buf)
n, errRead := c.respReader.Read(buf)
assert.Zero(t, n)
assert.ErrorIs(t, errRead, io.EOF) // the pipe is closed due to command being killed
})
c.debugGitCmd.DebugKill()
wg.Wait()
})
}

View File

@ -88,12 +88,17 @@ func parseGitVersionLine(s string) (*version.Version, error) {
return nil, fmt.Errorf("invalid git version: %q", s)
}
// version string is like: "git version 2.29.3" or "git version 2.29.3.windows.1"
// version output is like: "git version {versionString}"
// versionString can be:
// * "2.5.3"
// * "2.29.3.windows.1"
// * "2.28.0.618.gf4bc123cb7": https://github.com/go-gitea/gitea/issues/12731
versionString := fields[2]
if pos := strings.Index(versionString, "windows"); pos >= 1 {
versionString = versionString[:pos-1]
versionFields := strings.Split(versionString, ".")
if len(versionFields) > 3 {
versionFields = versionFields[:3]
}
return version.NewVersion(versionString)
return version.NewVersion(strings.Join(versionFields, "."))
}
func checkGitVersionCompatibility(gitVer *version.Version) error {

View File

@ -23,6 +23,10 @@ func TestParseGitVersion(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "2.29.3", v.String())
v, err = parseGitVersionLine("git version 2.28.0.618.gf4bc123cb7")
assert.NoError(t, err)
assert.Equal(t, "2.28.0", v.String())
_, err = parseGitVersionLine("git version")
assert.Error(t, err)

View File

@ -9,6 +9,14 @@ import (
)
type PipeBufferReader interface {
// Read should be used in the same goroutine as command's Wait
// When Reader in one goroutine, command's Wait in another goroutine, then the command exits, the pipe will be closed:
// * If the Reader goroutine reads faster, it will read all remaining data and then get io.EOF
// * But this io.EOF doesn't mean the Reader has gotten complete data, the data might still be corrupted
// * If the Reader goroutine reads slower, it will get os.ErrClosed because the os.Pipe is closed ahead when the command exits
//
// When using 2 goroutines, no clear solution to distinguish these two cases or make Reader knows whether the data is complete
// It should avoid using Reader in a different goroutine than the command if the Read error needs to be handled.
Read(p []byte) (n int, err error)
Bytes() []byte
}

View File

@ -98,6 +98,10 @@ func getChromaLexerByLanguage(fileName, lang string) chroma.Lexer {
lang = "C++"
}
}
if lang == "" && util.AsciiEqualFold(ext, ".sql") {
// there is a bug when using MySQL lexer: "--\nSELECT", the second line will be rendered as comment incorrectly
lang = "SQL"
}
// lexers.Get is slow if the language name can't be matched directly: it does extra "Match" call to iterate all lexers
return lexers.Get(lang)
}

View File

@ -108,6 +108,12 @@ c=2
),
lexerName: "Python",
},
{
name: "test.sql",
code: "--\nSELECT",
want: []template.HTML{"<span class=\"c1\">--\n</span>", `<span class="k">SELECT</span>`},
lexerName: "SQL",
},
}
for _, tt := range tests {

View File

@ -124,7 +124,7 @@ func (b *Indexer) Delete(_ context.Context, ids ...int64) error {
}
for _, id := range ids {
_, err := b.inner.Client.Index(b.inner.VersionedIndexName()).DeleteDocument(strconv.FormatInt(id, 10))
_, err := b.inner.Client.Index(b.inner.VersionedIndexName()).DeleteDocument(strconv.FormatInt(id, 10), nil)
if err != nil {
return err
}

View File

@ -9,6 +9,7 @@ import (
"io"
"regexp"
"strings"
"sync"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation"
@ -25,7 +26,9 @@ var (
ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
)
var versionMatcher = regexp.MustCompile(`\A[0-9]+(?:\.[0-9a-zA-Z]+)*(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?\z`)
var versionMatcher = sync.OnceValue(func() *regexp.Regexp {
return regexp.MustCompile(`\A[0-9]+(?:\.[0-9a-zA-Z]+)*(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?\z`)
})
// Package represents a RubyGems package
type Package struct {
@ -128,7 +131,7 @@ func (r requirement) AsVersionRequirement() []VersionRequirement {
continue
}
version, ok := versionInt.(string)
if !ok || version == "0" {
if !ok || (version == "0" && restriction == ">=") {
continue
}
@ -176,7 +179,7 @@ func parseMetadataFile(r io.Reader) (*Package, error) {
return nil, ErrInvalidName
}
if !versionMatcher.MatchString(spec.Version.Version) {
if !versionMatcher().MatchString(spec.Version.Version) {
return nil, ErrInvalidVersion
}

View File

@ -4,42 +4,30 @@
package rubygems
import (
"archive/tar"
"bytes"
"encoding/base64"
"io"
"testing"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
)
func TestParsePackageMetaData(t *testing.T) {
createArchive := func(filename string, content []byte) io.Reader {
var buf bytes.Buffer
tw := tar.NewWriter(&buf)
hdr := &tar.Header{
Name: filename,
Mode: 0o600,
Size: int64(len(content)),
}
tw.WriteHeader(hdr)
tw.Write(content)
tw.Close()
return &buf
}
t.Run("MissingMetadataFile", func(t *testing.T) {
data := createArchive("dummy.txt", []byte{0})
data := test.WriteTarArchive(map[string]string{"dummy.txt": ""})
rp, err := ParsePackageMetaData(data)
assert.ErrorIs(t, err, ErrMissingMetadataFile)
assert.Nil(t, rp)
})
t.Run("Valid", func(t *testing.T) {
content, _ := base64.StdEncoding.DecodeString("H4sICHC/I2EEAG1ldGFkYXRhAAEeAOH/bmFtZTogZwp2ZXJzaW9uOgogIHZlcnNpb246IDEKWw35Tx4AAAA=")
data := createArchive("metadata.gz", content)
metadataContent := test.CompressGzip(`
name: g
version:
version: 1
`)
data := test.WriteTarArchive(map[string]string{
"metadata.gz": metadataContent.String(),
})
rp, err := ParsePackageMetaData(data)
assert.NoError(t, err)
assert.NotNil(t, rp)
@ -47,17 +35,86 @@ func TestParsePackageMetaData(t *testing.T) {
}
func TestParseMetadataFile(t *testing.T) {
content, _ := base64.StdEncoding.DecodeString(`H4sIAMe7I2ECA9VVTW/UMBC9+1eYXvaUbJpSQBZUHJAqDlwK4kCFIseZzZrGH9iTqisEv52Js9nd
0KqggiqRXWnX45n3ZuZ5nCzL+JPQ15ulq7+AQnEORoj3HpReaSVRO8usNCB4qxEku4YQySbuCPo4
bjHOd07HeZGfMt9JXLlgBB9imOxx7UIULOPnCZMMLsDXXgeiYbW2jQ6C0y9TELBSa6kJ6/IzaySS
R1mUx1nxIitPeFGI9M2L6eGfWAMebANWaUgktzN9M3lsKNmxutBb1AYyCibbNhsDFu+q9GK/Tc4z
d2IcLBl9js5eHaXFsLyvXeNz0LQyL/YoLx8EsiCMBZlx46k6sS2PDD5AgA5kJPNKdhH2elWzOv7n
uv9Q9Aau/6ngP84elvNpXh5oRVlB5/yW7BH0+qu0G4gqaI/JdEHBFBS5l+pKtsARIjIwUnfj8Le0
+TrdJLl2DG5A9SjrjgZ1mG+4QbAD+G4ZZBUap6qVnnzGf6Rwp+vliBRqtnYGPBEKvkb0USyXE8mS
dVoR6hj07u0HZgAl3SRS8G/fmXcRK20jyq6rDMSYQFgidamqkXbbuspLXE/0k7GphtKqe67GuRC/
yjAbmt9LsOMp8xMamFkSQ38fP5EFjdz8LA4do2C69VvqWXAJgrPbKZb58/xZXrKoW6ttW13Bhvzi
4ftn7/yUxd4YGcglvTmmY8aGY3ZwRn4CqcWcidUGAAA=`)
rp, err := parseMetadataFile(bytes.NewReader(content))
content := test.CompressGzip(`--- !ruby/object:Gem::Specification
name: gitea
version: !ruby/object:Gem::Version
version: 1.0.5
platform: ruby
authors:
- Gitea
autorequire:
bindir: bin
cert_chain: []
date: 2021-08-23 00:00:00.000000000 Z
dependencies:
- !ruby/object:Gem::Dependency
name: runtime-dep
requirement: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: 1.2.0
- - "<"
- !ruby/object:Gem::Version
version: '2.0'
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: 1.2.0
- - "<"
- !ruby/object:Gem::Version
version: '2.0'
- !ruby/object:Gem::Dependency
name: dev-dep
requirement: !ruby/object:Gem::Requirement
requirements:
- - "~>"
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - "~>"
- !ruby/object:Gem::Version
version: '5.2'
description: RubyGems package test
email: rubygems@gitea.io
executables: []
extensions: []
extra_rdoc_files: []
files:
- lib/gitea.rb
homepage: https://gitea.io/
licenses:
- MIT
metadata: {}
post_install_message:
rdoc_options: []
require_paths:
- lib
required_ruby_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: 2.3.0
required_rubygems_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
requirements: []
rubyforge_project:
rubygems_version: 2.7.6.2
signing_key:
specification_version: 4
summary: Gitea package
test_files: []
`)
rp, err := parseMetadataFile(content)
assert.NoError(t, err)
assert.NotNil(t, rp)
@ -84,5 +141,5 @@ yjAbmt9LsOMp8xMamFkSQ38fP5EFjdz8LA4do2C69VvqWXAJgrPbKZb58/xZXrKoW6ttW13Bhvzi
assert.Equal(t, "dev-dep", rp.Metadata.DevelopmentDependencies[0].Name)
assert.Len(t, rp.Metadata.DevelopmentDependencies[0].Version, 1)
assert.Equal(t, "~>", rp.Metadata.DevelopmentDependencies[0].Version[0].Restriction)
assert.Equal(t, "5.2", rp.Metadata.DevelopmentDependencies[0].Version[0].Version)
assert.Equal(t, "0", rp.Metadata.DevelopmentDependencies[0].Version[0].Version)
}

View File

@ -17,6 +17,13 @@ import (
"code.gitea.io/gitea/modules/timeutil"
)
// SyncResult describes a reference update detected during sync.
type SyncResult struct {
RefName git.RefName
OldCommitID string
NewCommitID string
}
// SyncRepoBranches synchronizes branch table with repository branches
func SyncRepoBranches(ctx context.Context, repoID, doerID int64) (int64, error) {
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
@ -33,18 +40,19 @@ func SyncRepoBranches(ctx context.Context, repoID, doerID int64) (int64, error)
}
defer gitRepo.Close()
return SyncRepoBranchesWithRepo(ctx, repo, gitRepo, doerID)
count, _, err := SyncRepoBranchesWithRepo(ctx, repo, gitRepo, doerID)
return count, err
}
func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, doerID int64) (int64, error) {
func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, doerID int64) (int64, []*SyncResult, error) {
objFmt, err := gitRepo.GetObjectFormat()
if err != nil {
return 0, fmt.Errorf("GetObjectFormat: %w", err)
return 0, nil, fmt.Errorf("GetObjectFormat: %w", err)
}
if objFmt.Name() != repo.ObjectFormatName {
repo.ObjectFormatName = objFmt.Name()
if err = repo_model.UpdateRepositoryColsWithAutoTime(ctx, repo, "object_format_name"); err != nil {
return 0, fmt.Errorf("UpdateRepositoryColsWithAutoTime: %w", err)
return 0, nil, fmt.Errorf("UpdateRepositoryColsWithAutoTime: %w", err)
}
}
@ -52,7 +60,7 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository,
{
branches, _, err := gitRepo.GetBranchNames(0, 0)
if err != nil {
return 0, err
return 0, nil, err
}
log.Trace("SyncRepoBranches[%s]: branches[%d]: %v", repo.FullName(), len(branches), branches)
for _, branch := range branches {
@ -67,7 +75,7 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository,
RepoID: repo.ID,
})
if err != nil {
return 0, err
return 0, nil, err
}
for _, branch := range branches {
dbBranches[branch.Name] = branch
@ -77,11 +85,12 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository,
var toAdd []*git_model.Branch
var toUpdate []*git_model.Branch
var toRemove []int64
var syncResults []*SyncResult
for branch := range allBranches {
dbb := dbBranches[branch]
commit, err := gitRepo.GetBranchCommit(branch)
if err != nil {
return 0, err
return 0, nil, err
}
if dbb == nil {
toAdd = append(toAdd, &git_model.Branch{
@ -92,7 +101,12 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository,
PusherID: doerID,
CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()),
})
} else if commit.ID.String() != dbb.CommitID {
syncResults = append(syncResults, &SyncResult{
RefName: git.RefNameFromBranch(branch),
OldCommitID: "",
NewCommitID: commit.ID.String(),
})
} else if commit.ID.String() != dbb.CommitID || dbb.IsDeleted {
toUpdate = append(toUpdate, &git_model.Branch{
ID: dbb.ID,
RepoID: repo.ID,
@ -102,19 +116,29 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository,
PusherID: doerID,
CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()),
})
syncResults = append(syncResults, &SyncResult{
RefName: git.RefNameFromBranch(branch),
OldCommitID: dbb.CommitID,
NewCommitID: commit.ID.String(),
})
}
}
for _, dbBranch := range dbBranches {
if !allBranches.Contains(dbBranch.Name) && !dbBranch.IsDeleted {
toRemove = append(toRemove, dbBranch.ID)
syncResults = append(syncResults, &SyncResult{
RefName: git.RefNameFromBranch(dbBranch.Name),
OldCommitID: dbBranch.CommitID,
NewCommitID: "",
})
}
}
log.Trace("SyncRepoBranches[%s]: toAdd: %v, toUpdate: %v, toRemove: %v", repo.FullName(), toAdd, toUpdate, toRemove)
if len(toAdd) == 0 && len(toRemove) == 0 && len(toUpdate) == 0 {
return int64(len(allBranches)), nil
return int64(len(allBranches)), syncResults, nil
}
if err := db.WithTx(ctx, func(ctx context.Context) error {
@ -140,7 +164,7 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository,
return nil
}); err != nil {
return 0, err
return 0, nil, err
}
return int64(len(allBranches)), nil
return int64(len(allBranches)), syncResults, nil
}

View File

@ -53,7 +53,8 @@ func SyncRepoTags(ctx context.Context, repoID int64) error {
}
defer gitRepo.Close()
return SyncReleasesWithTags(ctx, repo, gitRepo)
_, err = SyncReleasesWithTags(ctx, repo, gitRepo)
return err
}
// StoreMissingLfsObjectsInRepository downloads missing LFS objects
@ -178,13 +179,14 @@ func (shortRelease) TableName() string {
// upstream. Hence, after each sync we want the release set to be
// identical to the upstream tag set. This is much more efficient for
// repositories like https://github.com/vim/vim (with over 13000 tags).
func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository) error {
func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository) ([]*SyncResult, error) {
log.Debug("SyncReleasesWithTags: in Repo[%d:%s/%s]", repo.ID, repo.OwnerName, repo.Name)
tags, _, err := gitRepo.GetTagInfos(0, 0)
if err != nil {
return fmt.Errorf("unable to GetTagInfos in pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
return nil, fmt.Errorf("unable to GetTagInfos in pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
}
var added, deleted, updated int
var syncResults []*SyncResult
err = db.WithTx(ctx, func(ctx context.Context) error {
dbReleases, err := db.Find[shortRelease](ctx, repo_model.FindReleasesOptions{
RepoID: repo.ID,
@ -195,7 +197,45 @@ func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitR
return fmt.Errorf("unable to FindReleases in pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
}
dbReleasesByID := make(map[int64]*shortRelease, len(dbReleases))
dbReleasesByTag := make(map[string]*shortRelease, len(dbReleases))
for _, release := range dbReleases {
dbReleasesByID[release.ID] = release
dbReleasesByTag[release.TagName] = release
}
inserts, deletes, updates := calcSync(tags, dbReleases)
syncResults = make([]*SyncResult, 0, len(inserts)+len(deletes)+len(updates))
for _, tag := range inserts {
syncResults = append(syncResults, &SyncResult{
RefName: git.RefNameFromTag(tag.Name),
OldCommitID: "",
NewCommitID: tag.Object.String(),
})
}
for _, deleteID := range deletes {
release := dbReleasesByID[deleteID]
if release == nil {
continue
}
syncResults = append(syncResults, &SyncResult{
RefName: git.RefNameFromTag(release.TagName),
OldCommitID: release.Sha1,
NewCommitID: "",
})
}
for _, tag := range updates {
release := dbReleasesByTag[tag.Name]
oldSha := ""
if release != nil {
oldSha = release.Sha1
}
syncResults = append(syncResults, &SyncResult{
RefName: git.RefNameFromTag(tag.Name),
OldCommitID: oldSha,
NewCommitID: tag.Object.String(),
})
}
//
// make release set identical to upstream tags
//
@ -238,11 +278,11 @@ func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitR
return nil
})
if err != nil {
return fmt.Errorf("unable to rebuild release table for pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
return nil, fmt.Errorf("unable to rebuild release table for pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
}
log.Trace("SyncReleasesWithTags: %d tags added, %d tags deleted, %d tags updated", added, deleted, updated)
return nil
return syncResults, nil
}
func calcSync(destTags []*git.Tag, dbTags []*shortRelease) ([]*git.Tag, []int64, []*git.Tag) {

View File

@ -58,26 +58,27 @@ type Repository struct {
Fork bool `json:"fork"`
Template bool `json:"template"`
// the original repository if this repository is a fork, otherwise null
Parent *Repository `json:"parent,omitempty"`
Mirror bool `json:"mirror"`
Size int `json:"size"`
Language string `json:"language"`
LanguagesURL string `json:"languages_url"`
HTMLURL string `json:"html_url"`
URL string `json:"url"`
Link string `json:"link"`
SSHURL string `json:"ssh_url"`
CloneURL string `json:"clone_url"`
OriginalURL string `json:"original_url"`
Website string `json:"website"`
Stars int `json:"stars_count"`
Forks int `json:"forks_count"`
Watchers int `json:"watchers_count"`
OpenIssues int `json:"open_issues_count"`
OpenPulls int `json:"open_pr_counter"`
Releases int `json:"release_counter"`
DefaultBranch string `json:"default_branch"`
Archived bool `json:"archived"`
Parent *Repository `json:"parent,omitempty"`
Mirror bool `json:"mirror"`
Size int `json:"size"`
Language string `json:"language"`
LanguagesURL string `json:"languages_url"`
HTMLURL string `json:"html_url"`
URL string `json:"url"`
Link string `json:"link"`
SSHURL string `json:"ssh_url"`
CloneURL string `json:"clone_url"`
OriginalURL string `json:"original_url"`
Website string `json:"website"`
Stars int `json:"stars_count"`
Forks int `json:"forks_count"`
Watchers int `json:"watchers_count"`
OpenIssues int `json:"open_issues_count"`
OpenPulls int `json:"open_pr_counter"`
Releases int `json:"release_counter"`
DefaultBranch string `json:"default_branch"`
DefaultTargetBranch string `json:"default_target_branch,omitempty"`
Archived bool `json:"archived"`
// swagger:strfmt date-time
Created time.Time `json:"created_at"`
// swagger:strfmt date-time

View File

@ -0,0 +1,48 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package templates
import (
"html/template"
"strings"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/htmlutil"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/svg"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
)
func commentTimelineEventIsWipToggle(c *issues_model.Comment) (isToggle, isWip bool) {
title1, ok1 := issues_model.CutWorkInProgressPrefix(c.OldTitle)
title2, ok2 := issues_model.CutWorkInProgressPrefix(c.NewTitle)
return ok1 != ok2 && strings.TrimSpace(title1) == strings.TrimSpace(title2), ok2
}
func (ut *RenderUtils) RenderTimelineEventBadge(c *issues_model.Comment) template.HTML {
if c.Type == issues_model.CommentTypeChangeTitle {
isToggle, isWip := commentTimelineEventIsWipToggle(c)
if !isToggle {
return svg.RenderHTML("octicon-pencil")
}
return util.Iif(isWip, svg.RenderHTML("octicon-git-pull-request-draft"), svg.RenderHTML("octicon-eye"))
}
setting.PanicInDevOrTesting("unimplemented comment type %v: %v", c.Type, c)
return htmlutil.HTMLFormat("(CommentType:%v)", c.Type)
}
func (ut *RenderUtils) RenderTimelineEventComment(c *issues_model.Comment, createdStr template.HTML) template.HTML {
if c.Type == issues_model.CommentTypeChangeTitle {
locale := ut.ctx.Value(translation.ContextKey).(translation.Locale)
isToggle, isWip := commentTimelineEventIsWipToggle(c)
if !isToggle {
return locale.Tr("repo.issues.change_title_at", ut.RenderEmoji(c.OldTitle), ut.RenderEmoji(c.NewTitle), createdStr)
}
trKey := util.Iif(isWip, "repo.pulls.marked_as_work_in_progress_at", "repo.pulls.marked_as_ready_for_review_at")
return locale.Tr(trKey, createdStr)
}
setting.PanicInDevOrTesting("unimplemented comment type %v: %v", c.Type, c)
return htmlutil.HTMLFormat("(Comment:%v,%v)", c.Type, c.Content)
}

View File

@ -0,0 +1,31 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package templates
import (
"html/template"
"testing"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/translation"
"github.com/stretchr/testify/assert"
)
func TestRenderTimelineEventComment(t *testing.T) {
ctx := reqctx.NewRequestContextForTest(t.Context())
ctx.SetContextValue(translation.ContextKey, &translation.MockLocale{})
ut := &RenderUtils{ctx: ctx}
var createdStr template.HTML = "(created-at)"
c := &issues_model.Comment{Type: issues_model.CommentTypeChangeTitle, OldTitle: "WIP: title", NewTitle: "title"}
assert.Equal(t, "repo.pulls.marked_as_ready_for_review_at:(created-at)", string(ut.RenderTimelineEventComment(c, createdStr)))
c = &issues_model.Comment{Type: issues_model.CommentTypeChangeTitle, OldTitle: "title", NewTitle: "WIP: title"}
assert.Equal(t, "repo.pulls.marked_as_work_in_progress_at:(created-at)", string(ut.RenderTimelineEventComment(c, createdStr)))
c = &issues_model.Comment{Type: issues_model.CommentTypeChangeTitle, OldTitle: "title", NewTitle: "WIP: new title"}
assert.Equal(t, "repo.issues.change_title_at:title,WIP: new title,(created-at)", string(ut.RenderTimelineEventComment(c, createdStr)))
}

View File

@ -5,6 +5,7 @@ package test
import (
"archive/tar"
"bytes"
"compress/gzip"
"io"
"net/http"
@ -12,6 +13,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/util"
)
// RedirectURL returns the redirect URL of a http response.
@ -82,3 +84,40 @@ func ReadAllTarGzContent(r io.Reader) (map[string]string, error) {
}
return content, nil
}
func WriteTarArchive(files map[string]string) *bytes.Buffer {
return WriteTarCompression(func(w io.Writer) io.WriteCloser { return util.NopCloser{Writer: w} }, files)
}
func WriteTarCompression[F func(io.Writer) io.WriteCloser | func(io.Writer) (io.WriteCloser, error)](compression F, files map[string]string) *bytes.Buffer {
buf := &bytes.Buffer{}
var cw io.WriteCloser
switch compressFunc := any(compression).(type) {
case func(io.Writer) io.WriteCloser:
cw = compressFunc(buf)
case func(io.Writer) (io.WriteCloser, error):
cw, _ = compressFunc(buf)
}
tw := tar.NewWriter(cw)
for name, content := range files {
hdr := &tar.Header{
Name: name,
Mode: 0o600,
Size: int64(len(content)),
}
_ = tw.WriteHeader(hdr)
_, _ = tw.Write([]byte(content))
}
_ = tw.Close()
_ = cw.Close()
return buf
}
func CompressGzip(content string) *bytes.Buffer {
buf := &bytes.Buffer{}
cw := gzip.NewWriter(buf)
_, _ = cw.Write([]byte(content))
_ = cw.Close()
return buf
}

View File

@ -197,11 +197,6 @@ func ToFloat64(number any) (float64, error) {
return value, nil
}
// ToPointer returns the pointer of a copy of any given value
func ToPointer[T any](val T) *T {
return &val
}
// Iif is an "inline-if", it returns "trueVal" if "condition" is true, otherwise "falseVal"
func Iif[T any](condition bool, trueVal, falseVal T) T {
if condition {

View File

@ -212,15 +212,6 @@ func TestToTitleCase(t *testing.T) {
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.NotSame(t, &abc, ToPointer(abc))
val123 := 123
assert.NotSame(t, &val123, ToPointer(val123))
}
func TestReserveLineBreakForTextarea(t *testing.T) {
assert.Equal(t, "test\ndata", ReserveLineBreakForTextarea("test\r\ndata"))
assert.Equal(t, "test\ndata\n", ReserveLineBreakForTextarea("test\r\ndata\r\n"))

View File

@ -69,7 +69,7 @@ func TestRouter(t *testing.T) {
chiCtx := chi.RouteContext(req.Context())
res.method = req.Method
res.pathParams = chiURLParamsToMap(chiCtx)
res.chiRoutePattern = util.ToPointer(chiCtx.RoutePattern())
res.chiRoutePattern = new(chiCtx.RoutePattern())
if mark != "" {
res.handlerMarks = append(res.handlerMarks, mark)
}
@ -139,7 +139,7 @@ func TestRouter(t *testing.T) {
testRoute(t, "GET /the-user/the-repo/other", resultStruct{
method: "GET",
handlerMarks: []string{"not-found:/"},
chiRoutePattern: util.ToPointer(""),
chiRoutePattern: new(""),
})
testRoute(t, "GET /the-user/the-repo/pulls", resultStruct{
method: "GET",
@ -150,7 +150,7 @@ func TestRouter(t *testing.T) {
method: "GET",
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "issues", "index": "123"},
handlerMarks: []string{"view-issue"},
chiRoutePattern: util.ToPointer("/{username}/{reponame}/{type:issues|pulls}/{index}"),
chiRoutePattern: new("/{username}/{reponame}/{type:issues|pulls}/{index}"),
})
testRoute(t, "GET /the-user/the-repo/issues/123?stop=hijack", resultStruct{
method: "GET",
@ -228,7 +228,7 @@ func TestRouter(t *testing.T) {
method: "GET",
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
handlerMarks: []string{"s1", "s2", "s3"},
chiRoutePattern: util.ToPointer("/api/v1/repos/{username}/{reponame}/branches/<dir:*>/<file:[a-z]{1,2}>"),
chiRoutePattern: new("/api/v1/repos/{username}/{reponame}/branches/<dir:*>/<file:[a-z]{1,2}>"),
})
})
}

View File

@ -148,6 +148,13 @@
"filter.private": "Private",
"no_results_found": "No results found.",
"internal_error_skipped": "Internal error occurred but is skipped: %s",
"characters_spaces": "Spaces",
"characters_tabs": "Tabs",
"text_indent_style": "Indent style",
"text_indent_size": "Indent size",
"text_line_wrap": "Wrap",
"text_line_nowrap": "No wrap",
"text_line_wrap_mode": "Line wrap mode",
"search.search": "Search…",
"search.type_tooltip": "Search type",
"search.fuzzy": "Fuzzy",
@ -1778,6 +1785,8 @@
"repo.pulls.title_desc": "wants to merge %[1]d commits from <code>%[2]s</code> into <code id=\"branch_target\">%[3]s</code>",
"repo.pulls.merged_title_desc": "merged %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> %[4]s",
"repo.pulls.change_target_branch_at": "changed target branch from <b>%s</b> to <b>%s</b> %s",
"repo.pulls.marked_as_work_in_progress_at": "marked the pull request as work in progress %s",
"repo.pulls.marked_as_ready_for_review_at": "marked the pull request as ready for review %s",
"repo.pulls.tab_conversation": "Conversation",
"repo.pulls.tab_commits": "Commits",
"repo.pulls.tab_files": "Files Changed",
@ -2122,6 +2131,8 @@
"repo.settings.pulls.ignore_whitespace": "Ignore Whitespace for Conflicts",
"repo.settings.pulls.enable_autodetect_manual_merge": "Enable autodetect manual merge (Note: In some special cases, misjudgments can occur)",
"repo.settings.pulls.allow_rebase_update": "Enable updating pull request branch by rebase",
"repo.settings.pulls.default_target_branch": "Default target branch for new pull requests",
"repo.settings.pulls.default_target_branch_default": "Default branch (%s)",
"repo.settings.pulls.default_delete_branch_after_merge": "Delete pull request branch after merge by default",
"repo.settings.pulls.default_allow_edits_from_maintainers": "Allow edits from maintainers by default",
"repo.settings.releases_desc": "Enable Repository Releases",
@ -2434,9 +2445,10 @@
"repo.settings.block_outdated_branch_desc": "Merging will not be possible when head branch is behind base branch.",
"repo.settings.block_admin_merge_override": "Administrators must follow branch protection rules",
"repo.settings.block_admin_merge_override_desc": "Administrators must follow branch protection rules and cannot circumvent it.",
"repo.settings.default_branch_desc": "Select a default repository branch for pull requests and code commits:",
"repo.settings.default_branch_desc": "Select a default branch for code commits.",
"repo.settings.default_target_branch_desc": "Pull requests can use different default target branch if it is set in the Pull Requests section of Repository Advance Settings.",
"repo.settings.merge_style_desc": "Merge Styles",
"repo.settings.default_merge_style_desc": "Default Merge Style",
"repo.settings.default_merge_style_desc": "Default merge style",
"repo.settings.choose_branch": "Choose a branch…",
"repo.settings.no_protected_branch": "There are no protected branches.",
"repo.settings.edit_protected_branch": "Edit",
@ -2648,7 +2660,7 @@
"repo.branch.restore_success": "Branch \"%s\" has been restored.",
"repo.branch.restore_failed": "Failed to restore branch \"%s\".",
"repo.branch.protected_deletion_failed": "Branch \"%s\" is protected. It cannot be deleted.",
"repo.branch.default_deletion_failed": "Branch \"%s\" is the default branch. It cannot be deleted.",
"repo.branch.default_deletion_failed": "Branch \"%s\" is the default or pull request target branch. It cannot be deleted.",
"repo.branch.default_branch_not_exist": "Default branch \"%s\" does not exist.",
"repo.branch.restore": "Restore Branch \"%s\"",
"repo.branch.download": "Download Branch \"%s\"",

View File

@ -1778,6 +1778,8 @@
"repo.pulls.title_desc": "ag iarraidh %[1]d gealltanas a chumasc ó <code>%[2]s</code> go <code id=\"branch_target\">%[3]s</code>",
"repo.pulls.merged_title_desc": "cumasc %[1]d tiomantas ó <code>%[2]s</code> go <code>%[3]s</code> %[4]s",
"repo.pulls.change_target_branch_at": "athraigh an spriocbhrainse ó <b>%s</b> go <b>%s</b> %s",
"repo.pulls.marked_as_work_in_progress_at": "marcáladh an iarratas tarraingthe mar obair ar siúl %s",
"repo.pulls.marked_as_ready_for_review_at": "marcáladh an iarratas tarraingthe mar réidh le haghaidh athbhreithnithe %s",
"repo.pulls.tab_conversation": "Comhrá",
"repo.pulls.tab_commits": "Tiomáintí",
"repo.pulls.tab_files": "Comhaid Athraithe",
@ -2122,6 +2124,8 @@
"repo.settings.pulls.ignore_whitespace": "Déan neamhaird de spás bán le haghaidh coinbhleachtaí",
"repo.settings.pulls.enable_autodetect_manual_merge": "Cumasaigh cumasc láimhe autodetector (Nóta: I roinnt cásanna speisialta, is féidir míbhreithiúnais tarlú)",
"repo.settings.pulls.allow_rebase_update": "Cumasaigh brainse iarratais tarraingthe a nuashonrú trí athbhunú",
"repo.settings.pulls.default_target_branch": "Brainse sprice réamhshocraithe le haghaidh iarratais tarraingthe nua",
"repo.settings.pulls.default_target_branch_default": "Brainse réamhshocraithe (%s)",
"repo.settings.pulls.default_delete_branch_after_merge": "Scrios brainse an iarratais tarraingthe tar éis cumasc de réir réamhshocraithe",
"repo.settings.pulls.default_allow_edits_from_maintainers": "Ceadaigh eagarthóirí ó chothabhálaí de réir réamhshocraithe",
"repo.settings.releases_desc": "Cumasaigh Eisiúintí Stórais",
@ -2434,9 +2438,10 @@
"repo.settings.block_outdated_branch_desc": "Ní bheidh cumasc indéanta nuair a bhíonn ceannbhrainse taobh thiar de bhronnbhrainse.",
"repo.settings.block_admin_merge_override": "Ní mór do riarthóirí rialacha cosanta brainse a leanúint",
"repo.settings.block_admin_merge_override_desc": "Ní mór do riarthóirí rialacha cosanta brainse a leanúint agus ní féidir leo iad a sheachaint.",
"repo.settings.default_branch_desc": "Roghnaigh brainse stóras réamhshocraithe le haghaidh iarratas tarraingte agus geallann an cód:",
"repo.settings.default_branch_desc": "Roghnaigh brainse réamhshocraithe le haghaidh tiomnuithe cóid.",
"repo.settings.default_target_branch_desc": "Is féidir le hiarratais tarraingthe brainse sprice réamhshocraithe difriúil a úsáid má tá sé socraithe sa rannán Iarratais Tarraingthe de na Socruithe Ardleibhéil Stórála.",
"repo.settings.merge_style_desc": "Stíleanna Cumaisc",
"repo.settings.default_merge_style_desc": "Stíl Cumaisc Réamhshocraithe",
"repo.settings.default_merge_style_desc": "Stíl chumasc réamhshocraithe",
"repo.settings.choose_branch": "Roghnaigh brainse…",
"repo.settings.no_protected_branch": "Níl aon bhrainsí cosanta ann.",
"repo.settings.edit_protected_branch": "Cuir in eagar",
@ -2648,7 +2653,7 @@
"repo.branch.restore_success": "Tá brainse \"%s\" curtha ar ais.",
"repo.branch.restore_failed": "Theip ar chur ar ais brainse \"%s\".",
"repo.branch.protected_deletion_failed": "Tá brainse \"%s\" cosanta. Ní féidir é a scriosadh.",
"repo.branch.default_deletion_failed": "Is é brainse \"%s\" an brainse réamhshocraithe. Ní féidir é a scriosadh.",
"repo.branch.default_deletion_failed": "Is é brainse \"%s\" an brainse sprioc réamhshocraithe nó an brainse sprioc don iarratas tarraingthe. Ní féidir é a scriosadh.",
"repo.branch.default_branch_not_exist": "Níl an brainse réamhshocraithe \"%s\" ann.",
"repo.branch.restore": "Athchóirigh Brainse \"%s\"",
"repo.branch.download": "Brainse Íosluchtaithe \"%s\"",

View File

@ -1,6 +1,6 @@
{
"type": "module",
"packageManager": "pnpm@10.28.2",
"packageManager": "pnpm@10.29.2",
"engines": {
"node": ">= 22.6.0",
"pnpm": ">= 10.0.0"
@ -15,6 +15,7 @@
"@github/relative-time-element": "5.0.0",
"@github/text-expander-element": "2.9.4",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@mermaid-js/layout-elk": "0.2.0",
"@primer/octicons": "19.21.2",
"@resvg/resvg-wasm": "2.6.2",
"@silverwind/vue3-calendar-heatmap": "2.1.1",
@ -25,7 +26,7 @@
"chart.js": "4.5.1",
"chartjs-adapter-dayjs-4": "1.0.4",
"chartjs-plugin-zoom": "2.2.0",
"clippie": "4.1.9",
"clippie": "4.1.10",
"compare-versions": "6.1.1",
"cropperjs": "1.6.2",
"css-loader": "7.1.3",
@ -57,19 +58,19 @@
"tributejs": "5.1.3",
"uint8-to-base64": "0.2.1",
"vanilla-colorful": "0.7.2",
"vue": "3.5.27",
"vue": "3.5.28",
"vue-bar-graph": "2.2.0",
"vue-chartjs": "5.3.3",
"vue-loader": "17.4.2",
"webpack": "5.104.1",
"webpack": "5.105.0",
"webpack-cli": "6.0.1",
"wrap-ansi": "9.0.2"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "4.6.0",
"@eslint/json": "0.14.0",
"@playwright/test": "1.58.1",
"@stylistic/eslint-plugin": "5.7.1",
"@playwright/test": "1.58.2",
"@stylistic/eslint-plugin": "5.8.0",
"@stylistic/stylelint-plugin": "5.0.1",
"@types/codemirror": "5.60.17",
"@types/dropzone": "5.7.9",
@ -82,9 +83,9 @@
"@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6",
"@types/toastify-js": "1.12.4",
"@typescript-eslint/parser": "8.54.0",
"@vitejs/plugin-vue": "6.0.3",
"@vitest/eslint-plugin": "1.6.6",
"@typescript-eslint/parser": "8.55.0",
"@vitejs/plugin-vue": "6.0.4",
"@vitest/eslint-plugin": "1.6.7",
"eslint": "9.39.2",
"eslint-import-resolver-typescript": "4.4.4",
"eslint-plugin-array-func": "5.1.0",
@ -96,25 +97,25 @@
"eslint-plugin-unicorn": "62.0.0",
"eslint-plugin-vue": "10.7.0",
"eslint-plugin-vue-scoped-css": "2.12.0",
"eslint-plugin-wc": "3.0.2",
"globals": "17.2.0",
"happy-dom": "20.4.0",
"eslint-plugin-wc": "3.1.0",
"globals": "17.3.0",
"happy-dom": "20.6.0",
"jiti": "2.6.1",
"markdownlint-cli": "0.47.0",
"material-icon-theme": "5.31.0",
"nolyfill": "1.0.44",
"postcss-html": "1.8.1",
"spectral-cli-bundle": "1.0.3",
"stylelint": "17.1.0",
"spectral-cli-bundle": "1.0.4",
"stylelint": "17.1.1",
"stylelint-config-recommended": "18.0.0",
"stylelint-declaration-block-no-ignored-properties": "3.0.0",
"stylelint-declaration-strict-value": "1.10.11",
"stylelint-value-no-unknown-custom-properties": "6.1.1",
"svgo": "4.0.0",
"typescript": "5.9.3",
"typescript-eslint": "8.54.0",
"updates": "17.0.9",
"vite-string-plugin": "2.0.0",
"typescript-eslint": "8.55.0",
"updates": "17.4.0",
"vite-string-plugin": "2.0.1",
"vitest": "4.0.18",
"vue-tsc": "3.2.4"
},

1038
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -117,7 +117,7 @@ func CommonRoutes() *web.Router {
&auth.OAuth2{},
&auth.Basic{},
&nuget.Auth{},
&conan.Auth{},
&Auth{},
&chef.Auth{},
})
@ -537,7 +537,8 @@ func ContainerRoutes() *web.Router {
verifyAuth(r, []auth.Method{
&auth.Basic{},
&container.Auth{},
// container auth requires an token, so container.Authenticate issues a Ghost user token for anonymous access
&Auth{AllowGhostUser: true},
})
// TODO: Content Discovery / References (not implemented yet)

View File

@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package conan
package packages
import (
"net/http"
@ -14,10 +14,13 @@ import (
var _ auth.Method = &Auth{}
type Auth struct{}
// Auth is for conan and container
type Auth struct {
AllowGhostUser bool
}
func (a *Auth) Name() string {
return "conan"
return "packages"
}
// Verify extracts the user from the Bearer token
@ -32,10 +35,22 @@ func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataS
return nil, nil
}
u, err := user_model.GetUserByID(req.Context(), packageMeta.UserID)
if err != nil {
return nil, err
var u *user_model.User
switch packageMeta.UserID {
case user_model.GhostUserID:
if !a.AllowGhostUser {
return nil, nil
}
u = user_model.NewGhostUser()
case user_model.ActionsUserID:
u = user_model.NewActionsUserWithTaskID(packageMeta.ActionsUserTaskID)
default:
u, err = user_model.GetUserByID(req.Context(), packageMeta.UserID)
if err != nil {
return nil, err
}
}
if packageMeta.Scope != "" {
store.GetData()["IsApiToken"] = true
store.GetData()["ApiTokenScope"] = packageMeta.Scope

View File

@ -1,47 +0,0 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package container
import (
"net/http"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/packages"
)
var _ auth.Method = &Auth{}
type Auth struct{}
func (a *Auth) Name() string {
return "container"
}
// Verify extracts the user from the Bearer token
// If it's an anonymous session, a ghost user is returned
func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataStore, sess auth.SessionStore) (*user_model.User, error) {
packageMeta, err := packages.ParseAuthorizationRequest(req)
if err != nil {
log.Trace("ParseAuthorizationToken: %v", err)
return nil, err
}
if packageMeta == nil || packageMeta.UserID == 0 {
return nil, nil
}
u, err := user_model.GetPossibleUserByID(req.Context(), packageMeta.UserID)
if err != nil {
return nil, err
}
if packageMeta.Scope != "" {
store.GetData()["IsApiToken"] = true
store.GetData()["ApiTokenScope"] = packageMeta.Scope
}
return u, nil
}

View File

@ -6,43 +6,21 @@ package nuget
import (
"net/http"
auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/services/auth"
)
var _ auth.Method = &Auth{}
type Auth struct{}
type Auth struct {
basicAuth auth.Basic
}
func (a *Auth) Name() string {
return "nuget"
}
// https://docs.microsoft.com/en-us/nuget/api/package-publish-resource#request-parameters
func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataStore, sess auth.SessionStore) (*user_model.User, error) {
token, err := auth_model.GetAccessTokenBySHA(req.Context(), req.Header.Get("X-NuGet-ApiKey"))
if err != nil {
if !(auth_model.IsErrAccessTokenNotExist(err) || auth_model.IsErrAccessTokenEmpty(err)) {
return nil, err
}
return nil, nil
}
u, err := user_model.GetUserByID(req.Context(), token.UID)
if err != nil {
return nil, err
}
token.UpdatedUnix = timeutil.TimeStampNow()
if err := auth_model.UpdateAccessToken(req.Context(), token); err != nil {
log.Error("UpdateAccessToken: %v", err)
}
store.GetData()["IsApiToken"] = true
store.GetData()["ApiToken"] = token
return u, nil
// ref: https://docs.microsoft.com/en-us/nuget/api/package-publish-resource#request-parameters
return a.basicAuth.VerifyAuthToken(req, w, store, sess, req.Header.Get("X-NuGet-ApiKey"))
}

View File

@ -57,8 +57,13 @@ func ListHooks(ctx *context.APIContext) {
case "all":
isSystemWebhook = optional.None[bool]()
}
listOptions := utils.GetListOptions(ctx)
opts := &webhook.ListSystemWebhookOptions{
ListOptions: listOptions,
IsSystem: isSystemWebhook,
}
sysHooks, err := webhook.GetSystemOrDefaultWebhooks(ctx, isSystemWebhook)
sysHooks, total, err := webhook.GetGlobalWebhooks(ctx, opts)
if err != nil {
ctx.APIErrorInternal(err)
return
@ -72,6 +77,8 @@ func ListHooks(ctx *context.APIContext) {
}
hooks[i] = h
}
ctx.SetLinkHeader(int(total), listOptions.PageSize)
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, hooks)
}

View File

@ -125,8 +125,8 @@ func ListRepoNotifications(ctx *context.APIContext) {
return
}
ctx.SetLinkHeader(int(totalCount), opts.PageSize)
ctx.SetTotalCountHeader(totalCount)
ctx.JSON(http.StatusOK, convert.ToNotifications(ctx, nl))
}

View File

@ -86,6 +86,7 @@ func ListNotifications(ctx *context.APIContext) {
return
}
ctx.SetLinkHeader(int(totalCount), opts.PageSize)
ctx.SetTotalCountHeader(totalCount)
ctx.JSON(http.StatusOK, convert.ToNotifications(ctx, nl))
}

View File

@ -67,6 +67,7 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) {
}
}
ctx.SetLinkHeader(int(count), opts.PageSize)
ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, apiSecrets)
}
@ -240,9 +241,10 @@ func (Action) ListVariables(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
listOptions := utils.GetListOptions(ctx)
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
OwnerID: ctx.Org.Organization.ID,
ListOptions: utils.GetListOptions(ctx),
ListOptions: listOptions,
})
if err != nil {
ctx.APIErrorInternal(err)
@ -259,7 +261,7 @@ func (Action) ListVariables(ctx *context.APIContext) {
Description: v.Description,
}
}
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, variables)
}

View File

@ -20,11 +20,12 @@ import (
// listMembers list an organization's members
func listMembers(ctx *context.APIContext, isMember bool) {
listOptions := utils.GetListOptions(ctx)
opts := &organization.FindOrgMembersOpts{
Doer: ctx.Doer,
IsDoerMember: isMember,
OrgID: ctx.Org.Organization.ID,
ListOptions: utils.GetListOptions(ctx),
ListOptions: listOptions,
}
count, err := organization.CountOrgMembers(ctx, opts)
@ -44,6 +45,7 @@ func listMembers(ctx *context.APIContext, isMember bool) {
apiMembers[i] = convert.ToUser(ctx, member, ctx.Doer)
}
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, apiMembers)
}

View File

@ -54,8 +54,9 @@ func ListTeams(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
listOptions := utils.GetListOptions(ctx)
teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{
ListOptions: utils.GetListOptions(ctx),
ListOptions: listOptions,
OrgID: ctx.Org.Organization.ID,
})
if err != nil {
@ -69,6 +70,7 @@ func ListTeams(ctx *context.APIContext) {
return
}
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, apiTeams)
}
@ -93,8 +95,9 @@ func ListUserTeams(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/TeamList"
listOptions := utils.GetListOptions(ctx)
teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{
ListOptions: utils.GetListOptions(ctx),
ListOptions: listOptions,
UserID: ctx.Doer.ID,
})
if err != nil {
@ -108,6 +111,7 @@ func ListUserTeams(ctx *context.APIContext) {
return
}
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, apiTeams)
}
@ -392,8 +396,9 @@ func GetTeamMembers(ctx *context.APIContext) {
return
}
listOptions := utils.GetListOptions(ctx)
teamMembers, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{
ListOptions: utils.GetListOptions(ctx),
ListOptions: listOptions,
TeamID: ctx.Org.Team.ID,
})
if err != nil {
@ -406,6 +411,7 @@ func GetTeamMembers(ctx *context.APIContext) {
members[i] = convert.ToUser(ctx, member, ctx.Doer)
}
ctx.SetLinkHeader(ctx.Org.Team.NumMembers, listOptions.PageSize)
ctx.SetTotalCountHeader(int64(ctx.Org.Team.NumMembers))
ctx.JSON(http.StatusOK, members)
}
@ -559,8 +565,9 @@ func GetTeamRepos(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
team := ctx.Org.Team
listOptions := utils.GetListOptions(ctx)
teamRepos, err := repo_model.GetTeamRepositories(ctx, &repo_model.SearchTeamRepoOptions{
ListOptions: utils.GetListOptions(ctx),
ListOptions: listOptions,
TeamID: team.ID,
})
if err != nil {
@ -576,6 +583,7 @@ func GetTeamRepos(ctx *context.APIContext) {
}
repos[i] = convert.ToRepo(ctx, repo, permission)
}
ctx.SetLinkHeader(team.NumRepos, listOptions.PageSize)
ctx.SetTotalCountHeader(int64(team.NumRepos))
ctx.JSON(http.StatusOK, repos)
}
@ -874,7 +882,7 @@ func ListTeamActivityFeeds(ctx *context.APIContext) {
ctx.APIErrorInternal(err)
return
}
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, convert.ToActivities(ctx, feeds, ctx.Doer))
}

View File

@ -69,10 +69,11 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
repo := ctx.Repo.Repository
listOptions := utils.GetListOptions(ctx)
opts := &secret_model.FindSecretsOptions{
RepoID: repo.ID,
ListOptions: utils.GetListOptions(ctx),
ListOptions: listOptions,
}
secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts)
@ -89,7 +90,7 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) {
Created: v.CreatedUnix.AsTime(),
}
}
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, apiSecrets)
}
@ -482,9 +483,11 @@ func (Action) ListVariables(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
listOptions := utils.GetListOptions(ctx)
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
RepoID: ctx.Repo.Repository.ID,
ListOptions: utils.GetListOptions(ctx),
ListOptions: listOptions,
})
if err != nil {
ctx.APIErrorInternal(err)
@ -502,6 +505,7 @@ func (Action) ListVariables(ctx *context.APIContext) {
}
}
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, variables)
}
@ -807,9 +811,10 @@ func ListActionTasks(ctx *context.APIContext) {
// "$ref": "#/responses/conflict"
// "422":
// "$ref": "#/responses/validationError"
listOptions := utils.GetListOptions(ctx)
tasks, total, err := db.FindAndCount[actions_model.ActionTask](ctx, &actions_model.FindTaskOptions{
ListOptions: utils.GetListOptions(ctx),
ListOptions: listOptions,
RepoID: ctx.Repo.Repository.ID,
})
if err != nil {
@ -830,6 +835,8 @@ func ListActionTasks(ctx *context.APIContext) {
res.Entries[i] = convertedTask
}
ctx.SetLinkHeader(int(total), listOptions.PageSize)
ctx.SetTotalCountHeader(total) // Duplicates api response field but it's better to set it for consistency
ctx.JSON(http.StatusOK, &res)
}

View File

@ -155,7 +155,7 @@ func DeleteBranch(ctx *context.APIContext) {
case git.IsErrBranchNotExist(err):
ctx.APIErrorNotFound(err)
case errors.Is(err, repo_service.ErrBranchIsDefault):
ctx.APIError(http.StatusForbidden, errors.New("can not delete default branch"))
ctx.APIError(http.StatusForbidden, errors.New("can not delete default or pull request target branch"))
case errors.Is(err, git_model.ErrBranchIsProtected):
ctx.APIError(http.StatusForbidden, errors.New("branch protected"))
default:

View File

@ -7,13 +7,13 @@ package repo
import (
"net/http"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
@ -77,23 +77,14 @@ func GetIssueDependencies(ctx *context.APIContext) {
return
}
page := max(ctx.FormInt("page"), 1)
limit := ctx.FormInt("limit")
if limit == 0 {
limit = setting.API.DefaultPagingNum
} else if limit > setting.API.MaxResponseItems {
limit = setting.API.MaxResponseItems
}
listOptions := utils.GetListOptions(ctx)
canWrite := ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull)
blockerIssues := make([]*issues_model.Issue, 0, limit)
blockerIssues := make([]*issues_model.Issue, 0, listOptions.PageSize)
// 2. Get the issues this issue depends on, i.e. the `<#b>`: `<issue> <- <#b>`
blockersInfo, err := issue.BlockedByDependencies(ctx, db.ListOptions{
Page: page,
PageSize: limit,
})
blockersInfo, total, err := issue.BlockedByDependencies(ctx, listOptions)
if err != nil {
ctx.APIErrorInternal(err)
return
@ -149,7 +140,8 @@ func GetIssueDependencies(ctx *context.APIContext) {
}
blockerIssues = append(blockerIssues, &blocker.Issue)
}
ctx.SetLinkHeader(int(total), listOptions.PageSize)
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, blockerIssues))
}

View File

@ -1138,7 +1138,7 @@ func parseCompareInfo(ctx *context.APIContext, compareParam string) (result *git
return nil, nil
}
baseRef := ctx.Repo.GitRepo.UnstableGuessRefByShortName(util.IfZero(compareReq.BaseOriRef, baseRepo.DefaultBranch))
baseRef := ctx.Repo.GitRepo.UnstableGuessRefByShortName(util.IfZero(compareReq.BaseOriRef, baseRepo.GetPullRequestTargetBranch(ctx)))
headRef := headGitRepo.UnstableGuessRefByShortName(util.IfZero(compareReq.HeadOriRef, headRepo.DefaultBranch))
log.Trace("Repo path: %q, base ref: %q->%q, head ref: %q->%q", ctx.Repo.Repository.RelativePath(), compareReq.BaseOriRef, baseRef, compareReq.HeadOriRef, headRef)

View File

@ -257,8 +257,8 @@ func GetCombinedCommitStatusByRef(ctx *context.APIContext) {
}
repo := ctx.Repo.Repository
statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, refCommit.Commit.ID.String(), utils.GetListOptions(ctx))
listOptions := utils.GetListOptions(ctx)
statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, refCommit.Commit.ID.String(), listOptions)
if err != nil {
ctx.APIErrorInternal(fmt.Errorf("GetLatestCommitStatus[%s, %s]: %w", repo.FullName(), refCommit.CommitID, err))
return
@ -269,6 +269,7 @@ func GetCombinedCommitStatusByRef(ctx *context.APIContext) {
ctx.APIErrorInternal(fmt.Errorf("CountLatestCommitStatus[%s, %s]: %w", repo.FullName(), refCommit.CommitID, err))
return
}
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count)
combiStatus := convert.ToCombinedStatus(ctx, refCommit.Commit.ID.String(), statuses,

View File

@ -333,6 +333,7 @@ func ListWikiPages(ctx *context.APIContext) {
pages = append(pages, wiki_service.ToWikiPageMetaData(wikiName, c, ctx.Repo.Repository))
}
ctx.SetLinkHeader(len(entries), limit)
ctx.SetTotalCountHeader(int64(len(entries)))
ctx.JSON(http.StatusOK, pages)
}
@ -445,6 +446,7 @@ func ListPageRevisions(ctx *context.APIContext) {
return
}
// FIXME: SetLinkHeader missing
ctx.SetTotalCountHeader(commitsCount)
ctx.JSON(http.StatusOK, convert.ToWikiCommitList(commitsHistory, commitsCount))
}

View File

@ -32,11 +32,12 @@ func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64) {
if ownerID != 0 && repoID != 0 {
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
}
listOptions := utils.GetListOptions(ctx)
opts := actions_model.FindRunJobOptions{
OwnerID: ownerID,
RepoID: repoID,
RunID: runID,
ListOptions: utils.GetListOptions(ctx),
ListOptions: listOptions,
}
for _, status := range ctx.FormStrings("status") {
values, err := convertToInternal(status)
@ -78,7 +79,8 @@ func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64) {
}
res.Entries[i] = convertedWorkflowJob
}
ctx.SetLinkHeader(int(total), listOptions.PageSize)
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, &res)
}
@ -120,10 +122,11 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
if ownerID != 0 && repoID != 0 {
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
}
listOptions := utils.GetListOptions(ctx)
opts := actions_model.FindRunOptions{
OwnerID: ownerID,
RepoID: repoID,
ListOptions: utils.GetListOptions(ctx),
ListOptions: listOptions,
}
if event := ctx.FormString("event"); event != "" {
@ -182,6 +185,7 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
}
res.Entries[i] = convertedRun
}
ctx.SetLinkHeader(int(total), listOptions.PageSize)
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, &res)
}

View File

@ -16,8 +16,9 @@ import (
)
func ListBlocks(ctx *context.APIContext, blocker *user_model.User) {
listOptions := utils.GetListOptions(ctx)
blocks, total, err := user_model.FindBlockings(ctx, &user_model.FindBlockingOptions{
ListOptions: utils.GetListOptions(ctx),
ListOptions: listOptions,
BlockerID: blocker.ID,
})
if err != nil {
@ -35,6 +36,7 @@ func ListBlocks(ctx *context.APIContext, blocker *user_model.User) {
users = append(users, convert.ToUser(ctx, b.Blockee, blocker))
}
ctx.SetLinkHeader(int(total), listOptions.PageSize)
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, &users)
}

View File

@ -333,10 +333,10 @@ func ListVariables(ctx *context.APIContext) {
// "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/notFound"
listOptions := utils.GetListOptions(ctx)
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
OwnerID: ctx.Doer.ID,
ListOptions: utils.GetListOptions(ctx),
ListOptions: listOptions,
})
if err != nil {
ctx.APIErrorInternal(err)
@ -354,6 +354,7 @@ func ListVariables(ctx *context.APIContext) {
}
}
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, variables)
}

View File

@ -24,12 +24,14 @@ func responseAPIUsers(ctx *context.APIContext, users []*user_model.User) {
}
func listUserFollowers(ctx *context.APIContext, u *user_model.User) {
users, count, err := user_model.GetUserFollowers(ctx, u, ctx.Doer, utils.GetListOptions(ctx))
listOptions := utils.GetListOptions(ctx)
users, count, err := user_model.GetUserFollowers(ctx, u, ctx.Doer, listOptions)
if err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count)
responseAPIUsers(ctx, users)
}
@ -88,12 +90,14 @@ func ListFollowers(ctx *context.APIContext) {
}
func listUserFollowing(ctx *context.APIContext, u *user_model.User) {
users, count, err := user_model.GetUserFollowing(ctx, u, ctx.Doer, utils.GetListOptions(ctx))
listOptions := utils.GetListOptions(ctx)
users, count, err := user_model.GetUserFollowing(ctx, u, ctx.Doer, listOptions)
if err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count)
responseAPIUsers(ctx, users)
}

View File

@ -1,4 +1,5 @@
// Copyright 2015 The Gogs Authors. All rights reserved.
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package user
@ -53,11 +54,11 @@ func composePublicKeysAPILink() string {
func listPublicKeys(ctx *context.APIContext, user *user_model.User) {
var keys []*asymkey_model.PublicKey
var err error
var count int
var count int64
fingerprint := ctx.FormString("fingerprint")
username := ctx.PathParam("username")
listOptions := utils.GetListOptions(ctx)
if fingerprint != "" {
var userID int64 // Unrestricted
// Querying not just listing
@ -65,20 +66,18 @@ func listPublicKeys(ctx *context.APIContext, user *user_model.User) {
// Restrict to provided uid
userID = user.ID
}
keys, err = db.Find[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{
keys, count, err = db.FindAndCount[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{
ListOptions: listOptions,
OwnerID: userID,
Fingerprint: fingerprint,
})
count = len(keys)
} else {
var total int64
// Use ListPublicKeys
keys, total, err = db.FindAndCount[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{
ListOptions: utils.GetListOptions(ctx),
keys, count, err = db.FindAndCount[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{
ListOptions: listOptions,
OwnerID: user.ID,
NotKeytype: asymkey_model.KeyTypePrincipal,
})
count = int(total)
}
if err != nil {
@ -95,7 +94,8 @@ func listPublicKeys(ctx *context.APIContext, user *user_model.User) {
}
}
ctx.SetTotalCountHeader(int64(count))
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, &apiKeys)
}

View File

@ -76,6 +76,7 @@ func GetStarredRepos(ctx *context.APIContext) {
return
}
ctx.SetLinkHeader(ctx.ContextUser.NumStars, utils.GetListOptions(ctx).PageSize)
ctx.SetTotalCountHeader(int64(ctx.ContextUser.NumStars))
ctx.JSON(http.StatusOK, &repos)
}
@ -107,6 +108,7 @@ func GetMyStarredRepos(ctx *context.APIContext) {
ctx.APIErrorInternal(err)
}
ctx.SetLinkHeader(ctx.Doer.NumStars, utils.GetListOptions(ctx).PageSize)
ctx.SetTotalCountHeader(int64(ctx.Doer.NumStars))
ctx.JSON(http.StatusOK, &repos)
}

View File

@ -71,6 +71,7 @@ func GetWatchedRepos(ctx *context.APIContext) {
ctx.APIErrorInternal(err)
}
ctx.SetLinkHeader(int(total), utils.GetListOptions(ctx).PageSize)
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, &repos)
}
@ -99,7 +100,7 @@ func GetMyWatchedRepos(ctx *context.APIContext) {
if err != nil {
ctx.APIErrorInternal(err)
}
ctx.SetLinkHeader(int(total), utils.GetListOptions(ctx).PageSize)
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, &repos)
}

View File

@ -23,8 +23,9 @@ import (
// ListOwnerHooks lists the webhooks of the provided owner
func ListOwnerHooks(ctx *context.APIContext, owner *user_model.User) {
listOptions := GetListOptions(ctx)
opts := &webhook.ListWebhookOptions{
ListOptions: GetListOptions(ctx),
ListOptions: listOptions,
OwnerID: owner.ID,
}
@ -42,7 +43,7 @@ func ListOwnerHooks(ctx *context.APIContext, owner *user_model.User) {
return
}
}
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, apiHooks)
}

View File

@ -12,7 +12,7 @@ import (
// GetListOptions returns list options using the page and limit parameters
func GetListOptions(ctx *context.APIContext) db.ListOptions {
return db.ListOptions{
Page: ctx.FormInt("page"),
Page: max(ctx.FormInt("page"), 1),
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
}
}

View File

@ -158,16 +158,16 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
if link.Href == "#" {
link.Href = srcLink
}
titleExtra = ctx.Locale.Tr("action.mirror_sync_push", act.GetRepoAbsoluteLink(ctx), srcLink, act.GetBranch(), act.ShortRepoPath(ctx))
titleExtra = ctx.Locale.Tr("action.mirror_sync_push", act.GetRepoAbsoluteLink(ctx), srcLink, act.RefName, act.ShortRepoPath(ctx))
case activities_model.ActionMirrorSyncCreate:
srcLink := toSrcLink(ctx, act)
if link.Href == "#" {
link.Href = srcLink
}
titleExtra = ctx.Locale.Tr("action.mirror_sync_create", act.GetRepoAbsoluteLink(ctx), srcLink, act.GetBranch(), act.ShortRepoPath(ctx))
titleExtra = ctx.Locale.Tr("action.mirror_sync_create", act.GetRepoAbsoluteLink(ctx), srcLink, act.RefName, act.ShortRepoPath(ctx))
case activities_model.ActionMirrorSyncDelete:
link.Href = act.GetRepoAbsoluteLink(ctx)
titleExtra = ctx.Locale.Tr("action.mirror_sync_delete", act.GetRepoAbsoluteLink(ctx), act.GetBranch(), act.ShortRepoPath(ctx))
titleExtra = ctx.Locale.Tr("action.mirror_sync_delete", act.GetRepoAbsoluteLink(ctx), act.RefName, act.ShortRepoPath(ctx))
case activities_model.ActionApprovePullRequest:
pullLink := toPullLink(ctx, act)
titleExtra = ctx.Locale.Tr("action.approve_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))

View File

@ -14,7 +14,6 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/common"
"code.gitea.io/gitea/services/attachment"
"code.gitea.io/gitea/services/context"
@ -200,7 +199,7 @@ func ServeAttachment(ctx *context.Context, uuid string) {
}
defer fr.Close()
common.ServeContentByReadSeeker(ctx.Base, attach.Name, util.ToPointer(attach.CreatedUnix.AsTime()), fr)
common.ServeContentByReadSeeker(ctx.Base, attach.Name, new(attach.CreatedUnix.AsTime()), fr)
}
// GetAttachment serve attachments

View File

@ -41,6 +41,7 @@ func Branches(ctx *context.Context) {
ctx.Data["AllowsPulls"] = ctx.Repo.Repository.AllowsPulls(ctx)
ctx.Data["IsWriter"] = ctx.Repo.CanWrite(unit.TypeCode)
ctx.Data["IsMirror"] = ctx.Repo.Repository.IsMirror
// TODO: Can be replaced by ctx.Repo.PullRequestCtx.CanCreateNewPull()
ctx.Data["CanPull"] = ctx.Repo.CanWrite(unit.TypeCode) ||
(ctx.IsSigned && repo_model.HasForkedRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID))
ctx.Data["PageIsViewCode"] = true

View File

@ -4,7 +4,6 @@
package repo
import (
"bufio"
gocontext "context"
"encoding/csv"
"errors"
@ -247,7 +246,7 @@ func ParseCompareInfo(ctx *context.Context) *git_service.CompareInfo {
}
// 4 get base and head refs
baseRefName := util.IfZero(compareReq.BaseOriRef, baseRepo.DefaultBranch)
baseRefName := util.IfZero(compareReq.BaseOriRef, baseRepo.GetPullRequestTargetBranch(ctx))
headRefName := util.IfZero(compareReq.HeadOriRef, headRepo.DefaultBranch)
baseRef := ctx.Repo.GitRepo.UnstableGuessRefByShortName(baseRefName)
@ -276,10 +275,10 @@ func ParseCompareInfo(ctx *context.Context) *git_service.CompareInfo {
ctx.Data["BaseBranch"] = baseRef.ShortName() // for legacy templates
ctx.Data["HeadUser"] = headOwner
ctx.Data["HeadBranch"] = headRef.ShortName() // for legacy templates
ctx.Repo.PullRequest.SameRepo = isSameRepo
ctx.Data["IsPull"] = true
context.InitRepoPullRequestCtx(ctx, baseRepo, headRepo)
// The current base and head repositories and branches may not
// actually be the intended branches that the user wants to
// create a pull-request from - but also determining the head
@ -744,13 +743,16 @@ func attachHiddenCommentIDs(section *gitdiff.DiffSection, lineComments map[int64
// ExcerptBlob render blob excerpt contents
func ExcerptBlob(ctx *context.Context) {
commitID := ctx.PathParam("sha")
lastLeft := ctx.FormInt("last_left")
lastRight := ctx.FormInt("last_right")
idxLeft := ctx.FormInt("left")
idxRight := ctx.FormInt("right")
leftHunkSize := ctx.FormInt("left_hunk_size")
rightHunkSize := ctx.FormInt("right_hunk_size")
direction := ctx.FormString("direction")
opts := gitdiff.BlobExcerptOptions{
LastLeft: ctx.FormInt("last_left"),
LastRight: ctx.FormInt("last_right"),
LeftIndex: ctx.FormInt("left"),
RightIndex: ctx.FormInt("right"),
LeftHunkSize: ctx.FormInt("left_hunk_size"),
RightHunkSize: ctx.FormInt("right_hunk_size"),
Direction: ctx.FormString("direction"),
Language: ctx.FormString("filelang"),
}
filePath := ctx.FormString("path")
gitRepo := ctx.Repo.GitRepo
@ -770,61 +772,27 @@ func ExcerptBlob(ctx *context.Context) {
diffBlobExcerptData.BaseLink = ctx.Repo.RepoLink + "/wiki/blob_excerpt"
}
chunkSize := gitdiff.BlobExcerptChunkSize
commit, err := gitRepo.GetCommit(commitID)
if err != nil {
ctx.HTTPError(http.StatusInternalServerError, "GetCommit")
ctx.ServerError("GetCommit", err)
return
}
section := &gitdiff.DiffSection{
FileName: filePath,
}
if direction == "up" && (idxLeft-lastLeft) > chunkSize {
idxLeft -= chunkSize
idxRight -= chunkSize
leftHunkSize += chunkSize
rightHunkSize += chunkSize
section.Lines, err = getExcerptLines(commit, filePath, idxLeft-1, idxRight-1, chunkSize)
} else if direction == "down" && (idxLeft-lastLeft) > chunkSize {
section.Lines, err = getExcerptLines(commit, filePath, lastLeft, lastRight, chunkSize)
lastLeft += chunkSize
lastRight += chunkSize
} else {
offset := -1
if direction == "down" {
offset = 0
}
section.Lines, err = getExcerptLines(commit, filePath, lastLeft, lastRight, idxRight-lastRight+offset)
leftHunkSize = 0
rightHunkSize = 0
idxLeft = lastLeft
idxRight = lastRight
}
blob, err := commit.Tree.GetBlobByPath(filePath)
if err != nil {
ctx.HTTPError(http.StatusInternalServerError, "getExcerptLines")
ctx.ServerError("GetBlobByPath", err)
return
}
newLineSection := &gitdiff.DiffLine{
Type: gitdiff.DiffLineSection,
SectionInfo: &gitdiff.DiffLineSectionInfo{
Path: filePath,
LastLeftIdx: lastLeft,
LastRightIdx: lastRight,
LeftIdx: idxLeft,
RightIdx: idxRight,
LeftHunkSize: leftHunkSize,
RightHunkSize: rightHunkSize,
},
reader, err := blob.DataAsync()
if err != nil {
ctx.ServerError("DataAsync", err)
return
}
if newLineSection.GetExpandDirection() != "" {
newLineSection.Content = fmt.Sprintf("@@ -%d,%d +%d,%d @@\n", idxLeft, leftHunkSize, idxRight, rightHunkSize)
switch direction {
case "up":
section.Lines = append([]*gitdiff.DiffLine{newLineSection}, section.Lines...)
case "down":
section.Lines = append(section.Lines, newLineSection)
}
defer reader.Close()
section, err := gitdiff.BuildBlobExcerptDiffSection(filePath, reader, opts)
if err != nil {
ctx.ServerError("BuildBlobExcerptDiffSection", err)
return
}
diffBlobExcerptData.PullIssueIndex = ctx.FormInt64("pull_issue_index")
@ -865,37 +833,3 @@ func ExcerptBlob(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplBlobExcerpt)
}
func getExcerptLines(commit *git.Commit, filePath string, idxLeft, idxRight, chunkSize int) ([]*gitdiff.DiffLine, error) {
blob, err := commit.Tree.GetBlobByPath(filePath)
if err != nil {
return nil, err
}
reader, err := blob.DataAsync()
if err != nil {
return nil, err
}
defer reader.Close()
scanner := bufio.NewScanner(reader)
var diffLines []*gitdiff.DiffLine
for line := 0; line < idxRight+chunkSize; line++ {
if ok := scanner.Scan(); !ok {
break
}
if line < idxRight {
continue
}
lineText := scanner.Text()
diffLine := &gitdiff.DiffLine{
LeftIdx: idxLeft + (line - idxRight) + 1,
RightIdx: line + 1,
Type: gitdiff.DiffLinePlain,
Content: " " + lineText,
}
diffLines = append(diffLines, diffLine)
}
if err = scanner.Err(); err != nil {
return nil, fmt.Errorf("getExcerptLines scan: %w", err)
}
return diffLines, nil
}

View File

@ -18,7 +18,6 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
@ -78,8 +77,6 @@ func prepareEditorPageFormOptions(ctx *context.Context, editorAction string) *co
ctx.Data["CommitFormOptions"] = commitFormOptions
// for online editor
ctx.Data["PreviewableExtensions"] = strings.Join(markup.PreviewableExtensions(), ",")
ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
ctx.Data["IsEditingFileOnly"] = ctx.FormString("return_uri") != ""
ctx.Data["ReturnURI"] = ctx.FormString("return_uri")
@ -321,7 +318,7 @@ func EditFile(ctx *context.Context) {
}
}
ctx.Data["EditorconfigJson"] = getContextRepoEditorConfig(ctx, ctx.Repo.TreePath)
ctx.Data["CodeEditorConfig"] = getCodeEditorConfig(ctx, ctx.Repo.TreePath)
ctx.HTML(http.StatusOK, tplEditFile)
}

View File

@ -20,6 +20,7 @@ func NewDiffPatch(ctx *context.Context) {
}
ctx.Data["PageIsPatch"] = true
ctx.Data["CodeEditorConfig"] = CodeEditorConfig{} // not really editing a file, so no need to fill in the config
ctx.HTML(http.StatusOK, tplPatchFile)
}

View File

@ -6,12 +6,13 @@ package repo
import (
"net/http"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/context"
files_service "code.gitea.io/gitea/services/repository/files"
)
func DiffPreviewPost(ctx *context.Context) {
content := ctx.FormString("content")
newContent := ctx.FormString("content")
treePath := files_service.CleanGitTreePath(ctx.Repo.TreePath)
if treePath == "" {
ctx.HTTPError(http.StatusBadRequest, "file name to diff is invalid")
@ -27,7 +28,12 @@ func DiffPreviewPost(ctx *context.Context) {
return
}
diff, err := files_service.GetDiffPreview(ctx, ctx.Repo.Repository, ctx.Repo.BranchName, treePath, content)
oldContent, err := entry.Blob().GetBlobContent(setting.UI.MaxDisplayFileSize)
if err != nil {
ctx.ServerError("GetBlobContent", err)
return
}
diff, err := files_service.GetDiffPreview(ctx, ctx.Repo.Repository, ctx.Repo.BranchName, treePath, oldContent, newContent)
if err != nil {
ctx.ServerError("GetDiffPreview", err)
return

View File

@ -7,6 +7,7 @@ import (
"context"
"fmt"
"path"
"strconv"
"strings"
git_model "code.gitea.io/gitea/models/git"
@ -14,9 +15,11 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
context_service "code.gitea.io/gitea/services/context"
)
@ -62,17 +65,33 @@ func getClosestParentWithFiles(gitRepo *git.Repository, branchName, originTreePa
return f(originTreePath, commit)
}
// getContextRepoEditorConfig returns the editorconfig JSON string for given treePath or "null"
func getContextRepoEditorConfig(ctx *context_service.Context, treePath string) string {
// CodeEditorConfig is also used by frontend, defined in "codeeditor.ts"
type CodeEditorConfig struct {
PreviewableExtensions []string `json:"previewable_extensions"`
LineWrapExtensions []string `json:"line_wrap_extensions"`
LineWrapOn bool `json:"line_wrap_on"`
IndentStyle string `json:"indent_style"`
IndentSize int `json:"indent_size"`
TabWidth int `json:"tab_width"`
TrimTrailingWhitespace *bool `json:"trim_trailing_whitespace,omitempty"`
}
func getCodeEditorConfig(ctx *context_service.Context, treePath string) (ret CodeEditorConfig) {
ret.PreviewableExtensions = markup.PreviewableExtensions()
ret.LineWrapExtensions = setting.Repository.Editor.LineWrapExtensions
ret.LineWrapOn = util.SliceContainsString(ret.LineWrapExtensions, path.Ext(treePath), true)
ec, _, err := ctx.Repo.GetEditorconfig()
if err == nil {
def, err := ec.GetDefinitionForFilename(treePath)
if err == nil {
jsonStr, _ := json.Marshal(def)
return string(jsonStr)
ret.IndentStyle = def.IndentStyle
ret.IndentSize, _ = strconv.Atoi(def.IndentSize)
ret.TabWidth = def.TabWidth
ret.TrimTrailingWhitespace = def.TrimTrailingWhitespace
}
}
return "null"
return ret
}
// getParentTreeFields returns list of parent tree names and corresponding tree paths based on given treePath.

View File

@ -109,11 +109,6 @@ func MustAllowPulls(ctx *context.Context) {
ctx.NotFound(nil)
return
}
// User can send pull request if owns a forked repository.
if ctx.IsSigned && repo_model.HasForkedRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) {
ctx.Repo.PullRequest.Allowed = true
}
}
func retrieveProjectsInternal(ctx *context.Context, repo *repo_model.Repository) (open, closed []*project_model.Project) {

View File

@ -469,7 +469,7 @@ func prepareIssueViewSidebarDependency(ctx *context.Context, issue *issues_model
ctx.Data["AllowCrossRepositoryDependencies"] = setting.Service.AllowCrossRepositoryDependencies
// Get Dependencies
blockedBy, err := issue.BlockedByDependencies(ctx, db.ListOptions{})
blockedBy, _, err := issue.BlockedByDependencies(ctx, db.ListOptions{})
if err != nil {
ctx.ServerError("BlockedByDependencies", err)
return

View File

@ -344,6 +344,35 @@ func (d *pullCommitStatusCheckData) CommitStatusCheckPrompt(locale translation.L
return locale.TrString("repo.pulls.status_checking")
}
func getViewPullHeadBranchInfo(ctx *context.Context, pull *issues_model.PullRequest, baseGitRepo *git.Repository) (headCommitID string, headCommitExists bool, err error) {
if pull.HeadRepo == nil {
return "", false, nil
}
headGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pull.HeadRepo)
if err != nil {
return "", false, util.Iif(errors.Is(err, util.ErrNotExist), nil, err)
}
defer closer.Close()
if pull.Flow == issues_model.PullRequestFlowGithub {
headCommitExists, _ = git_model.IsBranchExist(ctx, pull.HeadRepo.ID, pull.HeadBranch)
} else {
headCommitExists = gitrepo.IsReferenceExist(ctx, pull.BaseRepo, pull.GetGitHeadRefName())
}
if headCommitExists {
if pull.Flow != issues_model.PullRequestFlowGithub {
headCommitID, err = baseGitRepo.GetRefCommitID(pull.GetGitHeadRefName())
} else {
headCommitID, err = headGitRepo.GetBranchCommitID(pull.HeadBranch)
}
if err != nil {
return "", false, util.Iif(errors.Is(err, util.ErrNotExist), nil, err)
}
}
return headCommitID, headCommitExists, nil
}
// prepareViewPullInfo show meta information for a pull request preview page
func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_service.CompareInfo {
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
@ -430,34 +459,10 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s
return compareInfo
}
var headBranchExist bool
var headBranchSha string
// HeadRepo may be missing
if pull.HeadRepo != nil {
headGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pull.HeadRepo)
if err != nil {
ctx.ServerError("RepositoryFromContextOrOpen", err)
return nil
}
defer closer.Close()
if pull.Flow == issues_model.PullRequestFlowGithub {
headBranchExist, _ = git_model.IsBranchExist(ctx, pull.HeadRepo.ID, pull.HeadBranch)
} else {
headBranchExist = gitrepo.IsReferenceExist(ctx, pull.BaseRepo, pull.GetGitHeadRefName())
}
if headBranchExist {
if pull.Flow != issues_model.PullRequestFlowGithub {
headBranchSha, err = baseGitRepo.GetRefCommitID(pull.GetGitHeadRefName())
} else {
headBranchSha, err = headGitRepo.GetBranchCommitID(pull.HeadBranch)
}
if err != nil {
ctx.ServerError("GetBranchCommitID", err)
return nil
}
}
headBranchSha, headBranchExist, err := getViewPullHeadBranchInfo(ctx, pull, baseGitRepo)
if err != nil {
ctx.ServerError("getViewPullHeadBranchInfo", err)
return nil
}
if headBranchExist {

View File

@ -7,6 +7,7 @@ import (
"net/http"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/routers/web/repo"
"code.gitea.io/gitea/services/context"
)
@ -41,6 +42,7 @@ func GitHooksEdit(ctx *context.Context) {
return
}
ctx.Data["Hook"] = hook
ctx.Data["CodeEditorConfig"] = repo.CodeEditorConfig{} // not really editing a repo file, so no editor config
ctx.HTML(http.StatusOK, tplGithookEdit)
}

View File

@ -28,6 +28,7 @@ import (
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation"
"code.gitea.io/gitea/modules/web"
repo_router "code.gitea.io/gitea/routers/web/repo"
actions_service "code.gitea.io/gitea/services/actions"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
@ -88,6 +89,11 @@ func SettingsCtxData(ctx *context.Context) {
return
}
ctx.Data["PushMirrors"] = pushMirrors
repo_router.PrepareBranchList(ctx)
if ctx.Written() {
return
}
}
// Settings show a repository's settings page
@ -622,6 +628,7 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge,
DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle),
DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit,
DefaultTargetBranch: strings.TrimSpace(form.DefaultTargetBranch),
}))
} else if !unit_model.TypePullRequests.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests)

View File

@ -40,25 +40,21 @@ func (b *Basic) Name() string {
return BasicMethodName
}
// Verify extracts and validates Basic data (username and password/token) from the
// "Authorization" header of the request and returns the corresponding user object for that
// name/token on successful validation.
// Returns nil if header is empty or validation fails.
func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
func (b *Basic) parseAuthBasic(req *http.Request) (ret struct{ authToken, uname, passwd string }) {
// Basic authentication should only fire on API, Feed, Download, Archives or on Git or LFSPaths
// Not all feed (rss/atom) clients feature the ability to add cookies or headers, so we need to allow basic auth for feeds
detector := newAuthPathDetector(req)
if !detector.isAPIPath() && !detector.isFeedRequest(req) && !detector.isContainerPath() && !detector.isAttachmentDownload() && !detector.isArchivePath() && !detector.isGitRawOrAttachOrLFSPath() {
return nil, nil
return ret
}
authHeader := req.Header.Get("Authorization")
if authHeader == "" {
return nil, nil
return ret
}
parsed, ok := httpauth.ParseAuthorizationHeader(authHeader)
if !ok || parsed.BasicAuth == nil {
return nil, nil
return ret
}
uname, passwd := parsed.BasicAuth.Username, parsed.BasicAuth.Password
@ -73,7 +69,12 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
} else {
log.Trace("Basic Authorization: Attempting login with username as token")
}
ret.authToken, ret.uname, ret.passwd = authToken, uname, passwd
return ret
}
// VerifyAuthToken only the access token provided as parameter, used by other auth methods that want to reuse access token verification logic
func (b *Basic) VerifyAuthToken(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore, authToken string) (*user_model.User, error) {
// get oauth2 token's user's ID
_, uid := GetOAuthAccessTokenScopeAndUserID(req.Context(), authToken)
if uid != 0 {
@ -120,6 +121,23 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
store.GetData()["LoginMethod"] = ActionTokenMethodName
return user_model.NewActionsUserWithTaskID(task.ID), nil
}
return nil, nil
}
// Verify extracts and validates Basic data (username and password/token) from the
// "Authorization" header of the request and returns the corresponding user object for that
// name/token on successful validation.
// Returns nil if header is empty or validation fails.
func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
parseBasicRet := b.parseAuthBasic(req)
authToken, uname, passwd := parseBasicRet.authToken, parseBasicRet.uname, parseBasicRet.passwd
if authToken == "" && uname == "" {
return nil, nil
}
u, err := b.VerifyAuthToken(req, w, store, sess, authToken)
if u != nil || err != nil {
return u, err
}
if !setting.Service.EnableBasicAuth {
return nil, nil

View File

@ -221,7 +221,7 @@ func APIContexter() func(http.Handler) http.Handler {
ctx := &APIContext{
Base: base,
Cache: cache.GetCache(),
Repo: &Repository{PullRequest: &PullRequest{}},
Repo: &Repository{},
Org: &APIOrganization{},
}

View File

@ -129,7 +129,7 @@ func NewWebContext(base *Base, render Render, session session.Store) *Context {
Cache: cache.GetCache(),
Link: setting.AppSubURL + strings.TrimSuffix(base.Req.URL.EscapedPath(), "/"),
Repo: &Repository{PullRequest: &PullRequest{}},
Repo: &Repository{},
Org: &Organization{},
}
ctx.TemplateContext = NewTemplateContextForWeb(ctx)

View File

@ -37,11 +37,46 @@ import (
"github.com/editorconfig/editorconfig-core-go/v2"
)
// PullRequest contains information to make a pull request
type PullRequest struct {
BaseRepo *repo_model.Repository
Allowed bool // it only used by the web tmpl: "PullRequestCtx.Allowed"
SameRepo bool // it only used by the web tmpl: "PullRequestCtx.SameRepo"
// PullRequestContext contains context information for making a new pull request
type PullRequestContext struct {
ctx *Context
baseRepo, headRepo *repo_model.Repository
canCreateNewPull *bool
defaultTargetBranch *string
}
func (prc *PullRequestContext) SameRepo() bool {
return prc.baseRepo != nil && prc.headRepo != nil && prc.baseRepo.ID == prc.headRepo.ID
}
func (prc *PullRequestContext) CanCreateNewPull() bool {
if prc.canCreateNewPull != nil {
return *prc.canCreateNewPull
}
ctx := prc.ctx
// People who have push access or have forked repository can propose a new pull request.
can := prc.baseRepo.CanContentChange() &&
(ctx.Repo.CanWrite(unit_model.TypeCode) || (ctx.IsSigned && repo_model.HasForkedRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)))
prc.canCreateNewPull = &can
return can
}
func (prc *PullRequestContext) MakeDefaultCompareLink(headBranch string) string {
return prc.baseRepo.Link() + "/compare/" +
util.PathEscapeSegments(prc.DefaultTargetBranch()) + "..." +
util.Iif(prc.SameRepo(), "", util.PathEscapeSegments(prc.headRepo.OwnerName)+":") +
util.PathEscapeSegments(headBranch)
}
func (prc *PullRequestContext) DefaultTargetBranch() string {
if prc.defaultTargetBranch != nil {
return *prc.defaultTargetBranch
}
branchName := prc.baseRepo.GetPullRequestTargetBranch(prc.ctx)
prc.defaultTargetBranch = &branchName
return branchName
}
// Repository contains information to operate a repository
@ -64,7 +99,7 @@ type Repository struct {
CommitID string
CommitsCount int64
PullRequest *PullRequest
PullRequestCtx *PullRequestContext
}
// CanWriteToBranch checks if the branch is writable by the user
@ -418,6 +453,12 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
ctx.Data["IsEmptyRepo"] = ctx.Repo.Repository.IsEmpty
}
func InitRepoPullRequestCtx(ctx *Context, base, head *repo_model.Repository) {
ctx.Repo.PullRequestCtx = &PullRequestContext{ctx: ctx}
ctx.Repo.PullRequestCtx.baseRepo, ctx.Repo.PullRequestCtx.headRepo = base, head
ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequestCtx
}
// RepoAssignment returns a middleware to handle repository assignment
func RepoAssignment(ctx *Context) {
if ctx.Data["Repository"] != nil {
@ -666,28 +707,16 @@ func RepoAssignment(ctx *Context) {
ctx.Data["BranchesCount"] = branchesTotal
// People who have push access or have forked repository can propose a new pull request.
canPush := ctx.Repo.CanWrite(unit_model.TypeCode) ||
(ctx.IsSigned && repo_model.HasForkedRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID))
canCompare := false
// Pull request is allowed if this is a fork repository
// and base repository accepts pull requests.
// Pull request is allowed if this is a fork repository, and base repository accepts pull requests.
if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls(ctx) {
canCompare = true
// TODO: this (and below) "BaseRepo" var is not clear and should be removed in the future
ctx.Data["BaseRepo"] = repo.BaseRepo
ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
ctx.Repo.PullRequest.Allowed = canPush
InitRepoPullRequestCtx(ctx, repo.BaseRepo, repo)
} else if repo.AllowsPulls(ctx) {
// Or, this is repository accepts pull requests between branches.
canCompare = true
ctx.Data["BaseRepo"] = repo
ctx.Repo.PullRequest.BaseRepo = repo
ctx.Repo.PullRequest.Allowed = canPush
ctx.Repo.PullRequest.SameRepo = true
InitRepoPullRequestCtx(ctx, repo, repo)
}
ctx.Data["CanCompareOrPull"] = canCompare
ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
if ctx.Repo.Repository.Status == repo_model.RepositoryPendingTransfer {
repoTransfer, err := repo_model.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)

View File

@ -103,6 +103,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
defaultDeleteBranchAfterMerge := false
defaultMergeStyle := repo_model.MergeStyleMerge
defaultAllowMaintainerEdit := false
defaultTargetBranch := ""
if unit, err := repo.GetUnit(ctx, unit_model.TypePullRequests); err == nil {
config := unit.PullRequestsConfig()
hasPullRequests = true
@ -118,6 +119,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
defaultDeleteBranchAfterMerge = config.DefaultDeleteBranchAfterMerge
defaultMergeStyle = config.GetDefaultMergeStyle()
defaultAllowMaintainerEdit = config.DefaultAllowMaintainerEdit
defaultTargetBranch = config.DefaultTargetBranch
}
hasProjects := false
projectsMode := repo_model.ProjectsModeAll
@ -235,6 +237,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
DefaultDeleteBranchAfterMerge: defaultDeleteBranchAfterMerge,
DefaultMergeStyle: string(defaultMergeStyle),
DefaultAllowMaintainerEdit: defaultAllowMaintainerEdit,
DefaultTargetBranch: defaultTargetBranch,
AvatarURL: repo.AvatarLink(ctx),
Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate,
MirrorInterval: mirrorInterval,

View File

@ -143,6 +143,7 @@ type RepoSettingForm struct {
PullsAllowRebaseUpdate bool
DefaultDeleteBranchAfterMerge bool
DefaultAllowMaintainerEdit bool
DefaultTargetBranch string
EnableTimetracker bool
AllowOnlyContributorsToTrackTime bool
EnableIssueDependencies bool

View File

@ -16,6 +16,7 @@ import (
"path"
"sort"
"strings"
"time"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
@ -81,6 +82,8 @@ type DiffLine struct {
// DiffLineSectionInfo represents diff line section meta data
type DiffLineSectionInfo struct {
language *diffVarMutable[string]
Path string
// These line "idx" are 1-based line numbers
@ -121,8 +124,14 @@ type DiffHTMLOperation struct {
// BlobExcerptChunkSize represent max lines of excerpt
const BlobExcerptChunkSize = 20
// MaxDiffHighlightEntireFileSize is the maximum file size that will be highlighted with "entire file diff"
const MaxDiffHighlightEntireFileSize = 1 * 1024 * 1024
// Chroma seems extremely slow when highlighting large files, it might take dozens or hundreds of milliseconds.
// When fully highlighting a diff with a lot of large files, it would take many seconds or even dozens of seconds.
// So, don't highlight the entire file if it's too large, or highlighting takes too long.
// When there is no full-file highlighting, the legacy "line-by-line" highlighting is still applied as the fallback.
const (
MaxFullFileHighlightSizeLimit = 256 * 1024
MaxFullFileHighlightTimeLimit = 2 * time.Second
)
// GetType returns the type of DiffLine.
func (d *DiffLine) GetType() int {
@ -165,16 +174,19 @@ func (d *DiffLine) GetLineTypeMarker() string {
}
func (d *DiffLine) getBlobExcerptQuery() string {
query := fmt.Sprintf(
language := ""
if d.SectionInfo.language != nil { // for normal cases, it can't be nil, this check is only for some tests
language = d.SectionInfo.language.value
}
return fmt.Sprintf(
"last_left=%d&last_right=%d&"+
"left=%d&right=%d&"+
"left_hunk_size=%d&right_hunk_size=%d&"+
"path=%s",
"path=%s&filelang=%s",
d.SectionInfo.LastLeftIdx, d.SectionInfo.LastRightIdx,
d.SectionInfo.LeftIdx, d.SectionInfo.RightIdx,
d.SectionInfo.LeftHunkSize, d.SectionInfo.RightHunkSize,
url.QueryEscape(d.SectionInfo.Path))
return query
url.QueryEscape(d.SectionInfo.Path), url.QueryEscape(language))
}
func (d *DiffLine) GetExpandDirection() string {
@ -266,11 +278,12 @@ func FillHiddenCommentIDsForDiffLine(line *DiffLine, lineComments map[int64][]*i
line.SectionInfo.HiddenCommentIDs = hiddenCommentIDs
}
func getDiffLineSectionInfo(treePath, line string, lastLeftIdx, lastRightIdx int) *DiffLineSectionInfo {
func newDiffLineSectionInfo(curFile *DiffFile, line string, lastLeftIdx, lastRightIdx int) *DiffLineSectionInfo {
leftLine, leftHunk, rightLine, rightHunk := git.ParseDiffHunkString(line)
return &DiffLineSectionInfo{
Path: treePath,
Path: curFile.Name,
language: &curFile.language,
LastLeftIdx: lastLeftIdx,
LastRightIdx: lastRightIdx,
LeftIdx: leftLine,
@ -290,7 +303,10 @@ func getLineContent(content string, locale translation.Locale) DiffInline {
// DiffSection represents a section of a DiffFile.
type DiffSection struct {
file *DiffFile
language *diffVarMutable[string]
highlightedLeftLines *diffVarMutable[map[int]template.HTML]
highlightedRightLines *diffVarMutable[map[int]template.HTML]
FileName string
Lines []*DiffLine
}
@ -339,9 +355,9 @@ func (diffSection *DiffSection) getDiffLineForRender(diffLineType DiffLineType,
var fileLanguage string
var highlightedLeftLines, highlightedRightLines map[int]template.HTML
// when a "diff section" is manually prepared by ExcerptBlob, it doesn't have "file" information
if diffSection.file != nil {
fileLanguage = diffSection.file.Language
highlightedLeftLines, highlightedRightLines = diffSection.file.highlightedLeftLines, diffSection.file.highlightedRightLines
if diffSection.language != nil {
fileLanguage = diffSection.language.value
highlightedLeftLines, highlightedRightLines = diffSection.highlightedLeftLines.value, diffSection.highlightedRightLines.value
}
var lineHTML template.HTML
@ -392,6 +408,11 @@ func (diffSection *DiffSection) GetComputedInlineDiffFor(diffLine *DiffLine, loc
}
}
// diffVarMutable is a wrapper to make a variable mutable to be shared across structs
type diffVarMutable[T any] struct {
value T
}
// DiffFile represents a file diff.
type DiffFile struct {
// only used internally to parse Ambiguous filenames
@ -418,7 +439,6 @@ type DiffFile struct {
IsIncompleteLineTooLong bool
// will be filled by the extra loop in GitDiffForRender
Language string
IsGenerated bool
IsVendored bool
SubmoduleDiffInfo *SubmoduleDiffInfo // IsSubmodule==true, then there must be a SubmoduleDiffInfo
@ -430,9 +450,10 @@ type DiffFile struct {
IsViewed bool // User specific
HasChangedSinceLastReview bool // User specific
// for render purpose only, will be filled by the extra loop in GitDiffForRender
highlightedLeftLines map[int]template.HTML
highlightedRightLines map[int]template.HTML
// for render purpose only, will be filled by the extra loop in GitDiffForRender, the maps of lines are 0-based
language diffVarMutable[string]
highlightedLeftLines diffVarMutable[map[int]template.HTML]
highlightedRightLines diffVarMutable[map[int]template.HTML]
}
// GetType returns type of diff file.
@ -469,6 +490,7 @@ func (diffFile *DiffFile) GetTailSectionAndLimitedContent(leftCommit, rightCommi
Type: DiffLineSection,
Content: " ",
SectionInfo: &DiffLineSectionInfo{
language: &diffFile.language,
Path: diffFile.Name,
LastLeftIdx: lastLine.LeftIdx,
LastRightIdx: lastLine.RightIdx,
@ -549,7 +571,7 @@ func getCommitFileLineCountAndLimitedContent(commit *git.Commit, filePath string
if err != nil {
return 0, nil
}
w := &limitByteWriter{limit: MaxDiffHighlightEntireFileSize + 1}
w := &limitByteWriter{limit: MaxFullFileHighlightSizeLimit + 1}
lineCount, err = blob.GetBlobLineCount(w)
if err != nil {
return 0, nil
@ -907,6 +929,14 @@ func skipToNextDiffHead(input *bufio.Reader) (line string, err error) {
return line, err
}
func newDiffSectionForDiffFile(curFile *DiffFile) *DiffSection {
return &DiffSection{
language: &curFile.language,
highlightedLeftLines: &curFile.highlightedLeftLines,
highlightedRightLines: &curFile.highlightedRightLines,
}
}
func parseHunks(ctx context.Context, curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio.Reader) (lineBytes []byte, isFragment bool, err error) {
sb := strings.Builder{}
@ -964,12 +994,12 @@ func parseHunks(ctx context.Context, curFile *DiffFile, maxLines, maxLineCharact
line := sb.String()
// Create a new section to represent this hunk
curSection = &DiffSection{file: curFile}
curSection = newDiffSectionForDiffFile(curFile)
lastLeftIdx = -1
curFile.Sections = append(curFile.Sections, curSection)
// FIXME: the "-1" can't be right, these "line idx" are all 1-based, maybe there are other bugs that covers this bug.
lineSectionInfo := getDiffLineSectionInfo(curFile.Name, line, leftLine-1, rightLine-1)
lineSectionInfo := newDiffLineSectionInfo(curFile, line, leftLine-1, rightLine-1)
diffLine := &DiffLine{
Type: DiffLineSection,
Content: line,
@ -1004,7 +1034,7 @@ func parseHunks(ctx context.Context, curFile *DiffFile, maxLines, maxLineCharact
rightLine++
if curSection == nil {
// Create a new section to represent this hunk
curSection = &DiffSection{file: curFile}
curSection = newDiffSectionForDiffFile(curFile)
curFile.Sections = append(curFile.Sections, curSection)
lastLeftIdx = -1
}
@ -1037,7 +1067,7 @@ func parseHunks(ctx context.Context, curFile *DiffFile, maxLines, maxLineCharact
}
if curSection == nil {
// Create a new section to represent this hunk
curSection = &DiffSection{file: curFile}
curSection = newDiffSectionForDiffFile(curFile)
curFile.Sections = append(curFile.Sections, curSection)
lastLeftIdx = -1
}
@ -1064,7 +1094,7 @@ func parseHunks(ctx context.Context, curFile *DiffFile, maxLines, maxLineCharact
lastLeftIdx = -1
if curSection == nil {
// Create a new section to represent this hunk
curSection = &DiffSection{file: curFile}
curSection = newDiffSectionForDiffFile(curFile)
curFile.Sections = append(curFile.Sections, curSection)
}
curSection.Lines = append(curSection.Lines, diffLine)
@ -1294,6 +1324,8 @@ func GetDiffForRender(ctx context.Context, repoLink string, gitRepo *git.Reposit
return nil, err
}
startTime := time.Now()
checker, err := attribute.NewBatchChecker(gitRepo, opts.AfterCommitID, []string{attribute.LinguistVendored, attribute.LinguistGenerated, attribute.LinguistLanguage, attribute.GitlabLanguage, attribute.Diff})
if err != nil {
return nil, err
@ -1309,7 +1341,7 @@ func GetDiffForRender(ctx context.Context, repoLink string, gitRepo *git.Reposit
isVendored, isGenerated = attrs.GetVendored(), attrs.GetGenerated()
language := attrs.GetLanguage()
if language.Has() {
diffFile.Language = language.Value()
diffFile.language.value = language.Value()
}
attrDiff = attrs.Get(attribute.Diff).ToString()
}
@ -1333,13 +1365,14 @@ func GetDiffForRender(ctx context.Context, repoLink string, gitRepo *git.Reposit
diffFile.Sections = append(diffFile.Sections, tailSection)
}
shouldFullFileHighlight := !setting.Git.DisableDiffHighlight && attrDiff.Value() == ""
shouldFullFileHighlight := attrDiff.Value() == "" // only do highlight if no custom diff command
shouldFullFileHighlight = shouldFullFileHighlight && time.Since(startTime) < MaxFullFileHighlightTimeLimit
if shouldFullFileHighlight {
if limitedContent.LeftContent != nil && limitedContent.LeftContent.buf.Len() < MaxDiffHighlightEntireFileSize {
diffFile.highlightedLeftLines = highlightCodeLines(diffFile, true /* left */, limitedContent.LeftContent.buf.Bytes())
if limitedContent.LeftContent != nil {
diffFile.highlightedLeftLines.value = highlightCodeLinesForDiffFile(diffFile, true /* left */, limitedContent.LeftContent.buf.Bytes())
}
if limitedContent.RightContent != nil && limitedContent.RightContent.buf.Len() < MaxDiffHighlightEntireFileSize {
diffFile.highlightedRightLines = highlightCodeLines(diffFile, false /* right */, limitedContent.RightContent.buf.Bytes())
if limitedContent.RightContent != nil {
diffFile.highlightedRightLines.value = highlightCodeLinesForDiffFile(diffFile, false /* right */, limitedContent.RightContent.buf.Bytes())
}
}
}
@ -1347,13 +1380,26 @@ func GetDiffForRender(ctx context.Context, repoLink string, gitRepo *git.Reposit
return diff, nil
}
func highlightCodeLines(diffFile *DiffFile, isLeft bool, rawContent []byte) map[int]template.HTML {
func FillDiffFileHighlightLinesByContent(diffFile *DiffFile, left, right []byte) {
diffFile.highlightedLeftLines.value = highlightCodeLinesForDiffFile(diffFile, true /* left */, left)
diffFile.highlightedRightLines.value = highlightCodeLinesForDiffFile(diffFile, false /* right */, right)
}
func highlightCodeLinesForDiffFile(diffFile *DiffFile, isLeft bool, rawContent []byte) map[int]template.HTML {
return highlightCodeLines(diffFile.Name, diffFile.language.value, diffFile.Sections, isLeft, rawContent)
}
func highlightCodeLines(name, lang string, sections []*DiffSection, isLeft bool, rawContent []byte) map[int]template.HTML {
if setting.Git.DisableDiffHighlight || len(rawContent) > MaxFullFileHighlightSizeLimit {
return nil
}
content := util.UnsafeBytesToString(charset.ToUTF8(rawContent, charset.ConvertOpts{}))
highlightedNewContent, _ := highlight.RenderCodeFast(diffFile.Name, diffFile.Language, content)
highlightedNewContent, _ := highlight.RenderCodeFast(name, lang, content)
unsafeLines := highlight.UnsafeSplitHighlightedLines(highlightedNewContent)
lines := make(map[int]template.HTML, len(unsafeLines))
// only save the highlighted lines we need, but not the whole file, to save memory
for _, sec := range diffFile.Sections {
for _, sec := range sections {
for _, ln := range sec.Lines {
lineIdx := ln.LeftIdx
if !isLeft {

View File

@ -0,0 +1,121 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitdiff
import (
"bufio"
"bytes"
"fmt"
"html/template"
"io"
"code.gitea.io/gitea/modules/setting"
)
type BlobExcerptOptions struct {
LastLeft int
LastRight int
LeftIndex int
RightIndex int
LeftHunkSize int
RightHunkSize int
Direction string
Language string
}
func fillExcerptLines(section *DiffSection, filePath string, reader io.Reader, lang string, idxLeft, idxRight, chunkSize int) error {
buf := &bytes.Buffer{}
scanner := bufio.NewScanner(reader)
var diffLines []*DiffLine
for line := 0; line < idxRight+chunkSize; line++ {
if ok := scanner.Scan(); !ok {
break
}
lineText := scanner.Text()
if buf.Len()+len(lineText) < int(setting.UI.MaxDisplayFileSize) {
buf.WriteString(lineText)
buf.WriteByte('\n')
}
if line < idxRight {
continue
}
diffLine := &DiffLine{
LeftIdx: idxLeft + (line - idxRight) + 1,
RightIdx: line + 1,
Type: DiffLinePlain,
Content: " " + lineText,
}
diffLines = append(diffLines, diffLine)
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("fillExcerptLines scan: %w", err)
}
section.Lines = diffLines
// DiffLinePlain always uses right lines
section.highlightedRightLines.value = highlightCodeLines(filePath, lang, []*DiffSection{section}, false /* right */, buf.Bytes())
return nil
}
func BuildBlobExcerptDiffSection(filePath string, reader io.Reader, opts BlobExcerptOptions) (*DiffSection, error) {
lastLeft, lastRight, idxLeft, idxRight := opts.LastLeft, opts.LastRight, opts.LeftIndex, opts.RightIndex
leftHunkSize, rightHunkSize, direction := opts.LeftHunkSize, opts.RightHunkSize, opts.Direction
language := opts.Language
chunkSize := BlobExcerptChunkSize
section := &DiffSection{
language: &diffVarMutable[string]{value: language},
highlightedLeftLines: &diffVarMutable[map[int]template.HTML]{},
highlightedRightLines: &diffVarMutable[map[int]template.HTML]{},
FileName: filePath,
}
var err error
if direction == "up" && (idxLeft-lastLeft) > chunkSize {
idxLeft -= chunkSize
idxRight -= chunkSize
leftHunkSize += chunkSize
rightHunkSize += chunkSize
err = fillExcerptLines(section, filePath, reader, language, idxLeft-1, idxRight-1, chunkSize)
} else if direction == "down" && (idxLeft-lastLeft) > chunkSize {
err = fillExcerptLines(section, filePath, reader, language, lastLeft, lastRight, chunkSize)
lastLeft += chunkSize
lastRight += chunkSize
} else {
offset := -1
if direction == "down" {
offset = 0
}
err = fillExcerptLines(section, filePath, reader, language, lastLeft, lastRight, idxRight-lastRight+offset)
leftHunkSize = 0
rightHunkSize = 0
idxLeft = lastLeft
idxRight = lastRight
}
if err != nil {
return nil, err
}
newLineSection := &DiffLine{
Type: DiffLineSection,
SectionInfo: &DiffLineSectionInfo{
language: &diffVarMutable[string]{value: opts.Language},
Path: filePath,
LastLeftIdx: lastLeft,
LastRightIdx: lastRight,
LeftIdx: idxLeft,
RightIdx: idxRight,
LeftHunkSize: leftHunkSize,
RightHunkSize: rightHunkSize,
},
}
if newLineSection.GetExpandDirection() != "" {
newLineSection.Content = fmt.Sprintf("@@ -%d,%d +%d,%d @@\n", idxLeft, leftHunkSize, idxRight, rightHunkSize)
switch direction {
case "up":
section.Lines = append([]*DiffLine{newLineSection}, section.Lines...)
case "down":
section.Lines = append(section.Lines, newLineSection)
}
}
return section, nil
}

View File

@ -0,0 +1,39 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitdiff
import (
"bytes"
"strconv"
"testing"
"code.gitea.io/gitea/modules/translation"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBuildBlobExcerptDiffSection(t *testing.T) {
data := &bytes.Buffer{}
for i := range 100 {
data.WriteString("a = " + strconv.Itoa(i+1) + "\n")
}
locale := translation.MockLocale{}
lineMiddle := 50
diffSection, err := BuildBlobExcerptDiffSection("a.py", bytes.NewReader(data.Bytes()), BlobExcerptOptions{
LeftIndex: lineMiddle,
RightIndex: lineMiddle,
LeftHunkSize: 10,
RightHunkSize: 10,
Direction: "up",
})
require.NoError(t, err)
assert.Len(t, diffSection.highlightedRightLines.value, BlobExcerptChunkSize)
assert.NotEmpty(t, diffSection.highlightedRightLines.value[lineMiddle-BlobExcerptChunkSize-1])
assert.NotEmpty(t, diffSection.highlightedRightLines.value[lineMiddle-2]) // 0-based
diffInline := diffSection.GetComputedInlineDiffFor(diffSection.Lines[1], locale)
assert.Equal(t, `<span class="n">a</span> <span class="o">=</span> <span class="mi">30</span>`+"\n", string(diffInline.Content))
}

View File

@ -1111,22 +1111,20 @@ func TestDiffLine_GetExpandDirection(t *testing.T) {
func TestHighlightCodeLines(t *testing.T) {
t.Run("CharsetDetecting", func(t *testing.T) {
diffFile := &DiffFile{
Name: "a.c",
Language: "c",
Name: "a.c",
Sections: []*DiffSection{
{
Lines: []*DiffLine{{LeftIdx: 1}},
},
},
}
ret := highlightCodeLines(diffFile, true, []byte("// abc\xcc def\xcd")) // ISO-8859-1 bytes
ret := highlightCodeLinesForDiffFile(diffFile, true, []byte("// abc\xcc def\xcd")) // ISO-8859-1 bytes
assert.Equal(t, "<span class=\"c1\">// abcÌ defÍ\n</span>", string(ret[0]))
})
t.Run("LeftLines", func(t *testing.T) {
diffFile := &DiffFile{
Name: "a.c",
Language: "c",
Name: "a.c",
Sections: []*DiffSection{
{
Lines: []*DiffLine{
@ -1138,7 +1136,7 @@ func TestHighlightCodeLines(t *testing.T) {
},
}
const nl = "\n"
ret := highlightCodeLines(diffFile, true, []byte("a\nb\n"))
ret := highlightCodeLinesForDiffFile(diffFile, true, []byte("a\nb\n"))
assert.Equal(t, map[int]template.HTML{
0: `<span class="n">a</span>` + nl,
1: `<span class="n">b</span>`,

View File

@ -7,12 +7,45 @@ import (
"bytes"
"html/template"
"strings"
"unicode/utf8"
"code.gitea.io/gitea/modules/util"
"github.com/sergi/go-diff/diffmatchpatch"
)
// token is a html tag or entity, eg: "<span ...>", "</span>", "&lt;"
func extractHTMLToken(s string) (before, token, after string, valid bool) {
// extractDiffTokenRemainingFullTag tries to extract full tag with content from the remaining string
// e.g. for input: "content</span>the-rest...", it returns "content</span>", "the-rest...", true
func extractDiffTokenRemainingFullTag(s string) (token, after string, valid bool) {
pos := 0
for ; pos < len(s); pos++ {
c := s[pos]
if c == '<' {
break
}
// keep in mind: even if we'd like to relax this check,
// we should never ignore "&" because it is for HTML entity and can't be safely used in the diff algorithm,
// because diff between "&lt;" and "&gt;" will generate broken result.
isSymbolChar := 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' || c == '_' || c == '-' || c == '.'
if !isSymbolChar {
return "", s, false
}
}
if pos+1 >= len(s) || s[pos+1] != '/' {
return "", s, false
}
pos2 := strings.IndexByte(s[pos:], '>')
if pos2 == -1 {
return "", s, false
}
return s[:pos+pos2+1], s[pos+pos2+1:], true
}
// Returned token:
// * full tag with content: "<<span>content</span>>", it is used to optimize diff results to highlight the whole changed symbol
// * opening/closing tag: "<span ...>" or "</span>"
// * HTML entity: "&lt;"
func extractDiffToken(s string) (before, token, after string, valid bool) {
for pos1 := 0; pos1 < len(s); pos1++ {
switch s[pos1] {
case '<':
@ -20,7 +53,15 @@ func extractHTMLToken(s string) (before, token, after string, valid bool) {
if pos2 == -1 {
return "", "", s, false
}
return s[:pos1], s[pos1 : pos1+pos2+1], s[pos1+pos2+1:], true
before, token, after = s[:pos1], s[pos1:pos1+pos2+1], s[pos1+pos2+1:]
if !strings.HasPrefix(token, "</") {
// try to extract full tag with content, e.g. `<<span>content</span>>`, to optimize diff results
if fullTokenRemaining, fullTokenAfter, ok := extractDiffTokenRemainingFullTag(after); ok {
return before, "<" + token + fullTokenRemaining + ">", fullTokenAfter, true
}
}
return before, token, after, true
case '&':
pos2 := strings.IndexByte(s[pos1:], ';')
if pos2 == -1 {
@ -47,7 +88,9 @@ type highlightCodeDiff struct {
placeholderOverflowCount int
lineWrapperTags []string
diffCodeAddedOpen rune
diffCodeRemovedOpen rune
diffCodeClose rune
}
func newHighlightCodeDiff() *highlightCodeDiff {
@ -87,11 +130,26 @@ func (hcd *highlightCodeDiff) collectUsedRunes(code template.HTML) {
}
}
func (hcd *highlightCodeDiff) diffLineWithHighlight(lineType DiffLineType, codeA, codeB template.HTML) template.HTML {
return hcd.diffLineWithHighlightWrapper(nil, lineType, codeA, codeB)
func (hcd *highlightCodeDiff) diffEqualPartIsSpaceOnly(s string) bool {
for _, r := range s {
if r >= hcd.placeholderBegin {
recovered := hcd.placeholderTokenMap[r]
if strings.HasPrefix(recovered, "<<") {
return false // a full tag with content, it can't be space-only
} else if strings.HasPrefix(recovered, "<") {
continue // a single opening/closing tag, skip the tag and continue to check the content
}
return false // otherwise, it must be an HTML entity, it can't be space-only
}
isSpace := r == ' ' || r == '\t' || r == '\n' || r == '\r'
if !isSpace {
return false
}
}
return true
}
func (hcd *highlightCodeDiff) diffLineWithHighlightWrapper(lineWrapperTags []string, lineType DiffLineType, codeA, codeB template.HTML) template.HTML {
func (hcd *highlightCodeDiff) diffLineWithHighlight(lineType DiffLineType, codeA, codeB template.HTML) template.HTML {
hcd.collectUsedRunes(codeA)
hcd.collectUsedRunes(codeB)
@ -104,32 +162,44 @@ func (hcd *highlightCodeDiff) diffLineWithHighlightWrapper(lineWrapperTags []str
buf := bytes.NewBuffer(nil)
// restore the line wrapper tags <span class="line"> and <span class="cl">, if necessary
for _, tag := range lineWrapperTags {
buf.WriteString(tag)
if hcd.diffCodeClose == 0 {
// tests can pre-set the placeholders
hcd.diffCodeAddedOpen = hcd.registerTokenAsPlaceholder(`<span class="added-code">`)
hcd.diffCodeRemovedOpen = hcd.registerTokenAsPlaceholder(`<span class="removed-code">`)
hcd.diffCodeClose = hcd.registerTokenAsPlaceholder(`</span><!-- diff-code-close -->`)
}
addedCodePrefix := hcd.registerTokenAsPlaceholder(`<span class="added-code">`)
removedCodePrefix := hcd.registerTokenAsPlaceholder(`<span class="removed-code">`)
codeTagSuffix := hcd.registerTokenAsPlaceholder(`</span>`)
equalPartSpaceOnly := true
for _, diff := range diffs {
if diff.Type != diffmatchpatch.DiffEqual {
continue
}
if equalPartSpaceOnly = hcd.diffEqualPartIsSpaceOnly(diff.Text); !equalPartSpaceOnly {
break
}
}
if codeTagSuffix != 0 {
// only add "added"/"removed" tags when needed:
// * non-space contents appear in the DiffEqual parts (not a full-line add/del)
// * placeholder map still works (not exhausted, can get the closing tag placeholder)
addDiffTags := !equalPartSpaceOnly && hcd.diffCodeClose != 0
if addDiffTags {
for _, diff := range diffs {
switch {
case diff.Type == diffmatchpatch.DiffEqual:
buf.WriteString(diff.Text)
case diff.Type == diffmatchpatch.DiffInsert && lineType == DiffLineAdd:
buf.WriteRune(addedCodePrefix)
buf.WriteRune(hcd.diffCodeAddedOpen)
buf.WriteString(diff.Text)
buf.WriteRune(codeTagSuffix)
buf.WriteRune(hcd.diffCodeClose)
case diff.Type == diffmatchpatch.DiffDelete && lineType == DiffLineDel:
buf.WriteRune(removedCodePrefix)
buf.WriteRune(hcd.diffCodeRemovedOpen)
buf.WriteString(diff.Text)
buf.WriteRune(codeTagSuffix)
buf.WriteRune(hcd.diffCodeClose)
}
}
} else {
// placeholder map space is exhausted
// the caller will still add added/removed backgrounds for the whole line
for _, diff := range diffs {
take := diff.Type == diffmatchpatch.DiffEqual || (diff.Type == diffmatchpatch.DiffInsert && lineType == DiffLineAdd) || (diff.Type == diffmatchpatch.DiffDelete && lineType == DiffLineDel)
if take {
@ -137,19 +207,21 @@ func (hcd *highlightCodeDiff) diffLineWithHighlightWrapper(lineWrapperTags []str
}
}
}
for range lineWrapperTags {
buf.WriteString("</span>")
}
return hcd.recoverOneDiff(buf.String())
}
func (hcd *highlightCodeDiff) registerTokenAsPlaceholder(token string) rune {
recovered := token
if token[0] == '<' && token[1] != '<' {
// when recovering a single tag, only use the tag itself, ignore the trailing comment (for how the comment is generated, see the code in `convert` function)
recovered = token[:strings.IndexByte(token, '>')+1]
}
placeholder, ok := hcd.tokenPlaceholderMap[token]
if !ok {
placeholder = hcd.nextPlaceholder()
if placeholder != 0 {
hcd.tokenPlaceholderMap[token] = placeholder
hcd.placeholderTokenMap[placeholder] = token
hcd.placeholderTokenMap[placeholder] = recovered
}
}
return placeholder
@ -160,44 +232,42 @@ func (hcd *highlightCodeDiff) convertToPlaceholders(htmlContent template.HTML) s
var tagStack []string
res := strings.Builder{}
firstRunForLineTags := hcd.lineWrapperTags == nil
htmlCode := string(htmlContent)
var beforeToken, token string
var valid bool
htmlCode := string(htmlContent)
// the standard chroma highlight HTML is "<span class="line [hl]"><span class="cl"> ... </span></span>"
for {
beforeToken, token, htmlCode, valid = extractHTMLToken(htmlCode)
beforeToken, token, htmlCode, valid = extractDiffToken(htmlCode)
if !valid || token == "" {
break
}
// write the content before the token into result string, and consume the token in the string
res.WriteString(beforeToken)
// the standard chroma highlight HTML is `<span class="line [hl]"><span class="cl"> ... </span></span>`
// the line wrapper tags should be removed before diff
if strings.HasPrefix(token, `<span class="line`) || strings.HasPrefix(token, `<span class="cl"`) {
if firstRunForLineTags {
// if this is the first run for converting, save the line wrapper tags for later use, they should be added back
hcd.lineWrapperTags = append(hcd.lineWrapperTags, token)
}
htmlCode = strings.TrimSuffix(htmlCode, "</span>")
continue
}
var tokenInMap string
if strings.HasSuffix(token, "</") { // for closing tag
if strings.HasPrefix(token, "</") { // for closing tag
if len(tagStack) == 0 {
break // invalid diff result, no opening tag but see closing tag
continue // no opening tag but see closing tag, skip it
}
// make sure the closing tag in map is related to the open tag, to make the diff algorithm can match the opening/closing tags
// the closing tag will be recorded in the map by key "</span><!-- <span the-opening> -->" for "<span the-opening>"
tokenInMap = token + "<!-- " + tagStack[len(tagStack)-1] + "-->"
tagStack = tagStack[:len(tagStack)-1]
} else if token[0] == '<' { // for opening tag
tokenInMap = token
tagStack = append(tagStack, token)
} else if token[0] == '&' { // for html entity
} else if token[0] == '<' {
if token[1] == '<' {
// full tag `<<span>content</span>>`, recover to `<span>content</span>`
tokenInMap = token
} else {
// opening tag
tokenInMap = token
tagStack = append(tagStack, token)
}
} else if token[0] == '&' { // for HTML entity
tokenInMap = token
} // else: impossible
@ -210,8 +280,13 @@ func (hcd *highlightCodeDiff) convertToPlaceholders(htmlContent template.HTML) s
// unfortunately, all private use runes has been exhausted, no more placeholder could be used, no more converting
// usually, the exhausting won't occur in real cases, the magnitude of used placeholders is not larger than that of the CSS classes outputted by chroma.
hcd.placeholderOverflowCount++
if strings.HasPrefix(token, "<<") {
pos1 := strings.IndexByte(token, '>')
pos2 := strings.LastIndexByte(token, '<')
res.WriteString(token[pos1+1 : pos2]) // recover to `content` from "<<span>content</span>>"
}
if strings.HasPrefix(token, "&") {
// when the token is a html entity, something must be outputted even if there is no placeholder.
// when the token is an HTML entity, something must be outputted even if there is no placeholder.
res.WriteRune(0xFFFD) // replacement character TODO: how to handle this case more gracefully?
res.WriteString(token[1:]) // still output the entity code part, otherwise there will be no diff result.
}
@ -223,43 +298,99 @@ func (hcd *highlightCodeDiff) convertToPlaceholders(htmlContent template.HTML) s
return res.String()
}
// recoverOneRune tries to recover one rune
// * if the rune is a placeholder, it will be recovered to the corresponding content
// * otherwise it will be returned as is
func (hcd *highlightCodeDiff) recoverOneRune(buf []byte) (r rune, runeLen int, isSingleTag bool, recovered string) {
r, runeLen = utf8.DecodeRune(buf)
token := hcd.placeholderTokenMap[r]
if token == "" {
return r, runeLen, false, "" // rune itself, not a placeholder
} else if token[0] == '<' {
if token[1] == '<' {
return 0, runeLen, false, token[1 : len(token)-1] // full tag `<<span>content</span>>`, recover to `<span>content</span>`
}
return r, runeLen, true, token // single tag
}
return 0, runeLen, false, token // HTML entity
}
func (hcd *highlightCodeDiff) recoverOneDiff(str string) template.HTML {
sb := strings.Builder{}
var tagStack []string
var diffCodeOpenTag string
diffCodeCloseTag := hcd.placeholderTokenMap[hcd.diffCodeClose]
strBytes := util.UnsafeStringToBytes(str)
for _, r := range str {
token, ok := hcd.placeholderTokenMap[r]
if !ok || token == "" {
sb.WriteRune(r) // if the rune is not a placeholder, write it as it is
continue
}
var tokenToRecover string
if strings.HasPrefix(token, "</") { // for closing tag
// only get the tag itself, ignore the trailing comment (for how the comment is generated, see the code in `convert` function)
tokenToRecover = token[:strings.IndexByte(token, '>')+1]
if len(tagStack) == 0 {
continue // if no opening tag in stack yet, skip the closing tag
// this loop is slightly longer than expected, for performance consideration
for idx := 0; idx < len(strBytes); {
// take a look at the next rune
r, runeLen, isSingleTag, recovered := hcd.recoverOneRune(strBytes[idx:])
idx += runeLen
// loop section 1: if it isn't a single tag, then try to find the following runes until the next single tag, and recover them together
if !isSingleTag {
if diffCodeOpenTag != "" {
// start the "added/removed diff tag" if the current token is in the diff part
sb.WriteString(diffCodeOpenTag)
}
tagStack = tagStack[:len(tagStack)-1]
} else if token[0] == '<' { // for opening tag
tokenToRecover = token
tagStack = append(tagStack, token)
} else if token[0] == '&' { // for html entity
tokenToRecover = token
} // else: impossible
sb.WriteString(tokenToRecover)
if recovered != "" {
sb.WriteString(recovered)
} else {
sb.WriteRune(r)
}
// inner loop to recover following runes until the next single tag
for idx < len(strBytes) {
r, runeLen, isSingleTag, recovered = hcd.recoverOneRune(strBytes[idx:])
idx += runeLen
if isSingleTag {
break
}
if recovered != "" {
sb.WriteString(recovered)
} else {
sb.WriteRune(r)
}
}
if diffCodeOpenTag != "" {
// end the "added/removed diff tag" if the current token is in the diff part
sb.WriteString(diffCodeCloseTag)
}
}
if !isSingleTag {
break // the inner loop has already consumed all remaining runes, no more single tag found
}
// loop section 2: for opening/closing HTML tags
placeholder := r
if recovered[1] != '/' { // opening tag
if placeholder == hcd.diffCodeAddedOpen || placeholder == hcd.diffCodeRemovedOpen {
diffCodeOpenTag = recovered
recovered = ""
} else {
tagStack = append(tagStack, recovered)
}
} else { // closing tag
if placeholder == hcd.diffCodeClose {
diffCodeOpenTag = "" // the highlighted diff is closed, no more diff
recovered = ""
} else if len(tagStack) != 0 {
tagStack = tagStack[:len(tagStack)-1]
} else {
recovered = ""
}
}
sb.WriteString(recovered)
}
if len(tagStack) > 0 {
// close all opening tags
for i := len(tagStack) - 1; i >= 0; i-- {
tagToClose := tagStack[i]
// get the closing tag "</span>" from "<span class=...>" or "<span>"
pos := strings.IndexAny(tagToClose, " >")
if pos != -1 {
sb.WriteString("</" + tagToClose[1:pos] + ">")
} // else: impossible. every tag was pushed into the stack by the code above and is valid HTML opening tag
}
// close all opening tags
for i := len(tagStack) - 1; i >= 0; i-- {
tagToClose := tagStack[i]
// get the closing tag "</span>" from "<span class=...>" or "<span>"
pos := strings.IndexAny(tagToClose, " >")
// pos must be positive, because the tags were pushed by us
sb.WriteString("</" + tagToClose[1:pos] + ">")
}
return template.HTML(sb.String())
}

View File

@ -9,28 +9,62 @@ import (
"strings"
"testing"
"code.gitea.io/gitea/modules/highlight"
"github.com/stretchr/testify/assert"
)
func TestDiffWithHighlight(t *testing.T) {
t.Run("DiffLineAddDel", func(t *testing.T) {
func BenchmarkHighlightDiff(b *testing.B) {
for b.Loop() {
// still fast enough: BenchmarkHighlightDiff-12 1000000 1027 ns/op
// TODO: the real bottleneck is that "diffLineWithHighlight" is called twice when rendering "added" and "removed" lines by the caller
// Ideally the caller should cache the diff result, and then use the diff result to render "added" and "removed" lines separately
hcd := newHighlightCodeDiff()
codeA := template.HTML(`x <span class="k">foo</span> y`)
codeB := template.HTML(`x <span class="k">bar</span> y`)
outDel := hcd.diffLineWithHighlight(DiffLineDel, codeA, codeB)
assert.Equal(t, `x <span class="k"><span class="removed-code">foo</span></span> y`, string(outDel))
outAdd := hcd.diffLineWithHighlight(DiffLineAdd, codeA, codeB)
assert.Equal(t, `x <span class="k"><span class="added-code">bar</span></span> y`, string(outAdd))
hcd.diffLineWithHighlight(DiffLineDel, codeA, codeB)
}
}
func TestDiffWithHighlight(t *testing.T) {
t.Run("DiffLineAddDel", func(t *testing.T) {
t.Run("WithDiffTags", func(t *testing.T) {
hcd := newHighlightCodeDiff()
codeA := template.HTML(`x <span class="k">foo</span> y`)
codeB := template.HTML(`x <span class="k">bar</span> y`)
outDel := hcd.diffLineWithHighlight(DiffLineDel, codeA, codeB)
assert.Equal(t, `x <span class="removed-code"><span class="k">foo</span></span> y`, string(outDel))
outAdd := hcd.diffLineWithHighlight(DiffLineAdd, codeA, codeB)
assert.Equal(t, `x <span class="added-code"><span class="k">bar</span></span> y`, string(outAdd))
})
t.Run("NoRedundantTags", func(t *testing.T) {
// the equal parts only contain spaces, in this case, don't use "added/removed" tags
// because the diff lines already have a background color to indicate the change
hcd := newHighlightCodeDiff()
codeA := template.HTML("<span> </span> \t<span>foo</span> ")
codeB := template.HTML(" <span>bar</span> \n")
outDel := hcd.diffLineWithHighlight(DiffLineDel, codeA, codeB)
assert.Equal(t, string(codeA), string(outDel))
outAdd := hcd.diffLineWithHighlight(DiffLineAdd, codeA, codeB)
assert.Equal(t, string(codeB), string(outAdd))
})
})
t.Run("CleanUp", func(t *testing.T) {
hcd := newHighlightCodeDiff()
codeA := template.HTML(`<span class="cm">this is a comment</span>`)
codeB := template.HTML(`<span class="cm">this is updated comment</span>`)
codeA := template.HTML(` <span class="cm">this is a comment</span>`)
codeB := template.HTML(` <span class="cm">this is updated comment</span>`)
outDel := hcd.diffLineWithHighlight(DiffLineDel, codeA, codeB)
assert.Equal(t, `<span class="cm">this is <span class="removed-code">a</span> comment</span>`, string(outDel))
assert.Equal(t, ` <span class="cm">this is <span class="removed-code">a</span> comment</span>`, string(outDel))
outAdd := hcd.diffLineWithHighlight(DiffLineAdd, codeA, codeB)
assert.Equal(t, `<span class="cm">this is <span class="added-code">updated</span> comment</span>`, string(outAdd))
assert.Equal(t, ` <span class="cm">this is <span class="added-code">updated</span> comment</span>`, string(outAdd))
codeA = `<span class="line"><span>line1</span></span>` + "\n" + `<span class="cl"><span>line2</span></span>`
codeB = `<span class="cl"><span>line1</span></span>` + "\n" + `<span class="line"><span>line!</span></span>`
outDel = hcd.diffLineWithHighlight(DiffLineDel, codeA, codeB)
assert.Equal(t, `<span>line1</span>`+"\n"+`<span class="removed-code"><span>line2</span></span>`, string(outDel))
outAdd = hcd.diffLineWithHighlight(DiffLineAdd, codeA, codeB)
assert.Equal(t, `<span>line1</span>`+"\n"+`<span><span class="added-code">line!</span></span>`, string(outAdd))
})
t.Run("OpenCloseTags", func(t *testing.T) {
@ -40,6 +74,55 @@ func TestDiffWithHighlight(t *testing.T) {
assert.Equal(t, "<span></span>", string(hcd.recoverOneDiff("O")))
assert.Empty(t, string(hcd.recoverOneDiff("C")))
})
t.Run("ComplexDiff1", func(t *testing.T) {
oldCode, _ := highlight.RenderCodeFast("a.go", "Go", `xxx || yyy`)
newCode, _ := highlight.RenderCodeFast("a.go", "Go", `bot&xxx || bot&yyy`)
hcd := newHighlightCodeDiff()
out := hcd.diffLineWithHighlight(DiffLineAdd, oldCode, newCode)
assert.Equal(t, strings.ReplaceAll(`
<span class="added-code"><span class="nx">bot</span></span><span class="o"><span class="added-code">&amp;</span></span>
<span class="nx">xxx</span><span class="w"> </span><span class="o">||</span><span class="w"> </span>
<span class="added-code"><span class="nx">bot</span></span><span class="o"><span class="added-code">&amp;</span></span>
<span class="nx">yyy</span>`, "\n", ""), string(out))
})
forceTokenAsPlaceholder := func(hcd *highlightCodeDiff, r rune, token string) rune {
// for testing purpose only
hcd.tokenPlaceholderMap[token] = r
hcd.placeholderTokenMap[r] = token
return r
}
t.Run("ComplexDiff2", func(t *testing.T) {
// When running "diffLineWithHighlight", the newly inserted "added-code", and "removed-code" tags may break the original layout.
// The newly inserted tags can appear in any position, because the "diff" algorithm can make outputs like:
// * Equal: <span>
// * Insert: xx</span><span>yy
// * Equal: zz</span>
// Then the newly inserted tags will make this output, the tags mismatch.
// * <span> <added>xx</span><span>yy</added> zz</span>
// So we need to fix it to:
// * <span> <added>xx</added></span> <span><added>yy</added> zz</span>
hcd := newHighlightCodeDiff()
hcd.diffCodeAddedOpen = forceTokenAsPlaceholder(hcd, '[', "<add>")
hcd.diffCodeClose = forceTokenAsPlaceholder(hcd, ']', "</add>")
forceTokenAsPlaceholder(hcd, '{', "<T>")
forceTokenAsPlaceholder(hcd, '}', "</T>")
assert.Equal(t, `aa<T>xx<add>yy</add>zz</T>bb`, string(hcd.recoverOneDiff("aa{xx[yy]zz}bb")))
assert.Equal(t, `aa<add>xx</add><T><add>yy</add></T><add>zz</add>bb`, string(hcd.recoverOneDiff("aa[xx{yy}zz]bb")))
assert.Equal(t, `aa<T>xx<add>yy</add></T><add>zz</add>bb`, string(hcd.recoverOneDiff("aa{xx[yy}zz]bb")))
assert.Equal(t, `aa<add>xx</add><T><add>yy</add>zz</T>bb`, string(hcd.recoverOneDiff("aa[xx{yy]zz}bb")))
assert.Equal(t, `aa<add>xx</add><T><add>yy</add><add>zz</add></T><add>bb</add>cc`, string(hcd.recoverOneDiff("aa[xx{yy][zz}bb]cc")))
// And do a simple test for "diffCodeRemovedOpen", it shares the same logic as "diffCodeAddedOpen"
hcd = newHighlightCodeDiff()
hcd.diffCodeRemovedOpen = forceTokenAsPlaceholder(hcd, '[', "<del>")
hcd.diffCodeClose = forceTokenAsPlaceholder(hcd, ']', "</del>")
forceTokenAsPlaceholder(hcd, '{', "<T>")
forceTokenAsPlaceholder(hcd, '}', "</T>")
assert.Equal(t, `aa<del>xx</del><T><del>yy</del><del>zz</del></T><del>bb</del>cc`, string(hcd.recoverOneDiff("aa[xx{yy][zz}bb]cc")))
})
}
func TestDiffWithHighlightPlaceholder(t *testing.T) {
@ -64,6 +147,11 @@ func TestDiffWithHighlightPlaceholderExhausted(t *testing.T) {
assert.Equal(t, placeHolderAmp+"lt;", string(output))
output = hcd.diffLineWithHighlight(DiffLineAdd, `<span class="k">&lt;</span>`, `<span class="k">&gt;</span>`)
assert.Equal(t, placeHolderAmp+"gt;", string(output))
output = hcd.diffLineWithHighlight(DiffLineDel, `<span class="k">foo</span>`, `<span class="k">bar</span>`)
assert.Equal(t, "foo", string(output))
output = hcd.diffLineWithHighlight(DiffLineAdd, `<span class="k">foo</span>`, `<span class="k">bar</span>`)
assert.Equal(t, "bar", string(output))
}
func TestDiffWithHighlightTagMatch(t *testing.T) {

View File

@ -54,12 +54,12 @@ func TestCodebaseDownloadRepo(t *testing.T) {
assertMilestonesEqual(t, []*base.Milestone{
{
Title: "Milestone1",
Deadline: timePtr(time.Date(2021, time.September, 16, 0, 0, 0, 0, time.UTC)),
Deadline: new(time.Date(2021, time.September, 16, 0, 0, 0, 0, time.UTC)),
},
{
Title: "Milestone2",
Deadline: timePtr(time.Date(2021, time.September, 17, 0, 0, 0, 0, time.UTC)),
Closed: timePtr(time.Date(2021, time.September, 17, 0, 0, 0, 0, time.UTC)),
Deadline: new(time.Date(2021, time.September, 17, 0, 0, 0, 0, time.UTC)),
Closed: new(time.Date(2021, time.September, 17, 0, 0, 0, 0, time.UTC)),
State: "closed",
},
}, milestones)

View File

@ -14,7 +14,6 @@ import (
"code.gitea.io/gitea/modules/log"
base "code.gitea.io/gitea/modules/migration"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/codecommit"
@ -87,7 +86,7 @@ type CodeCommitDownloader struct {
// GetRepoInfo returns a repository information
func (c *CodeCommitDownloader) GetRepoInfo(ctx context.Context) (*base.Repository, error) {
output, err := c.codeCommitClient.GetRepository(ctx, &codecommit.GetRepositoryInput{
RepositoryName: util.ToPointer(c.repoName),
RepositoryName: new(c.repoName),
})
if err != nil {
return nil, err
@ -119,7 +118,7 @@ func (c *CodeCommitDownloader) GetComments(ctx context.Context, commentable base
for {
resp, err := c.codeCommitClient.GetCommentsForPullRequest(ctx, &codecommit.GetCommentsForPullRequestInput{
NextToken: nextToken,
PullRequestId: util.ToPointer(strconv.FormatInt(commentable.GetForeignIndex(), 10)),
PullRequestId: new(strconv.FormatInt(commentable.GetForeignIndex(), 10)),
})
if err != nil {
return nil, false, err
@ -161,7 +160,7 @@ func (c *CodeCommitDownloader) GetPullRequests(ctx context.Context, page, perPag
prs := make([]*base.PullRequest, 0, len(batch))
for _, id := range batch {
output, err := c.codeCommitClient.GetPullRequest(ctx, &codecommit.GetPullRequestInput{
PullRequestId: util.ToPointer(id),
PullRequestId: new(id),
})
if err != nil {
return nil, false, err
@ -241,7 +240,7 @@ func (c *CodeCommitDownloader) getAllPullRequestIDs(ctx context.Context) ([]stri
for {
output, err := c.codeCommitClient.ListPullRequests(ctx, &codecommit.ListPullRequestsInput{
RepositoryName: util.ToPointer(c.repoName),
RepositoryName: new(c.repoName),
NextToken: nextToken,
})
if err != nil {

View File

@ -345,25 +345,43 @@ func (g *GiteaDownloader) GetReleases(ctx context.Context) ([]*base.Release, err
return releases, nil
}
func (g *GiteaDownloader) getIssueReactions(index int64) ([]*base.Reaction, error) {
var reactions []*base.Reaction
func (g *GiteaDownloader) getIssueReactions(ctx context.Context, index int64) ([]*base.Reaction, error) {
if err := g.client.CheckServerVersionConstraint(">=1.11"); err != nil {
log.Info("GiteaDownloader: instance to old, skip getIssueReactions")
return reactions, nil
}
rl, _, err := g.client.GetIssueReactions(g.repoOwner, g.repoName, index)
if err != nil {
return nil, err
return nil, nil
}
for _, reaction := range rl {
reactions = append(reactions, &base.Reaction{
UserID: reaction.User.ID,
UserName: reaction.User.UserName,
Content: reaction.Reaction,
})
allReactions := make([]*base.Reaction, 0, g.maxPerPage)
for i := 1; ; i++ {
// make sure gitea can shutdown gracefully
select {
case <-ctx.Done():
return nil, nil
default:
}
reactions, _, err := g.client.ListIssueReactions(g.repoOwner, g.repoName, index, gitea_sdk.ListIssueReactionsOptions{ListOptions: gitea_sdk.ListOptions{
PageSize: g.maxPerPage,
Page: i,
}})
if err != nil {
return nil, err
}
for _, reaction := range reactions {
allReactions = append(allReactions, &base.Reaction{
UserID: reaction.User.ID,
UserName: reaction.User.UserName,
Content: reaction.Reaction,
})
}
if !g.pagination || len(reactions) < g.maxPerPage {
break
}
}
return reactions, nil
return allReactions, nil
}
func (g *GiteaDownloader) getCommentReactions(commentID int64) ([]*base.Reaction, error) {
@ -388,7 +406,7 @@ func (g *GiteaDownloader) getCommentReactions(commentID int64) ([]*base.Reaction
}
// GetIssues returns issues according start and limit
func (g *GiteaDownloader) GetIssues(_ context.Context, page, perPage int) ([]*base.Issue, bool, error) {
func (g *GiteaDownloader) GetIssues(ctx context.Context, page, perPage int) ([]*base.Issue, bool, error) {
if perPage > g.maxPerPage {
perPage = g.maxPerPage
}
@ -413,7 +431,7 @@ func (g *GiteaDownloader) GetIssues(_ context.Context, page, perPage int) ([]*ba
milestone = issue.Milestone.Title
}
reactions, err := g.getIssueReactions(issue.Index)
reactions, err := g.getIssueReactions(ctx, issue.Index)
if err != nil {
WarnAndNotice("Unable to load reactions during migrating issue #%d in %s. Error: %v", issue.Index, g, err)
}
@ -497,7 +515,7 @@ func (g *GiteaDownloader) GetComments(ctx context.Context, commentable base.Comm
}
// GetPullRequests returns pull requests according page and perPage
func (g *GiteaDownloader) GetPullRequests(_ context.Context, page, perPage int) ([]*base.PullRequest, bool, error) {
func (g *GiteaDownloader) GetPullRequests(ctx context.Context, page, perPage int) ([]*base.PullRequest, bool, error) {
if perPage > g.maxPerPage {
perPage = g.maxPerPage
}
@ -546,7 +564,7 @@ func (g *GiteaDownloader) GetPullRequests(_ context.Context, page, perPage int)
mergeCommitSHA = *pr.MergedCommitID
}
reactions, err := g.getIssueReactions(pr.Index)
reactions, err := g.getIssueReactions(ctx, pr.Index)
if err != nil {
WarnAndNotice("Unable to load reactions during migrating pull #%d in %s. Error: %v", pr.Index, g, err)
}

View File

@ -86,16 +86,16 @@ func TestGiteaDownloadRepo(t *testing.T) {
{
Title: "V2 Finalize",
Created: time.Unix(0, 0),
Deadline: timePtr(time.Unix(1599263999, 0)),
Updated: timePtr(time.Unix(0, 0)),
Deadline: new(time.Unix(1599263999, 0)),
Updated: new(time.Unix(0, 0)),
State: "open",
},
{
Title: "V1",
Description: "Generate Content",
Created: time.Unix(0, 0),
Updated: timePtr(time.Unix(0, 0)),
Closed: timePtr(time.Unix(1598985406, 0)),
Updated: new(time.Unix(0, 0)),
Closed: new(time.Unix(1598985406, 0)),
State: "closed",
},
}, milestones)
@ -171,7 +171,7 @@ func TestGiteaDownloadRepo(t *testing.T) {
Content: "laugh",
},
},
Closed: timePtr(time.Date(2020, 9, 1, 15, 49, 34, 0, time.UTC)),
Closed: new(time.Date(2020, 9, 1, 15, 49, 34, 0, time.UTC)),
},
{
Number: 2,
@ -190,7 +190,7 @@ func TestGiteaDownloadRepo(t *testing.T) {
Color: "d4c5f9",
Description: "",
}},
Closed: timePtr(time.Unix(1598969497, 0)),
Closed: new(time.Unix(1598969497, 0)),
},
}, issues)
@ -237,7 +237,7 @@ func TestGiteaDownloadRepo(t *testing.T) {
IsLocked: false,
Created: time.Unix(1598982759, 0),
Updated: time.Unix(1599023425, 0),
Closed: timePtr(time.Unix(1598982934, 0)),
Closed: new(time.Unix(1598982934, 0)),
Assignees: []string{"techknowlogick"},
Base: base.PullRequestBranch{
CloneURL: "",
@ -254,7 +254,7 @@ func TestGiteaDownloadRepo(t *testing.T) {
OwnerName: "6543-forks",
},
Merged: true,
MergedTime: timePtr(time.Unix(1598982934, 0)),
MergedTime: new(time.Unix(1598982934, 0)),
MergeCommitSHA: "827aa28a907853e5ddfa40c8f9bc52471a2685fd",
PatchURL: "https://gitea.com/gitea/test_repo/pulls/12.patch",
}, prs[1])

View File

@ -364,11 +364,12 @@ func (g *GiteaLocalUploader) CreateReleases(ctx context.Context, releases ...*ba
// SyncTags syncs releases with tags in the database
func (g *GiteaLocalUploader) SyncTags(ctx context.Context) error {
return repo_module.SyncReleasesWithTags(ctx, g.repo, g.gitRepo)
_, err := repo_module.SyncReleasesWithTags(ctx, g.repo, g.gitRepo)
return err
}
func (g *GiteaLocalUploader) SyncBranches(ctx context.Context) error {
_, err := repo_module.SyncRepoBranchesWithRepo(ctx, g.repo, g.gitRepo, g.doer.ID)
_, _, err := repo_module.SyncRepoBranchesWithRepo(ctx, g.repo, g.gitRepo, g.doer.ID)
return err
}

View File

@ -47,19 +47,19 @@ func TestGitHubDownloadRepo(t *testing.T) {
{
Title: "1.0.0",
Description: "Milestone 1.0.0",
Deadline: timePtr(time.Date(2019, 11, 11, 8, 0, 0, 0, time.UTC)),
Deadline: new(time.Date(2019, 11, 11, 8, 0, 0, 0, time.UTC)),
Created: time.Date(2019, 11, 12, 19, 37, 8, 0, time.UTC),
Updated: timePtr(time.Date(2019, 11, 12, 21, 56, 17, 0, time.UTC)),
Closed: timePtr(time.Date(2019, 11, 12, 19, 45, 49, 0, time.UTC)),
Updated: new(time.Date(2019, 11, 12, 21, 56, 17, 0, time.UTC)),
Closed: new(time.Date(2019, 11, 12, 19, 45, 49, 0, time.UTC)),
State: "closed",
},
{
Title: "1.1.0",
Description: "Milestone 1.1.0",
Deadline: timePtr(time.Date(2019, 11, 12, 8, 0, 0, 0, time.UTC)),
Deadline: new(time.Date(2019, 11, 12, 8, 0, 0, 0, time.UTC)),
Created: time.Date(2019, 11, 12, 19, 37, 25, 0, time.UTC),
Updated: timePtr(time.Date(2019, 11, 12, 21, 39, 27, 0, time.UTC)),
Closed: timePtr(time.Date(2019, 11, 12, 19, 45, 46, 0, time.UTC)),
Updated: new(time.Date(2019, 11, 12, 21, 39, 27, 0, time.UTC)),
Closed: new(time.Date(2019, 11, 12, 19, 45, 46, 0, time.UTC)),
State: "closed",
},
}, milestones)
@ -163,7 +163,7 @@ func TestGitHubDownloadRepo(t *testing.T) {
Content: "+1",
},
},
Closed: timePtr(time.Date(2019, 11, 12, 20, 22, 22, 0, time.UTC)),
Closed: new(time.Date(2019, 11, 12, 20, 22, 22, 0, time.UTC)),
},
{
Number: 2,
@ -214,7 +214,7 @@ func TestGitHubDownloadRepo(t *testing.T) {
Content: "+1",
},
},
Closed: timePtr(time.Date(2019, 11, 12, 21, 1, 31, 0, time.UTC)),
Closed: new(time.Date(2019, 11, 12, 21, 1, 31, 0, time.UTC)),
},
}, issues)
@ -284,9 +284,9 @@ func TestGitHubDownloadRepo(t *testing.T) {
OwnerName: "go-gitea",
RepoName: "test_repo",
},
Closed: timePtr(time.Date(2019, 11, 12, 21, 39, 27, 0, time.UTC)),
Closed: new(time.Date(2019, 11, 12, 21, 39, 27, 0, time.UTC)),
Merged: true,
MergedTime: timePtr(time.Date(2019, 11, 12, 21, 39, 27, 0, time.UTC)),
MergedTime: new(time.Date(2019, 11, 12, 21, 39, 27, 0, time.UTC)),
MergeCommitSHA: "f32b0a9dfd09a60f616f29158f772cedd89942d2",
ForeignIndex: 3,
},

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