mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-01 03:04:48 +02:00
Merge remote-tracking branch 'origin/main' into update-eslint-plugins
* origin/main: (39 commits) [skip ci] Updated translations via Crowdin fix(mssql): expand legacy issue and comment long-text columns (#38120) fix(deps): update npm dependencies (#38137) ci: trigger giteabot maintenance on main pushes (#38135) [skip ci] Updated translations via Crowdin chore(deps): update module github.com/go-swagger/go-swagger to v0.34.1 (#38122) fix(pull): preserve squash message trailers and additional commit messages (#37954) fix(packages): validate module version in goproxy ParsePackage (#38104) feat: add raw diff/patch endpoint for repository comparisons (#37632) chore(deps): update pnpm to v11.5.3 (#38133) chore(deps): update action dependencies (#38121) docs: update missed gov docs update (#38131) fix(deps): update npm dependencies (#38123) chore(deps): update dependency djlint to v1.39.0 (#38124) feat(actions): show run status on browser tab favicon (#38071) chore: center info message for unsupported jupyter notebook versions (#38114) chore(actions): Add icon for status filter (#38082) feat(org): add team visibility so org members can discover teams (#37680) chore(deps): bump dockerfile to use Alpine 3.24 (#38077) chore: fix form string abuse (#38106) ... # Conflicts: # package.json # pnpm-lock.yaml
This commit is contained in:
@@ -1,113 +0,0 @@
|
||||
name: AgentScan
|
||||
|
||||
on:
|
||||
# jobs only use pinned actions and never checkout code
|
||||
pull_request_target: # zizmor: ignore[dangerous-triggers]
|
||||
types: [opened, reopened, synchronize, edited]
|
||||
|
||||
concurrency:
|
||||
group: agent-scan-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
agentscan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: AgentScan
|
||||
id: agentscan
|
||||
uses: MatteoGabriele/agentscan-action@0a0c88109b5153dff2805f969f5060441efb7b65
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
skip-members: "dependabot[bot],renovate[bot], giteabot (backports)"
|
||||
agent-scan-comment: false
|
||||
|
||||
- name: Handle flagged PR
|
||||
if: contains(fromJSON('["automation","mixed"]'), steps.agentscan.outputs.classification) || steps.agentscan.outputs.community-flagged == 'true'
|
||||
env:
|
||||
CLASSIFICATION: ${{ steps.agentscan.outputs.classification }}
|
||||
COMMUNITY_FLAGGED: ${{ steps.agentscan.outputs.community-flagged }}
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
|
||||
with:
|
||||
script: |
|
||||
const core = require('@actions/core');
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
const classification = process.env.CLASSIFICATION;
|
||||
const communityFlagged = process.env.COMMUNITY_FLAGGED === 'true';
|
||||
const shouldClose = classification === 'automation' || communityFlagged;
|
||||
|
||||
const issue = context.payload.pull_request;
|
||||
const labels = issue.labels?.map(l => l.name) || [];
|
||||
|
||||
if (!labels.includes('possible bot')) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
labels: ['possible bot'],
|
||||
});
|
||||
}
|
||||
|
||||
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const alreadyCommented = comments.some(c => c.user.type === 'Bot' && c.body.includes('AI Contribution Policy'));
|
||||
|
||||
if (!alreadyCommented) {
|
||||
const closingNote = shouldClose
|
||||
? "We're closing this for now as the account looks automated. If we got that wrong, please just reopen the PR and we'll take another look."
|
||||
: 'If this was flagged in error, we apologise! 😳 Just let us know. 🙏';
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body: [
|
||||
"We've flagged this pull request as potentially AI-assisted.",
|
||||
'',
|
||||
'Gitea welcomes the thoughtful use of AI tools, but contributors must use them responsibly and clearly disclose any assistance. Please follow the AI Contribution Policy in `CONTRIBUTING.md` and update this PR accordingly:',
|
||||
'',
|
||||
'Maintainers may close PRs that do not disclose AI assistance, appear to be low-quality AI-generated content, or where the contributor cannot explain the changes.',
|
||||
'',
|
||||
'See: https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md#ai-contribution-policy',
|
||||
'',
|
||||
closingNote,
|
||||
].join('\n'),
|
||||
});
|
||||
} else {
|
||||
core.info('Possible-bot comment already exists - skipping comment.');
|
||||
}
|
||||
|
||||
if (shouldClose && issue.state === 'open' && !alreadyCommented) {
|
||||
await github.rest.pulls.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber,
|
||||
state: 'closed',
|
||||
title: '🚨 unwelcome pr from bot 🚨',
|
||||
});
|
||||
}
|
||||
|
||||
const actionTaken = [
|
||||
'Added `possible bot` label',
|
||||
alreadyCommented ? null : 'posted policy comment',
|
||||
shouldClose && !alreadyCommented ? 'closed PR' : null,
|
||||
].filter(Boolean).join(', ');
|
||||
|
||||
core.summary
|
||||
.addHeading('AgentScan: Possible Bot Flag', 2)
|
||||
.addTable([
|
||||
[{ data: 'Property', header: true }, { data: 'Value', header: true }],
|
||||
['Pull Request', `#${prNumber}`],
|
||||
['Classification', classification],
|
||||
['Community flagged', String(communityFlagged)],
|
||||
['Action', actionTaken || 'No action (already handled)'],
|
||||
])
|
||||
.write();
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: renovatebot/github-action@693b9ef15eec82123529a37c782242f091365961 # v46.1.14
|
||||
- uses: renovatebot/github-action@8217b3fc286df088d7c27f3255fe8414463bc0fd # v46.1.15
|
||||
with:
|
||||
renovate-version: ${{ env.RENOVATE_VERSION }}
|
||||
configurationFile: renovate.json5
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
name: giteabot
|
||||
|
||||
on:
|
||||
# When main advances, rerun merge queue maintenance so the oldest
|
||||
# reviewed/wait-merge PR can be updated against the new base promptly.
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
# pull_request_target gives this workflow access to GITEABOT_TOKEN on PRs from
|
||||
# forks, which the bot needs to write labels, statuses and comments. Safe here
|
||||
# because the job only runs a pinned action and never checks out PR HEAD.
|
||||
# These PR lifecycle events drive label maintenance, queue maintenance, and
|
||||
# explicit bot actions triggered by relevant label changes.
|
||||
pull_request_target: # zizmor: ignore[dangerous-triggers]
|
||||
types:
|
||||
- opened
|
||||
@@ -13,13 +20,19 @@ on:
|
||||
- closed
|
||||
- review_requested
|
||||
- review_request_removed
|
||||
# Review events keep review-derived state such as lgtm labels and status checks
|
||||
# in sync after approvals, edits, or dismissals.
|
||||
pull_request_review:
|
||||
types:
|
||||
- submitted
|
||||
- edited
|
||||
- dismissed
|
||||
# Periodic maintenance is still useful as a backstop for queue cleanup and
|
||||
# other housekeeping, even though main pushes now trigger it promptly.
|
||||
schedule:
|
||||
- cron: "15 3 * * *"
|
||||
# Allow maintainers to rerun selected checks manually when debugging bot
|
||||
# behavior without waiting for another repository event.
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
checks:
|
||||
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
ports:
|
||||
- "7700:7700"
|
||||
redis:
|
||||
image: redis:latest@sha256:e74c9b933d78e2829583d88f92793f4524752a15ac59c8baff2dd5ed000b7432
|
||||
image: redis:latest@sha256:a505f8b9d8ac3ff7b0848055b4abf1901d6d77606774aa1e38bd37f1197ed2b5
|
||||
options: >- # wait until redis has started
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 5s
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Build frontend on the native platform to avoid QEMU-related issues with nodejs ecosystem
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.26-alpine3.23 AS frontend-build
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.26-alpine3.24 AS frontend-build
|
||||
RUN apk --no-cache add build-base git nodejs pnpm
|
||||
WORKDIR /src
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
@@ -9,7 +9,7 @@ COPY --exclude=.git/ . .
|
||||
RUN make frontend
|
||||
|
||||
# Build backend for each target platform
|
||||
FROM docker.io/library/golang:1.26-alpine3.23 AS build-env
|
||||
FROM docker.io/library/golang:1.26-alpine3.24 AS build-env
|
||||
|
||||
ARG GITEA_VERSION
|
||||
ARG TAGS=""
|
||||
@@ -44,7 +44,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
|
||||
/tmp/local/etc/s6/.s6-svscan/* \
|
||||
/go/src/gitea.dev/gitea
|
||||
|
||||
FROM docker.io/library/alpine:3.23 AS gitea
|
||||
FROM docker.io/library/alpine:3.24 AS gitea
|
||||
|
||||
EXPOSE 22 3000
|
||||
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Build frontend on the native platform to avoid QEMU-related issues with nodejs ecosystem
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.26-alpine3.23 AS frontend-build
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.26-alpine3.24 AS frontend-build
|
||||
RUN apk --no-cache add build-base git nodejs pnpm
|
||||
WORKDIR /src
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
@@ -9,7 +9,7 @@ COPY --exclude=.git/ . .
|
||||
RUN make frontend
|
||||
|
||||
# Build backend for each target platform
|
||||
FROM docker.io/library/golang:1.26-alpine3.23 AS build-env
|
||||
FROM docker.io/library/golang:1.26-alpine3.24 AS build-env
|
||||
|
||||
ARG GITEA_VERSION
|
||||
ARG TAGS=""
|
||||
@@ -39,7 +39,7 @@ COPY docker/rootless /tmp/local
|
||||
RUN chmod 755 /tmp/local/usr/local/bin/* \
|
||||
/go/src/gitea.dev/gitea
|
||||
|
||||
FROM docker.io/library/alpine:3.23 AS gitea-rootless
|
||||
FROM docker.io/library/alpine:3.24 AS gitea-rootless
|
||||
|
||||
EXPOSE 2222 3000
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-che
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 # renovate: datasource=go
|
||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 # renovate: datasource=go
|
||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.8.0 # renovate: datasource=go
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.34.0 # renovate: datasource=go
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.34.1 # renovate: datasource=go
|
||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@v1.9.0 # renovate: datasource=go
|
||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.3.0 # renovate: datasource=go
|
||||
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 # renovate: datasource=go
|
||||
|
||||
Generated
+9
-39
File diff suppressed because one or more lines are too long
@@ -57,8 +57,8 @@ func main() {
|
||||
log.Fatalf("scanning swagger:enum annotations: %v", err)
|
||||
}
|
||||
names := make([]string, 0, len(astEnumMap))
|
||||
for _, n := range astEnumMap {
|
||||
names = append(names, n)
|
||||
for _, ns := range astEnumMap {
|
||||
names = append(names, ns...)
|
||||
}
|
||||
sort.Strings(names)
|
||||
fmt.Fprintf(os.Stderr, "discovered %d swagger:enum types: %s\n", len(names), strings.Join(names, ", "))
|
||||
|
||||
+123
-18
@@ -6,6 +6,7 @@ package openapi3gen
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/modules/json"
|
||||
@@ -25,10 +26,12 @@ var rxDeprecated = regexp.MustCompile(`(?i)(?:^|[\n.;])\s*deprecated\b`)
|
||||
// Gitea-specific post-processing: file-schema fixups, URI formats,
|
||||
// deprecated flags, and shared-enum extraction.
|
||||
//
|
||||
// astEnumMap is a value-set-key → Go-type-name map (built by
|
||||
// ScanSwaggerEnumTypes). If a shared enum in the spec has no entry in the
|
||||
// map, Convert returns an error — no fallback naming.
|
||||
func Convert(swaggerJSON []byte, astEnumMap map[string]string) (*openapi3.T, error) {
|
||||
// astEnumMap is a value-set-key → Go-type-name(s) map (built by
|
||||
// ScanSwaggerEnumTypes). When a value set is shared by multiple Go types,
|
||||
// per-property disambiguation uses the x-go-enum-desc extension. If a shared
|
||||
// enum in the spec has no matching entry, Convert returns an error — no
|
||||
// fallback naming.
|
||||
func Convert(swaggerJSON []byte, astEnumMap map[string][]string) (*openapi3.T, error) {
|
||||
var swagger2 openapi2.T
|
||||
if err := json.Unmarshal(swaggerJSON, &swagger2); err != nil {
|
||||
return nil, fmt.Errorf("parsing swagger 2.0: %w", err)
|
||||
@@ -176,12 +179,24 @@ type enumUsage struct {
|
||||
// If the derived enum name collides with an existing component schema, or
|
||||
// no // swagger:enum annotation matches the value set, generation aborts
|
||||
// with an actionable error — there are no silent fallbacks.
|
||||
func extractSharedEnums(doc *openapi3.T, astEnumMap map[string]string) error {
|
||||
func extractSharedEnums(doc *openapi3.T, astEnumMap map[string][]string) error {
|
||||
if doc.Components == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
enumGroups := map[string][]enumUsage{}
|
||||
type groupKey struct {
|
||||
valueSet string
|
||||
typeName string
|
||||
}
|
||||
enumGroups := map[groupKey][]enumUsage{}
|
||||
groupOrder := []groupKey{} // deterministic iteration
|
||||
|
||||
addUsage := func(key groupKey, u enumUsage) {
|
||||
if _, seen := enumGroups[key]; !seen {
|
||||
groupOrder = append(groupOrder, key)
|
||||
}
|
||||
enumGroups[key] = append(enumGroups[key], u)
|
||||
}
|
||||
|
||||
for schemaName, schemaRef := range doc.Components.Schemas {
|
||||
if schemaRef.Value == nil {
|
||||
@@ -192,24 +207,31 @@ func extractSharedEnums(doc *openapi3.T, astEnumMap map[string]string) error {
|
||||
continue
|
||||
}
|
||||
if len(propRef.Value.Enum) > 1 && propRef.Value.Type.Is("string") {
|
||||
key := EnumKey(propRef.Value.Enum)
|
||||
enumGroups[key] = append(enumGroups[key], enumUsage{schemaName, propName, propRef, false})
|
||||
key := groupKey{
|
||||
valueSet: EnumKey(propRef.Value.Enum),
|
||||
typeName: extractEnumTypeName(propRef.Value, astEnumMap),
|
||||
}
|
||||
addUsage(key, enumUsage{schemaName, propName, propRef, false})
|
||||
}
|
||||
if propRef.Value.Type.Is("array") && propRef.Value.Items != nil &&
|
||||
propRef.Value.Items.Value != nil && propRef.Value.Items.Ref == "" &&
|
||||
len(propRef.Value.Items.Value.Enum) > 1 && propRef.Value.Items.Value.Type.Is("string") {
|
||||
key := EnumKey(propRef.Value.Items.Value.Enum)
|
||||
enumGroups[key] = append(enumGroups[key], enumUsage{schemaName, propName, propRef, true})
|
||||
key := groupKey{
|
||||
valueSet: EnumKey(propRef.Value.Items.Value.Enum),
|
||||
typeName: extractEnumTypeName(propRef.Value.Items.Value, astEnumMap),
|
||||
}
|
||||
addUsage(key, enumUsage{schemaName, propName, propRef, true})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, usages := range enumGroups {
|
||||
for _, key := range groupOrder {
|
||||
usages := enumGroups[key]
|
||||
if len(usages) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
enumName, err := deriveEnumName(key, usages, astEnumMap)
|
||||
enumName, err := deriveEnumName(key.valueSet, key.typeName, usages, astEnumMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -257,12 +279,17 @@ func extractSharedEnums(doc *openapi3.T, astEnumMap map[string]string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// deriveEnumName looks up a shared enum's Go type name from astEnumMap by
|
||||
// value-set key. If no annotation matches, returns an error identifying the
|
||||
// offending properties and the fix.
|
||||
func deriveEnumName(key string, usages []enumUsage, astEnumMap map[string]string) (string, error) {
|
||||
if name, ok := astEnumMap[key]; ok {
|
||||
return name, nil
|
||||
// deriveEnumName looks up a shared enum's Go type name. If typeName is
|
||||
// non-empty (because we recovered it from x-go-enum-desc), it is used
|
||||
// directly. Otherwise the value-set must map to exactly one known type. On
|
||||
// failure, returns an error identifying the offending properties.
|
||||
func deriveEnumName(key, typeName string, usages []enumUsage, astEnumMap map[string][]string) (string, error) {
|
||||
if typeName != "" {
|
||||
return typeName, nil
|
||||
}
|
||||
names := astEnumMap[key]
|
||||
if len(names) == 1 {
|
||||
return names[0], nil
|
||||
}
|
||||
|
||||
props := map[string]bool{}
|
||||
@@ -273,9 +300,87 @@ func deriveEnumName(key string, usages []enumUsage, astEnumMap map[string]string
|
||||
for p := range props {
|
||||
propList = append(propList, p)
|
||||
}
|
||||
if len(names) > 1 {
|
||||
return "", fmt.Errorf(
|
||||
"value-set %q is shared by multiple swagger:enum types %v and could not be disambiguated for properties: %v; "+
|
||||
"ensure go-swagger emits x-go-enum-desc for those properties",
|
||||
key, names, propList,
|
||||
)
|
||||
}
|
||||
return "", fmt.Errorf(
|
||||
"no swagger:enum annotation matches value-set %q used by %d properties: %v; "+
|
||||
"fix by adding a named string type with // swagger:enum to modules/structs or modules/commitstatus",
|
||||
key, len(usages), propList,
|
||||
)
|
||||
}
|
||||
|
||||
// extractEnumTypeName recovers the Go type name a schema's enum came from by
|
||||
// parsing the property's x-go-enum-desc extension. go-swagger emits one line
|
||||
// per value as "<value> <ConstName>[ <free text>]"; the type is the longest
|
||||
// common prefix of the const names, narrowed to the candidate set in
|
||||
// astEnumMap. Returns "" if extraction is inconclusive.
|
||||
func extractEnumTypeName(s *openapi3.Schema, astEnumMap map[string][]string) string {
|
||||
if s == nil || s.Extensions == nil {
|
||||
return ""
|
||||
}
|
||||
raw, ok := s.Extensions["x-go-enum-desc"]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
desc, ok := raw.(string)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
candidates := astEnumMap[EnumKey(s.Enum)]
|
||||
if len(candidates) == 0 {
|
||||
return ""
|
||||
}
|
||||
// Collect the const names (second whitespace-separated field per line).
|
||||
var consts []string
|
||||
for line := range strings.SplitSeq(desc, "\n") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 2 {
|
||||
consts = append(consts, fields[1])
|
||||
}
|
||||
}
|
||||
if len(consts) == 0 {
|
||||
return ""
|
||||
}
|
||||
// A candidate matches when it is a prefix of every const name AND the
|
||||
// first character after the prefix is an uppercase ASCII letter — this
|
||||
// rejects e.g. "Alpha" matching "Alphabet" (suffix "bet" starts lower)
|
||||
// while still accepting both "Alpha" and "AlphaPlus" against "AlphaPlusX"
|
||||
// (both prefixes valid). The most specific (longest) wins; ties return
|
||||
// "" so deriveEnumName surfaces the ambiguity rather than silently
|
||||
// picking a winner.
|
||||
ordered := append([]string(nil), candidates...)
|
||||
sort.Slice(ordered, func(i, j int) bool { return len(ordered[i]) > len(ordered[j]) })
|
||||
var matches []string
|
||||
for _, name := range ordered {
|
||||
ok := true
|
||||
for _, c := range consts {
|
||||
if !strings.HasPrefix(c, name) {
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
suffix := c[len(name):]
|
||||
// Empty suffix means the const name exactly equals the type name — valid exact match.
|
||||
// A non-empty suffix must begin with an uppercase letter to reject incidental
|
||||
// prefix matches (e.g. "Alpha" should not match "Alphabet").
|
||||
if len(suffix) > 0 && (suffix[0] < 'A' || suffix[0] > 'Z') {
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
matches = append(matches, name)
|
||||
}
|
||||
}
|
||||
if len(matches) == 0 {
|
||||
return ""
|
||||
}
|
||||
if len(matches) > 1 && len(matches[0]) == len(matches[1]) {
|
||||
return ""
|
||||
}
|
||||
return matches[0]
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
|
||||
func TestDeriveEnumName_hit(t *testing.T) {
|
||||
key := EnumKey([]any{"red", "green", "blue"})
|
||||
astMap := map[string]string{key: "Color"}
|
||||
astMap := map[string][]string{key: {"Color"}}
|
||||
usages := []enumUsage{{schemaName: "Paint", propName: "color"}}
|
||||
got, err := deriveEnumName(key, usages, astMap)
|
||||
got, err := deriveEnumName(key, "", usages, astMap)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
@@ -26,7 +26,7 @@ func TestDeriveEnumName_hit(t *testing.T) {
|
||||
func TestDeriveEnumName_miss(t *testing.T) {
|
||||
key := EnumKey([]any{"x", "y"})
|
||||
usages := []enumUsage{{schemaName: "Thing", propName: "kind"}}
|
||||
_, err := deriveEnumName(key, usages, map[string]string{})
|
||||
_, err := deriveEnumName(key, "", usages, map[string][]string{})
|
||||
if err == nil {
|
||||
t.Fatal("expected miss error, got nil")
|
||||
}
|
||||
@@ -64,7 +64,7 @@ func TestExtractSharedEnums_usesASTMap(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
astMap := map[string]string{EnumKey([]any{"red", "green", "blue"}): "Color"}
|
||||
astMap := map[string][]string{EnumKey([]any{"red", "green", "blue"}): {"Color"}}
|
||||
if err := extractSharedEnums(doc, astMap); err != nil {
|
||||
t.Fatalf("extractSharedEnums: %v", err)
|
||||
}
|
||||
@@ -139,6 +139,54 @@ func TestFixFileSchemas_recursesIntoNested(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractEnumTypeName_TeamVisibility(t *testing.T) {
|
||||
enum := []any{"public", "limited", "private"}
|
||||
key := EnumKey(enum)
|
||||
astMap := map[string][]string{key: {"UserVisibility", "TeamVisibility"}}
|
||||
schema := &openapi3.Schema{
|
||||
Type: &openapi3.Types{"string"},
|
||||
Enum: enum,
|
||||
Extensions: map[string]any{
|
||||
"x-go-enum-desc": "public TeamVisibilityPublic\nlimited TeamVisibilityLimited\nprivate TeamVisibilityPrivate",
|
||||
},
|
||||
}
|
||||
if got := extractEnumTypeName(schema, astMap); got != "TeamVisibility" {
|
||||
t.Fatalf("got %q, want %q", got, "TeamVisibility")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractEnumTypeName_ambiguousPrefixTie(t *testing.T) {
|
||||
enum := []any{"one", "two"}
|
||||
key := EnumKey(enum)
|
||||
astMap := map[string][]string{key: {"AB", "AC"}}
|
||||
schema := &openapi3.Schema{
|
||||
Type: &openapi3.Types{"string"},
|
||||
Enum: enum,
|
||||
Extensions: map[string]any{
|
||||
"x-go-enum-desc": "one ABOne\ntwo ACTwo",
|
||||
},
|
||||
}
|
||||
if got := extractEnumTypeName(schema, astMap); got != "" {
|
||||
t.Fatalf("got %q, want empty string for ambiguous tie", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractEnumTypeName_rejectsIncidentalPrefix(t *testing.T) {
|
||||
enum := []any{"a", "b"}
|
||||
key := EnumKey(enum)
|
||||
astMap := map[string][]string{key: {"Alpha", "Alphabet"}}
|
||||
schema := &openapi3.Schema{
|
||||
Type: &openapi3.Types{"string"},
|
||||
Enum: enum,
|
||||
Extensions: map[string]any{
|
||||
"x-go-enum-desc": "a AlphabetA\nb AlphabetB",
|
||||
},
|
||||
}
|
||||
if got := extractEnumTypeName(schema, astMap); got != "Alphabet" {
|
||||
t.Fatalf("got %q, want %q", got, "Alphabet")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractSharedEnums_missReturnsError(t *testing.T) {
|
||||
doc := &openapi3.T{
|
||||
Components: &openapi3.Components{
|
||||
@@ -164,7 +212,7 @@ func TestExtractSharedEnums_missReturnsError(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := extractSharedEnums(doc, map[string]string{}); err == nil {
|
||||
if err := extractSharedEnums(doc, map[string][]string{}); err == nil {
|
||||
t.Fatal("expected miss error")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,13 +34,16 @@ func EnumKey(values []any) string {
|
||||
var rxSwaggerEnum = regexp.MustCompile(`swagger:enum\s+(\w+)`)
|
||||
|
||||
// ScanSwaggerEnumTypes walks .go files under each dir and returns a map from
|
||||
// a canonical value-set key (see EnumKey) to the Go type name declared with
|
||||
// // swagger:enum TypeName.
|
||||
// a canonical value-set key (see EnumKey) to the Go type names declared with
|
||||
// // swagger:enum TypeName. Multiple type names per key are allowed (e.g.
|
||||
// distinct enum types that happen to share a value set such as
|
||||
// {public, limited, private}); callers must disambiguate per-usage (typically
|
||||
// by parsing the property's x-go-enum-desc extension to recover the const
|
||||
// type prefix).
|
||||
//
|
||||
// Returns an error on parse failure, on an annotation for a type whose
|
||||
// constants can't be extracted, or on value-set collisions between two
|
||||
// different enum types.
|
||||
func ScanSwaggerEnumTypes(dirs []string) (map[string]string, error) {
|
||||
// Returns an error on parse failure or on an annotation for a type whose
|
||||
// constants can't be extracted.
|
||||
func ScanSwaggerEnumTypes(dirs []string) (map[string][]string, error) {
|
||||
fset := token.NewFileSet()
|
||||
parsed := []*ast.File{}
|
||||
|
||||
@@ -92,17 +95,18 @@ func ScanSwaggerEnumTypes(dirs []string) (map[string]string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
result := map[string]string{}
|
||||
result := map[string][]string{}
|
||||
for typeName := range enumTypes {
|
||||
values, ok := enumValues[typeName]
|
||||
if !ok || len(values) == 0 {
|
||||
return nil, fmt.Errorf("swagger:enum %s has no const block with typed string values", typeName)
|
||||
}
|
||||
key := EnumKey(values)
|
||||
if existing, ok := result[key]; ok && existing != typeName {
|
||||
return nil, fmt.Errorf("swagger:enum value-set collision: %s and %s both use %q", existing, typeName, key)
|
||||
}
|
||||
result[key] = typeName
|
||||
result[key] = append(result[key], typeName)
|
||||
}
|
||||
for key, names := range result {
|
||||
sort.Strings(names)
|
||||
result[key] = names
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -6,10 +6,19 @@ package openapi3gen
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func single(got map[string][]string, key string) string {
|
||||
v := got[key]
|
||||
if len(v) != 1 {
|
||||
return ""
|
||||
}
|
||||
return v[0]
|
||||
}
|
||||
|
||||
func TestEnumKey_sortsAndJoins(t *testing.T) {
|
||||
key := EnumKey([]any{"b", "a", "c"})
|
||||
if key != "a|b|c" {
|
||||
@@ -47,7 +56,7 @@ const (
|
||||
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||
}
|
||||
wantKey := EnumKey([]any{"red", "green", "blue"})
|
||||
if got[wantKey] != "Color" {
|
||||
if single(got, wantKey) != "Color" {
|
||||
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Color")
|
||||
}
|
||||
}
|
||||
@@ -98,13 +107,14 @@ const (
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := ScanSwaggerEnumTypes([]string{dir})
|
||||
if err == nil {
|
||||
t.Fatal("expected collision error, got nil")
|
||||
got, err := ScanSwaggerEnumTypes([]string{dir})
|
||||
if err != nil {
|
||||
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||
}
|
||||
msg := err.Error()
|
||||
if !strings.Contains(msg, "Alpha") || !strings.Contains(msg, "Beta") {
|
||||
t.Fatalf("error %q should mention both Alpha and Beta", msg)
|
||||
key := EnumKey([]any{"x", "y"})
|
||||
names := got[key]
|
||||
if !slices.Equal(names, []string{"Alpha", "Beta"}) {
|
||||
t.Fatalf("map[%q] = %v, want [Alpha Beta]", key, names)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +178,7 @@ type Hue string
|
||||
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||
}
|
||||
wantKey := EnumKey([]any{"a", "b"})
|
||||
if got[wantKey] != "Hue" {
|
||||
if single(got, wantKey) != "Hue" {
|
||||
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Hue")
|
||||
}
|
||||
}
|
||||
@@ -194,7 +204,7 @@ type Shade string
|
||||
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||
}
|
||||
wantKey := EnumKey([]any{"dark", "light"})
|
||||
if got[wantKey] != "Shade" {
|
||||
if single(got, wantKey) != "Shade" {
|
||||
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Shade")
|
||||
}
|
||||
}
|
||||
@@ -230,10 +240,10 @@ const (
|
||||
}
|
||||
colorKey := EnumKey([]any{"red", "blue"})
|
||||
shadeKey := EnumKey([]any{"dark", "light"})
|
||||
if got[colorKey] != "Color" {
|
||||
if single(got, colorKey) != "Color" {
|
||||
t.Fatalf("Color: map[%q] = %q, want %q", colorKey, got[colorKey], "Color")
|
||||
}
|
||||
if got[shadeKey] != "Shade" {
|
||||
if single(got, shadeKey) != "Shade" {
|
||||
t.Fatalf("Shade: map[%q] = %q, want %q", shadeKey, got[shadeKey], "Shade")
|
||||
}
|
||||
}
|
||||
|
||||
+41
-96
@@ -151,9 +151,6 @@ func (d *delayWriter) WriteString(s string) (n int, err error) {
|
||||
}
|
||||
|
||||
func (d *delayWriter) Close() error {
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
stopped := d.timer.Stop()
|
||||
if stopped || d.buf == nil {
|
||||
return nil
|
||||
@@ -163,16 +160,6 @@ func (d *delayWriter) Close() error {
|
||||
return err
|
||||
}
|
||||
|
||||
type nilWriter struct{}
|
||||
|
||||
func (n *nilWriter) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (n *nilWriter) WriteString(s string) (int, error) {
|
||||
return len(s), nil
|
||||
}
|
||||
|
||||
func parseGitHookCommitRefLine(line string) (oldCommitID, newCommitID string, refFullName git.RefName, ok bool) {
|
||||
fields := strings.Split(line, " ")
|
||||
if len(fields) != 3 {
|
||||
@@ -227,8 +214,7 @@ Gitea or set your environment appropriately.`, "")
|
||||
total := 0
|
||||
lastline := 0
|
||||
|
||||
var out io.Writer
|
||||
out = &nilWriter{}
|
||||
out := io.Discard
|
||||
if setting.Git.VerbosePush {
|
||||
if setting.Git.VerbosePushDelay > 0 {
|
||||
dWriter := newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay)
|
||||
@@ -350,12 +336,10 @@ Gitea or set your environment appropriately.`, "")
|
||||
return nil
|
||||
}
|
||||
|
||||
var out io.Writer
|
||||
var dWriter *delayWriter
|
||||
out = &nilWriter{}
|
||||
out := io.Discard
|
||||
if setting.Git.VerbosePush {
|
||||
if setting.Git.VerbosePushDelay > 0 {
|
||||
dWriter = newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay)
|
||||
dWriter := newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay)
|
||||
defer dWriter.Close()
|
||||
out = dWriter
|
||||
} else {
|
||||
@@ -382,101 +366,62 @@ Gitea or set your environment appropriately.`, "")
|
||||
PushTrigger: repo_module.PushTrigger(os.Getenv(repo_module.EnvPushTrigger)),
|
||||
IsWiki: isWiki,
|
||||
}
|
||||
oldCommitIDs := make([]string, hookBatchSize)
|
||||
newCommitIDs := make([]string, hookBatchSize)
|
||||
refFullNames := make([]git.RefName, hookBatchSize)
|
||||
count := 0
|
||||
total := 0
|
||||
wasEmpty := false
|
||||
masterPushed := false
|
||||
|
||||
oldCommitIDs := make([]string, 0, hookBatchSize)
|
||||
newCommitIDs := make([]string, 0, hookBatchSize)
|
||||
refFullNames := make([]git.RefName, 0, hookBatchSize)
|
||||
results := make([]private.HookPostReceiveBranchResult, 0)
|
||||
|
||||
defer func() {
|
||||
hookPrintResults(results)
|
||||
}()
|
||||
|
||||
processBatch := func() error {
|
||||
if len(refFullNames) == 0 {
|
||||
return nil
|
||||
}
|
||||
_, _ = fmt.Fprintf(out, " Processing %d references\n", len(refFullNames))
|
||||
hookOptions.OldCommitIDs = oldCommitIDs
|
||||
hookOptions.NewCommitIDs = newCommitIDs
|
||||
hookOptions.RefFullNames = refFullNames
|
||||
resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
|
||||
if extra.HasError() {
|
||||
return fail(ctx, extra.UserMsg, "HookPostReceive failed: %v", extra.Error)
|
||||
}
|
||||
results = append(results, resp.Results...)
|
||||
oldCommitIDs = oldCommitIDs[:0]
|
||||
newCommitIDs = newCommitIDs[:0]
|
||||
refFullNames = refFullNames[:0]
|
||||
return nil
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
for scanner.Scan() {
|
||||
// TODO: support news feeds for wiki
|
||||
// wiki doesn't need "post-receive" at the moment
|
||||
if isWiki {
|
||||
continue
|
||||
}
|
||||
|
||||
var ok bool
|
||||
oldCommitIDs[count], newCommitIDs[count], refFullNames[count], ok = parseGitHookCommitRefLine(scanner.Text())
|
||||
oldCommitID, newCommitID, refFullName, ok := parseGitHookCommitRefLine(scanner.Text())
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
_, _ = fmt.Fprintf(out, ".")
|
||||
|
||||
fmt.Fprintf(out, ".")
|
||||
commitID, _ := git.NewIDFromString(newCommitIDs[count])
|
||||
if refFullNames[count] == git.BranchPrefix+"master" && !commitID.IsZero() && count == total {
|
||||
masterPushed = true
|
||||
}
|
||||
count++
|
||||
total++
|
||||
|
||||
if count >= hookBatchSize {
|
||||
fmt.Fprintf(out, " Processing %d references\n", count)
|
||||
hookOptions.OldCommitIDs = oldCommitIDs
|
||||
hookOptions.NewCommitIDs = newCommitIDs
|
||||
hookOptions.RefFullNames = refFullNames
|
||||
resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
|
||||
if extra.HasError() {
|
||||
_ = dWriter.Close()
|
||||
hookPrintResults(results)
|
||||
return fail(ctx, extra.UserMsg, "HookPostReceive failed: %v", extra.Error)
|
||||
oldCommitIDs = append(oldCommitIDs, oldCommitID)
|
||||
newCommitIDs = append(newCommitIDs, newCommitID)
|
||||
refFullNames = append(refFullNames, refFullName)
|
||||
if len(refFullNames) >= hookBatchSize {
|
||||
// process and start a new batch
|
||||
if err := processBatch(); err != nil {
|
||||
return err
|
||||
}
|
||||
wasEmpty = wasEmpty || resp.RepoWasEmpty
|
||||
results = append(results, resp.Results...)
|
||||
count = 0
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
_ = dWriter.Close()
|
||||
hookPrintResults(results)
|
||||
return fail(ctx, "Hook failed: stdin read error", "scanner error: %v", err)
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
if wasEmpty && masterPushed {
|
||||
// We need to tell the repo to reset the default branch to master
|
||||
extra := private.SetDefaultBranch(ctx, repoUser, repoName, "master")
|
||||
if extra.HasError() {
|
||||
return fail(ctx, extra.UserMsg, "SetDefaultBranch failed: %v", extra.Error)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(out, "Processed %d references in total\n", total)
|
||||
|
||||
_ = dWriter.Close()
|
||||
hookPrintResults(results)
|
||||
return nil
|
||||
}
|
||||
|
||||
hookOptions.OldCommitIDs = oldCommitIDs[:count]
|
||||
hookOptions.NewCommitIDs = newCommitIDs[:count]
|
||||
hookOptions.RefFullNames = refFullNames[:count]
|
||||
|
||||
fmt.Fprintf(out, " Processing %d references\n", count)
|
||||
|
||||
resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
|
||||
if resp == nil {
|
||||
_ = dWriter.Close()
|
||||
hookPrintResults(results)
|
||||
return fail(ctx, extra.UserMsg, "HookPostReceive failed: %v", extra.Error)
|
||||
}
|
||||
wasEmpty = wasEmpty || resp.RepoWasEmpty
|
||||
results = append(results, resp.Results...)
|
||||
|
||||
fmt.Fprintf(out, "Processed %d references in total\n", total)
|
||||
|
||||
if wasEmpty && masterPushed {
|
||||
// We need to tell the repo to reset the default branch to master
|
||||
extra := private.SetDefaultBranch(ctx, repoUser, repoName, "master")
|
||||
if extra.HasError() {
|
||||
return fail(ctx, extra.UserMsg, "SetDefaultBranch failed: %v", extra.Error)
|
||||
}
|
||||
}
|
||||
_ = dWriter.Close()
|
||||
hookPrintResults(results)
|
||||
|
||||
return nil
|
||||
return processBatch()
|
||||
}
|
||||
|
||||
func hookPrintResults(results []private.HookPostReceiveBranchResult) {
|
||||
|
||||
@@ -531,6 +531,10 @@ INTERNAL_TOKEN =
|
||||
;;
|
||||
;; The value of the X-Content-Type-Options HTTP header for all responses. Use "unset" to remove the header.
|
||||
;X_CONTENT_TYPE_OPTIONS = nosniff
|
||||
;;
|
||||
;; The value of the general Content-Security-Policy for most web pages.
|
||||
;; Leave it empty to apply the default policy, or set it to "unset" to disable Content-Security-Policy.
|
||||
;CONTENT_SECURITY_POLICY_GENERAL =
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -2668,19 +2672,21 @@ LEVEL = Info
|
||||
;FILE_EXTENSIONS = .adoc,.asciidoc
|
||||
;; External command to render all matching extensions
|
||||
;RENDER_COMMAND = "asciidoc --out-file=- -"
|
||||
;; Don't pass the file on STDIN, pass the filename as argument instead.
|
||||
;; Whether Gitea should write the content into a local temp file for the render command's input.
|
||||
;; * false: the content will be passed via STDIN to the command.
|
||||
;; * true: write the content into a local temp file, and pass the temp filename as argument to the command.
|
||||
;IS_INPUT_FILE = false
|
||||
;; How the content will be rendered.
|
||||
;; * sanitized: Sanitize the content and render it inside current page, default to only allow a few HTML tags and attributes. Customized sanitizer rules can be defined in [markup.sanitizer.*] .
|
||||
;; * no-sanitizer: Disable the sanitizer and render the content inside current page. It's **insecure** and may lead to XSS attack if the content contains malicious code.
|
||||
;; * iframe: Render the content in a separate standalone page and embed it into current page by iframe. The iframe is in sandbox mode with same-origin disabled, and the JS code are safely isolated from parent page.
|
||||
;RENDER_CONTENT_MODE = sanitized
|
||||
;; The sandbox applied to the iframe and Content-Security-Policy header when RENDER_CONTENT_MODE is `iframe`.
|
||||
;; The sandbox applied to the Content-Security-Policy for the rendered content when RENDER_CONTENT_MODE is `iframe`.
|
||||
;; It defaults to a safe set of "allow-*" restrictions (space separated).
|
||||
;; You can also set it by your requirements or use "disabled" to disable the sandbox completely.
|
||||
;; When set it, make sure there is no security risk:
|
||||
;; * PDF-only content: generally safe to use "disabled", and it needs to be "disabled" because PDF only renders with no sandbox.
|
||||
;; * HTML content with JS: if the "RENDER_COMMAND" can guarantee there is no XSS, then it is safe, otherwise, you need to fine tune the "allow-*" restrictions.
|
||||
;; * HTML content with JS: do not set "allow-same-origin" unless the "RENDER_COMMAND" can guarantee there is no XSS.
|
||||
;RENDER_CONTENT_SANDBOX =
|
||||
;; Whether post-process the rendered HTML content, including:
|
||||
;; resolve relative links and image sources, recognizing issue/commit references, escaping invisible characters,
|
||||
|
||||
@@ -164,7 +164,12 @@ Mergers are the maintainers who carry out the final merge of approved PRs. Their
|
||||
|
||||
#### Becoming a merger
|
||||
|
||||
A merger should already be a Gitea maintainer. To apply, use the [Discord](https://discord.gg/Gitea) `#maintainers` channel. Mergers teams may also invite contributors.
|
||||
A merger must already be a Gitea maintainer.
|
||||
To apply, use the [Discord](https://discord.gg/Gitea) `#maintainers` channel.
|
||||
The minimum requirement for applications to become a merger is to have participated actively in the community for at least four months before applying.
|
||||
Ultimately, regardless of previous participation, you can only become a merger if the TOC votes in your favor.
|
||||
|
||||
You may also be invited by the TOC to become a merger.
|
||||
|
||||
### Technical Oversight Committee (TOC)
|
||||
|
||||
@@ -185,17 +190,30 @@ As long as seats are empty in the TOC, members of the previous TOC can fill them
|
||||
|
||||
If an elected member that accepts the seat does not have 2FA configured yet, they will be temporarily counted as `answer pending` until they manage to configure 2FA, thus leaving their seat empty for this duration.
|
||||
|
||||
If multiple persons have the same amount of votes, a random draw will be used to determine the order of the candidates with the same amount of votes, and thus who gets the seat first.
|
||||
The candidates will be placed in the list in an alphabetical insensitive order by their username.
|
||||
We use this script to determine the order of candidates with the same amount of votes:
|
||||
|
||||
```python
|
||||
import random
|
||||
random.seed("Gitea TOC <YEAR> Election")
|
||||
random.choice([<CANDIDATE_1>, <CANDIDATE_2>, ...])
|
||||
```
|
||||
|
||||
The result of this script needs then to be published in the TOC election issue to ensure transparency of the process.
|
||||
|
||||
### Current TOC members
|
||||
|
||||
- 2025-01-01 ~ 2026-06-14
|
||||
- 2026-06-14 ~ 2026-12-31
|
||||
- Company
|
||||
- [Jason Song](https://gitea.com/wolfogre) <i@wolfogre.com>
|
||||
- [Lunny Xiao](https://gitea.com/lunny) <xiaolunwen@gmail.com>
|
||||
- [Matti Ranta](https://gitea.com/techknowlogick) <techknowlogick@gitea.com>
|
||||
- Community
|
||||
- [6543](https://gitea.com/6543) <6543@obermui.de>
|
||||
- [bircni](https://gitea.com/bircni) <bircni@icloud.com>
|
||||
- [delvh](https://gitea.com/delvh) <dev.lh@web.de>
|
||||
- [lafriks](https://gitea.com/lafriks) <lauris@nix.lv>
|
||||
- [TheFox0x7](https://gitea.com/TheFox0x7) <thefox0x7@gmail.com>
|
||||
|
||||
|
||||
### Previous TOC/owners members
|
||||
|
||||
@@ -204,10 +222,10 @@ Here's the history of the owners and the time they served:
|
||||
- [Lunny Xiao](https://gitea.com/lunny) - 2016, 2017, [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023
|
||||
- [Kim Carlbäcker](https://github.com/bkcsoft) - 2016, 2017
|
||||
- [Thomas Boerger](https://gitea.com/tboerger) - 2016, 2017
|
||||
- [Lauris Bukšis-Haberkorns](https://gitea.com/lafriks) - [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801)
|
||||
- [Lauris Bukšis](https://gitea.com/lafriks) - [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), 2025
|
||||
- [Matti Ranta](https://gitea.com/techknowlogick) - [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023
|
||||
- [Andrew Thornton](https://gitea.com/zeripath) - [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023
|
||||
- [6543](https://gitea.com/6543) - 2023
|
||||
- [6543](https://gitea.com/6543) - 2023, 2025
|
||||
- [John Olheiser](https://gitea.com/jolheiser) - 2023, 2024
|
||||
- [Jason Song](https://gitea.com/wolfogre) - 2023
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
module gitea.dev
|
||||
|
||||
go 1.26.3
|
||||
go 1.26.4
|
||||
|
||||
require (
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
||||
connectrpc.com/connect v1.20.0
|
||||
gitea.com/gitea/runner v1.0.6
|
||||
gitea.com/gitea/runner v1.0.8
|
||||
gitea.com/go-chi/binding v0.0.0-20260414111559-654cea7ac60a
|
||||
gitea.com/go-chi/cache v0.2.1
|
||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
|
||||
@@ -13,19 +13,19 @@ require (
|
||||
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
|
||||
gitea.dev/actions-proto-go v0.6.0
|
||||
gitea.dev/sdk v1.0.1
|
||||
gitea.dev/sdk v1.1.0
|
||||
github.com/42wim/httpsig v1.2.4
|
||||
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.3
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.7.0
|
||||
github.com/Azure/go-ntlmssp v0.1.1
|
||||
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347
|
||||
github.com/ProtonMail/go-crypto v1.4.1
|
||||
github.com/PuerkitoBio/goquery v1.12.0
|
||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0
|
||||
github.com/alecthomas/chroma/v2 v2.25.0
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.17
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.14
|
||||
github.com/alecthomas/chroma/v2 v2.26.1
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.24
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.34.4
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||
github.com/blevesearch/bleve/v2 v2.6.0
|
||||
github.com/bohde/codel v0.2.0
|
||||
@@ -33,7 +33,7 @@ require (
|
||||
github.com/caddyserver/certmagic v0.25.3
|
||||
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20260309112543-12416315a635
|
||||
github.com/chi-middleware/proxy v1.1.1
|
||||
github.com/dlclark/regexp2/v2 v2.1.0
|
||||
github.com/dlclark/regexp2/v2 v2.2.1
|
||||
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.4
|
||||
@@ -42,7 +42,7 @@ require (
|
||||
github.com/ethantkoenig/rupture v1.0.1
|
||||
github.com/felixge/fgprof v0.9.5
|
||||
github.com/fsnotify/fsnotify v1.10.1
|
||||
github.com/getkin/kin-openapi v0.139.0
|
||||
github.com/getkin/kin-openapi v0.140.0
|
||||
github.com/gliderlabs/ssh v0.3.8
|
||||
github.com/go-chi/chi/v5 v5.3.0
|
||||
github.com/go-chi/cors v1.2.2
|
||||
@@ -59,25 +59,25 @@ require (
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/google/go-github/v88 v88.0.0
|
||||
github.com/google/licenseclassifier/v2 v2.0.0
|
||||
github.com/google/pprof v0.0.0-20260507013755-92041b743c96
|
||||
github.com/google/pprof v0.0.0-20260604005048-7023385849c0
|
||||
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.9.0
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/huandu/xstrings v1.5.0
|
||||
github.com/jhillyerd/enmime/v2 v2.4.0
|
||||
github.com/jhillyerd/enmime/v2 v2.4.1
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/klauspost/compress v1.18.6
|
||||
github.com/klauspost/cpuid/v2 v2.3.0
|
||||
github.com/lib/pq v1.12.3
|
||||
github.com/markbates/goth v1.82.0
|
||||
github.com/mattn/go-isatty v0.0.22
|
||||
github.com/mattn/go-sqlite3 v1.14.44
|
||||
github.com/meilisearch/meilisearch-go v0.36.2
|
||||
github.com/mattn/go-sqlite3 v1.14.45
|
||||
github.com/meilisearch/meilisearch-go v0.36.3
|
||||
github.com/mholt/archives v0.1.5
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/microsoft/go-mssqldb v1.9.7
|
||||
github.com/microsoft/go-mssqldb v1.10.0
|
||||
github.com/minio/minio-go/v7 v7.2.0
|
||||
github.com/msteinert/pam/v2 v2.1.0
|
||||
github.com/niklasfasching/go-org v1.9.1
|
||||
@@ -86,7 +86,7 @@ require (
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/quasoft/websspi v1.1.2
|
||||
github.com/redis/go-redis/v9 v9.19.0
|
||||
github.com/redis/go-redis/v9 v9.20.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
|
||||
github.com/sassoftware/go-rpmutils v0.4.0
|
||||
@@ -96,24 +96,25 @@ require (
|
||||
github.com/tstranex/u2f v1.0.0
|
||||
github.com/ulikunitz/xz v0.5.15
|
||||
github.com/urfave/cli-docs/v3 v3.1.0
|
||||
github.com/urfave/cli/v3 v3.9.0
|
||||
github.com/urfave/cli/v3 v3.9.1
|
||||
github.com/wneessen/go-mail v0.7.3
|
||||
github.com/yohcop/openid-go v1.0.1
|
||||
github.com/yuin/goldmark v1.8.2
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
gitlab.com/gitlab-org/api/client-go/v2 v2.34.0
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.3
|
||||
golang.org/x/crypto v0.52.0
|
||||
golang.org/x/image v0.41.0
|
||||
golang.org/x/net v0.55.0
|
||||
gitlab.com/gitlab-org/api/client-go/v2 v2.38.0
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.5
|
||||
golang.org/x/crypto v0.53.0
|
||||
golang.org/x/image v0.42.0
|
||||
golang.org/x/mod v0.37.0
|
||||
golang.org/x/net v0.56.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/sys v0.45.0
|
||||
golang.org/x/text v0.37.0
|
||||
golang.org/x/sync v0.21.0
|
||||
golang.org/x/sys v0.46.0
|
||||
golang.org/x/text v0.38.0
|
||||
google.golang.org/grpc v1.81.1
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gopkg.in/ini.v1 v1.67.2
|
||||
modernc.org/sqlite v1.50.1
|
||||
gopkg.in/ini.v1 v1.67.3
|
||||
modernc.org/sqlite v1.52.0
|
||||
mvdan.cc/xurls/v2 v2.6.0
|
||||
strk.kbt.io/projects/go/libravatar v0.0.0-20260301104140-add494e31dab
|
||||
xorm.io/builder v0.3.13
|
||||
@@ -124,24 +125,24 @@ require (
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect
|
||||
github.com/DataDog/zstd v1.5.7 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.16.0 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.18.2 // indirect
|
||||
github.com/STARRY-S/zip v0.2.3 // indirect
|
||||
github.com/andybalholm/brotli v1.2.1 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.4 // indirect
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect
|
||||
github.com/aws/smithy-go v1.25.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.42.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 // indirect
|
||||
github.com/aws/smithy-go v1.27.2 // 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.4 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.3.11 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.24.5 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.3.12 // indirect
|
||||
github.com/blevesearch/geo v0.2.5 // indirect
|
||||
github.com/blevesearch/go-faiss v1.1.0 // indirect
|
||||
github.com/blevesearch/go-faiss v1.1.4 // indirect
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||
github.com/blevesearch/mmap-go v1.2.0 // indirect
|
||||
@@ -156,13 +157,13 @@ require (
|
||||
github.com/blevesearch/zapx/v14 v14.4.3 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.4.3 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.3.4 // indirect
|
||||
github.com/blevesearch/zapx/v17 v17.1.2 // indirect
|
||||
github.com/blevesearch/zapx/v17 v17.1.6 // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
|
||||
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||
github.com/bodgit/sevenzip v1.6.1 // indirect
|
||||
github.com/bodgit/sevenzip v1.6.4 // 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/bradfitz/gomemcache v0.0.0-20260422231931-4d751bb6e37c // indirect
|
||||
github.com/caddyserver/zerossl v0.1.5 // indirect
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
@@ -182,10 +183,9 @@ require (
|
||||
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
||||
github.com/go-enry/go-oniguruma v1.2.1 // indirect
|
||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.23.1 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.26.1 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/go-webauthn/x v0.2.6 // indirect
|
||||
github.com/goccy/go-json v0.10.6 // indirect
|
||||
@@ -206,31 +206,28 @@ require (
|
||||
github.com/inbucket/html2text v1.0.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kevinburke/ssh_config v1.6.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.7.7 // indirect
|
||||
github.com/markbates/going v1.0.3 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.23 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||
github.com/mattn/go-colorable v0.1.15 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.24 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.13 // indirect
|
||||
github.com/mholt/acmez/v3 v3.1.6 // indirect
|
||||
github.com/miekg/dns v1.1.72 // 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
|
||||
github.com/minio/minlz v1.1.0 // indirect
|
||||
github.com/minio/minlz v1.1.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
|
||||
github.com/mschoch/smat v0.2.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.2.2 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.2.3 // indirect
|
||||
github.com/oasdiff/yaml v0.1.0 // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.13 // indirect
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
|
||||
@@ -238,14 +235,13 @@ require (
|
||||
github.com/olekukonko/ll v0.1.8 // indirect
|
||||
github.com/olekukonko/tablewriter v1.1.4 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.26 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.27 // indirect
|
||||
github.com/pjbgf/sha1cd v0.6.0 // indirect
|
||||
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.67.5 // indirect
|
||||
github.com/prometheus/common v0.68.1 // indirect
|
||||
github.com/prometheus/procfs v0.20.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rhysd/actionlint v1.7.12 // indirect
|
||||
@@ -257,9 +253,9 @@ require (
|
||||
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/stangelandcl/ppmd v0.1.1 // indirect
|
||||
github.com/tinylib/msgp v1.6.4 // indirect
|
||||
github.com/unknwon/com v1.0.1 // indirect
|
||||
github.com/woodsbury/decimal128 v1.3.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
@@ -268,18 +264,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.1 // indirect
|
||||
go.uber.org/zap v1.28.0 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
|
||||
golang.org/x/mod v0.35.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
golang.org/x/tools v0.44.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401020348-3a24fdc17823 // indirect
|
||||
golang.org/x/tools v0.45.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.72.3 // indirect
|
||||
modernc.org/libc v1.73.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
|
||||
@@ -10,8 +10,8 @@ dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
gitea.com/gitea/runner v1.0.6 h1:QG0YB97z1S7bu3q5VX5sOEs5gyER6AG/oSGFS1dJ1Hg=
|
||||
gitea.com/gitea/runner v1.0.6/go.mod h1:q+WGNAMeOZL4fRqvBTbAFgR2tupWtzLOrzoyKcky3QQ=
|
||||
gitea.com/gitea/runner v1.0.8 h1:zKfC4+zyyGIDagqhII3WVw52P+A9iAa63It0lniN4SI=
|
||||
gitea.com/gitea/runner v1.0.8/go.mod h1:AGLQXo8ELz9WPzNJ5W1SvnCik8ZX3jF0o6yCPCmwLtM=
|
||||
gitea.com/go-chi/binding v0.0.0-20260414111559-654cea7ac60a h1:JHoBrfuTSF9Ke9aNfSYj1XRPBHjKPgCApVprnt2Am0M=
|
||||
gitea.com/go-chi/binding v0.0.0-20260414111559-654cea7ac60a/go.mod h1:FOsLJIMdpiHzBp3Vby6Wfkdw2ppGscrjgU1IC7E4/zQ=
|
||||
gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g=
|
||||
@@ -28,18 +28,18 @@ gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGq
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||
gitea.dev/actions-proto-go v0.6.0 h1:gjllYQ5vmwlkqOeofTQu5qKTZpmf7kWsafoHvoPCSzY=
|
||||
gitea.dev/actions-proto-go v0.6.0/go.mod h1:p4RX+D9oqiEEzzkPMXscw2CmaGuYFPWFc6xIOmDNDqs=
|
||||
gitea.dev/sdk v1.0.1 h1:CWXQUQvp2I6YKOWkhYo1Flx2sRNfMK1X9Op4oR2awXs=
|
||||
gitea.dev/sdk v1.0.1/go.mod h1:jCf5Uzz0Jkb61jxNgMxLOCWwle1J1B2nKdcRtxuK9rY=
|
||||
gitea.dev/sdk v1.1.0 h1:wLlz03WkLEiXa2bQpO1JQBTlYf7tQI2neYtZK1kU+TE=
|
||||
gitea.dev/sdk v1.1.0/go.mod h1:Zfl+EZXdsGGCLkryDfsmvYrQo6GKMl4U3BJA8Beu+cs=
|
||||
github.com/42wim/httpsig v1.2.4 h1:mI5bH0nm4xn7K18fo1K3okNDRq8CCJ0KbBYWyA6r8lU=
|
||||
github.com/42wim/httpsig v1.2.4/go.mod h1:yKsYfSyTBEohkPik224QPFylmzEBtda/kjyIAJjh3ps=
|
||||
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432 h1:3Fcz1QzlS7Jv4FT2KI3cHNSZL+KPN3dXxurn9f3YL/Y=
|
||||
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432/go.mod h1:BLWe6Nol65Xxncvaw07yYMxiyk02We1lBrbRYsMYsjE=
|
||||
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=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 h1:/Zt+cDPnpC3OVDm/JKLOs7M2DKmLRIIp3XIx9pHHiig=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1/go.mod h1:Ng3urmn6dYe8gnbCMoHHVl5APYz2txho3koEkV2o2HA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZba3YZqeTNJPtvqZoBu1sBN/L4sry+u2U3Y75w=
|
||||
@@ -50,8 +50,8 @@ github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 h1:FwladfywkNirM+FZY
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2/go.mod h1:vv5Ad0RrIoT1lJFdWBZwt4mB1+j+V8DUroixmKDTCdk=
|
||||
github.com/Azure/go-ntlmssp v0.1.1 h1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw=
|
||||
github.com/Azure/go-ntlmssp v0.1.1/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
|
||||
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||
@@ -67,8 +67,8 @@ github.com/PuerkitoBio/goquery v1.12.0 h1:pAcL4g3WRXekcB9AU/y1mbKez2dbY2AajVhtkO
|
||||
github.com/PuerkitoBio/goquery v1.12.0/go.mod h1:802ej+gV2y7bbIhOIoPY5sT183ZW0YFofScC4q/hIpQ=
|
||||
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.16.0 h1:Kys1UNf49d5W8Tq3bpuAhIr/Z8/yPB+59CO8A6c/BbE=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.16.0/go.mod h1:eq4wdNXxtJIS/oikeCzdX1rBzek7ANzbth041hrU8Q4=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.18.2 h1:oPq3Cgx//iDuJQVp6xSInAKW34J9CEwE5GmLI2z+Eic=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.18.2/go.mod h1:eq4wdNXxtJIS/oikeCzdX1rBzek7ANzbth041hrU8Q4=
|
||||
github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
|
||||
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
|
||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0 h1:LvK7+C6qgz8BPnmn7xGekf8vTkcqTvyBBHaLYIMxx0g=
|
||||
@@ -76,8 +76,8 @@ github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0/go.mod h1:I28hc9eaiqK
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||
github.com/alecthomas/chroma/v2 v2.25.0 h1:DWkVlxrNpxPf+Qcfe04LBqUArxUiybK8ZQ9T7OFu68E=
|
||||
github.com/alecthomas/chroma/v2 v2.25.0/go.mod h1:+95AZrRWlpW9g6qXD7S7UdHviopsGP/kCIrtJcU3QoQ=
|
||||
github.com/alecthomas/chroma/v2 v2.26.1 h1:2X21EdxGZNv5GF9mG5u+uzc02GCFyGxbcBm3Grd9A78=
|
||||
github.com/alecthomas/chroma/v2 v2.26.1/go.mod h1:lxhRRa9H4hPmRLOOdYga4zkQIQjq3dtrrdwQeCfu78Y=
|
||||
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=
|
||||
@@ -85,45 +85,45 @@ github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktp
|
||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
|
||||
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/andybalholm/cascadia v1.3.4 h1:vM2lgh0Vru9Vwyfm4cQqWP2HHMW0u0+2PAW7Q38Qufg=
|
||||
github.com/andybalholm/cascadia v1.3.4/go.mod h1:BLRmbRjpEtNKieZOCCvYj4RqN+KRA41GBe/5O+G93kM=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
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.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.17 h1:gP2nkGsS+KMvF/jfFz2Vv2qiiOqWKyPACSzPsqHgoW8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.17/go.mod h1:Bsew3S/moG5iT77giPj1q8wb/s0RE5/QfH+ASjYtuQc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk=
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.14 h1:3N664oayz66ttIkc8B9/OLntMWhoGhKqPudRRfsZQ20=
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.14/go.mod h1:M+6j5lOmtDMjLlFMO8lfTs0gI3qBsSjM8L7F1cQF5Ng=
|
||||
github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI=
|
||||
github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/aws/aws-sdk-go-v2 v1.42.0 h1:XvXMJTkFQtpBKIWZnmr9ZEOc2InWM2yldjXEJ/bymhA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.42.0/go.mod h1:27+ACypSLljLAEKsCYOmrjKh83vuTRkuAe9Uv/3A4bg=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.24 h1:2hQqYCV9yqyePQ9o6dCrZc/zO8U3TwPr9mIKlZnPu/I=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.24/go.mod h1:IDwpACtwqHLISdzfwUUNq4P9DsB/h5BLg4FwJPNfqFY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 h1:f3vKqSo13fhTYb+JEcXwXefZQE26I1FB5eTSniU67ko=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29/go.mod h1:MzoLFUArKGpGD+ukmPiTPG1X5x4o6M2kq4v2dr1FiEc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 h1:RdwIf/CuUsvJX3RgJagbOyotl/cxoLY4xviKuE7p2GY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29/go.mod h1:71wt8W2EgswdZy9Mf9KNnzxZ3TiZlv4caKghPktDOkA=
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.34.4 h1:Uu+wqrOXozYYvaxcNIqjFsMTjoIJIZDN3R0f70ZIjyQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.34.4/go.mod h1:pYrBdL1tMTZO7PaKRsa1cTUB8HtQh3fFM3zJHGhTQcE=
|
||||
github.com/aws/smithy-go v1.27.2 h1:y9NPmSE6am6LjEFPfqHqG/jJk7AauQvhCJONKh7kpzk=
|
||||
github.com/aws/smithy-go v1.27.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
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=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bits-and-blooms/bitset v1.1.10/go.mod h1:w0XsmFg8qg6cmpTtJ0z3pKgjTDBMMnI/+I2syrE6XBE=
|
||||
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||
github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
|
||||
github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/bits-and-blooms/bitset v1.24.5 h1:654xBVHc23gJMAgOTkPNoCVfiRxuIOAUnAZFtopqJ4w=
|
||||
github.com/bits-and-blooms/bitset v1.24.5/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
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.6.0 h1:Cyd3dd4q5tCbOV8MnKUVRUDYMHOir9xn12NZzXVSEd4=
|
||||
github.com/blevesearch/bleve/v2 v2.6.0/go.mod h1:gLmI8lWgHgrIYf7UpUX7JISI1CaqC6VScu46mHThuAY=
|
||||
github.com/blevesearch/bleve_index_api v1.0.0/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4=
|
||||
github.com/blevesearch/bleve_index_api v1.3.11 h1:x29vbV8OjWfLcrDVd7Lr1q+BkLNS0JWNEig0MCVnKH4=
|
||||
github.com/blevesearch/bleve_index_api v1.3.11/go.mod h1:xvd48t5XMeeioWQ5/jZvgLrV98flT2rdvEJ3l/ki4Ko=
|
||||
github.com/blevesearch/bleve_index_api v1.3.12 h1:MirVNltwGq8z0PhOgiQp+bKL5qq8OvCxEwOOC7NnHNE=
|
||||
github.com/blevesearch/bleve_index_api v1.3.12/go.mod h1:xvd48t5XMeeioWQ5/jZvgLrV98flT2rdvEJ3l/ki4Ko=
|
||||
github.com/blevesearch/geo v0.2.5 h1:yJg9FX1oRwLnjXSXF+ECHfXFTF4diF02Ca/qUGVjJhE=
|
||||
github.com/blevesearch/geo v0.2.5/go.mod h1:Jhq7WE2K6mJTx1xS44M2pUO6Io+wjCSHh1+co3YOgH4=
|
||||
github.com/blevesearch/go-faiss v1.1.0 h1:xM7Jc0ZUCv5lssG9Ohj3Jv0SdTpxcUABU1dDt9XVsc4=
|
||||
github.com/blevesearch/go-faiss v1.1.0/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
|
||||
github.com/blevesearch/go-faiss v1.1.4 h1:wGHK+yiOSIvBAQMr4LcTaHBFf9v1dBebs3WpFqT93Rg=
|
||||
github.com/blevesearch/go-faiss v1.1.4/go.mod h1:w3W9AiWsFRGVaMG+/cmJi7iHEAuGyC6blsgO1EzCK/M=
|
||||
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=
|
||||
@@ -163,15 +163,15 @@ github.com/blevesearch/zapx/v15 v15.4.3 h1:iJiMJOHrz216jyO6lS0m9RTCEkprUnzvqAI2l
|
||||
github.com/blevesearch/zapx/v15 v15.4.3/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
|
||||
github.com/blevesearch/zapx/v16 v16.3.4 h1:hDAqA8qusZTNbPEL7//w5P65UZ2de6yhSeUaTbp0Po0=
|
||||
github.com/blevesearch/zapx/v16 v16.3.4/go.mod h1:zqkPPqs9GS9FzVWzCO3Wf1X044yWAV17+4zb+FTiEHg=
|
||||
github.com/blevesearch/zapx/v17 v17.1.2 h1:avbOk2igaASNoiy0BE/jPgcxAnRI2PGeydeP4hg7Ikk=
|
||||
github.com/blevesearch/zapx/v17 v17.1.2/go.mod h1:WQObxKrqUX7cd0G1GMvDfc/bmZzQvoy7APOPimx7DiI=
|
||||
github.com/blevesearch/zapx/v17 v17.1.6 h1:rVGeyH0EPElBXM4PvjrCdt8LDdRLpa4GC1gMRQkCWUE=
|
||||
github.com/blevesearch/zapx/v17 v17.1.6/go.mod h1:c+mPvbZgZnDPOUS5Z9EXhntMcJnpIVjQTM9TF5yEGJM=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q=
|
||||
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
||||
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
|
||||
github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4=
|
||||
github.com/bodgit/sevenzip v1.6.1/go.mod h1:GVoYQbEVbOGT8n2pfqCIMRUaRjQ8F9oSqoBEqZh5fQ8=
|
||||
github.com/bodgit/sevenzip v1.6.4 h1:iHiVJfxbrB6RF4X+snI2MpVgNBKmVfGaTqZGNlMQIU0=
|
||||
github.com/bodgit/sevenzip v1.6.4/go.mod h1:ZtNi5KNgHXeXg1G7WiF0IWSuFE2eG6lt/cTGlvuirO0=
|
||||
github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
|
||||
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
|
||||
github.com/bohde/codel v0.2.0 h1:fzF7ibgKmCfQbOzQCblmQcwzDRmV7WO7VMLm/hDvD3E=
|
||||
@@ -179,8 +179,8 @@ github.com/bohde/codel v0.2.0/go.mod h1:Idb1IRvTdwkRjIjguLIo+FXhIBhcpGl94o7xra6g
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
|
||||
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20260422231931-4d751bb6e37c h1:6Gpm9YYUEQx2T9zMsYolQhr6sjwwGtFitSA0pQsa7a8=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20260422231931-4d751bb6e37c/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
@@ -240,8 +240,8 @@ github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
|
||||
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2/v2 v2.1.0 h1:jHXRmHRZGbuQzDZjMlCAXOvQb75iv3HyLDzXGj5H1AY=
|
||||
github.com/dlclark/regexp2/v2 v2.1.0/go.mod h1:Bz5TMy5d8fPK0ximH0Yi9KvsRHNnvXqUx9XG6a4wB+I=
|
||||
github.com/dlclark/regexp2/v2 v2.2.1 h1:mf4KkFUj0gJuarK8P+LgiS+Lit7m9N1yAwEfPbee7R0=
|
||||
github.com/dlclark/regexp2/v2 v2.2.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
@@ -274,8 +274,8 @@ github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx5
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78=
|
||||
github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/getkin/kin-openapi v0.139.0 h1:pBFXcZJFwz9J1X64jzxlOoNgFm+TF7kNrs9+HJVN6Ic=
|
||||
github.com/getkin/kin-openapi v0.139.0/go.mod h1:NGxPfE4PwS/TRXEbyx2RrxDFPZvxcWw31Tw8XXjPZLs=
|
||||
github.com/getkin/kin-openapi v0.140.0 h1:JFn675aXRFjyiZKa/BFWploGldQlI0gobp4J5k0EZ2g=
|
||||
github.com/getkin/kin-openapi v0.140.0/go.mod h1:lISrB64F0CPcuDJ3LdtPTMJBY8VENjR9wJBdrcT6J3g=
|
||||
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0=
|
||||
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
@@ -295,8 +295,6 @@ github.com/go-enry/go-enry/v2 v2.9.6 h1:np63eOtMV56zfYDHnFVgpEVOk8fr2kmylcMnAZUD
|
||||
github.com/go-enry/go-enry/v2 v2.9.6/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
|
||||
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
|
||||
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
|
||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e h1:oRq/fiirun5HqlEWMLIcDmLpIELlG4iGbd0s8iqgPi8=
|
||||
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.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=
|
||||
@@ -309,10 +307,12 @@ github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ=
|
||||
github.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4=
|
||||
github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY=
|
||||
github.com/go-openapi/swag/jsonname v0.26.1 h1:VReupaV6WxlAsCn0e4DUfgV6bPmINnPpyJDLqSfNPcE=
|
||||
github.com/go-openapi/swag/jsonname v0.26.1/go.mod h1:OvdW6BoWoj33pTfi7x9vFrgmT+fk7aw0BRwvCE0YOuc=
|
||||
github.com/go-openapi/testify/v2 v2.5.1 h1:TMdhCaw8fUNraVSf3Omoob1dO/AzBfhtFAPW0an6sBo=
|
||||
github.com/go-openapi/testify/v2 v2.5.1/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
|
||||
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=
|
||||
@@ -390,8 +390,8 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
|
||||
github.com/google/licenseclassifier/v2 v2.0.0 h1:1Y57HHILNf4m0ABuMVb6xk4vAJYEUO0gDxNpog0pyeA=
|
||||
github.com/google/licenseclassifier/v2 v2.0.0/go.mod h1:cOjbdH0kyC9R22sdQbYsFkto4NGCAc+ZSwbeThazEtM=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/pprof v0.0.0-20260507013755-92041b743c96 h1:YDDnaZ9afWajDboPMt9Vikqca/yWAX7KAxVzb4lJU1M=
|
||||
github.com/google/pprof v0.0.0-20260507013755-92041b743c96/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
|
||||
github.com/google/pprof v0.0.0-20260604005048-7023385849c0 h1:h1QTMDl6q9wDvDCJVpKQSjgleGFYnd2fOxmg2K+6BGE=
|
||||
github.com/google/pprof v0.0.0-20260604005048-7023385849c0/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
@@ -413,8 +413,8 @@ github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pw
|
||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
||||
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
||||
github.com/graph-gophers/graphql-go v1.9.0 h1:yu0ucKHLc5qGpRwLYKIWtr9bOoxovkWasuBrPQwlHls=
|
||||
github.com/graph-gophers/graphql-go v1.9.0/go.mod h1:23olKZ7duEvHlF/2ELEoSZaY1aNPfShjP782SOoNTyM=
|
||||
github.com/graph-gophers/graphql-go v1.10.2 h1:HXu6Wu5klCH4ALn1fQHVI20cjEIa4wftavHIgbLA4Fo=
|
||||
github.com/graph-gophers/graphql-go v1.10.2/go.mod h1:AsADheC4CCFwd8n1/QbkduTlHgYYMsRgtPihYVAlEsk=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@@ -456,11 +456,10 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||
github.com/jhillyerd/enmime/v2 v2.4.0 h1:6bPyyg2OPXEK1fKsLT89DntZf05LqaL2cIx+cvkEXTo=
|
||||
github.com/jhillyerd/enmime/v2 v2.4.0/go.mod h1:TLpvqImPiumRecsJK5TYseRw2bPg3g0EtWc+SfU7cMs=
|
||||
github.com/jhillyerd/enmime/v2 v2.4.1 h1:VkBX8GJJ/wbQofWsKP3egRqgXcwmxlY94YUmXTj08kE=
|
||||
github.com/jhillyerd/enmime/v2 v2.4.1/go.mod h1:TLpvqImPiumRecsJK5TYseRw2bPg3g0EtWc+SfU7cMs=
|
||||
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
@@ -502,24 +501,23 @@ github.com/lib/pq v1.12.3/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=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/markbates/going v1.0.3 h1:mY45T5TvW+Xz5A6jY7lf4+NLg9D8+iuStIHyR7M8qsE=
|
||||
github.com/markbates/going v1.0.3/go.mod h1:fQiT6v6yQar9UD6bd/D4Z5Afbk9J6BBVBtLiyY4gp2o=
|
||||
github.com/markbates/goth v1.82.0 h1:8j/c34AjBSTNzO7zTsOyP5IYCQCMBTRBHAbBt/PI0bQ=
|
||||
github.com/markbates/goth v1.82.0/go.mod h1:/DRlcq0pyqkKToyZjsL2KgiA1zbF1HIjE7u2uC79rUk=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
|
||||
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
|
||||
github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
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.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8=
|
||||
github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
||||
github.com/meilisearch/meilisearch-go v0.36.2 h1:MYaMPCpdLh2aYPt+zK+19mLoA4dfBY3S1L7T0FADCjU=
|
||||
github.com/meilisearch/meilisearch-go v0.36.2/go.mod h1:hWcR0MuWLSzHfbz9GGzIr3s9rnXLm1jqkmHkJPbUSvM=
|
||||
github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
|
||||
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/mattn/go-shellwords v1.0.13 h1:DC0OMEpGjm6LfNFU4ckYcvbQKyp2vE8atyFGXNtDcf4=
|
||||
github.com/mattn/go-shellwords v1.0.13/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.45 h1:6KA/spDguL3KV8rnybG7ezSaE4SeMR3KC9VbUoAQaIk=
|
||||
github.com/mattn/go-sqlite3 v1.14.45/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
||||
github.com/meilisearch/meilisearch-go v0.36.3 h1:Yx1aTY5jDgtbStPVkhJTDoLnZTy5sejQSPyjfNMy6e4=
|
||||
github.com/meilisearch/meilisearch-go v0.36.3/go.mod h1:hWcR0MuWLSzHfbz9GGzIr3s9rnXLm1jqkmHkJPbUSvM=
|
||||
github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk=
|
||||
github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY=
|
||||
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
|
||||
@@ -538,8 +536,8 @@ 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.2.0 h1:RCJM0R1XOsRs+A3x3UCaf3ZYbByDaLjFeAi+YCQEPhs=
|
||||
github.com/minio/minio-go/v7 v7.2.0/go.mod h1:EU9hENAStx/xXduNdrGO5e4X5vk19NtgB+RIPjZO8o0=
|
||||
github.com/minio/minlz v1.1.0 h1:rUOGu3EP4EqJC5k3qCsIwEnZiJULKqtRyDdqbhlvMmQ=
|
||||
github.com/minio/minlz v1.1.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
||||
github.com/minio/minlz v1.1.1 h1:OGmft1V6AnI/Wme332U6bhG54nxEan+VFgkD7lat4KM=
|
||||
github.com/minio/minlz v1.1.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -547,8 +545,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 h1:j2kD3MT1z4PXCiUllUJF9mWUESr9TWKS7iEKsQ/IipM=
|
||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
|
||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
||||
@@ -562,8 +558,8 @@ github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOF
|
||||
github.com/ncruces/go-strftime v1.0.0/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.2 h1:/5oL8dzYivRM/tqX9VcTSWfbpwcbwKG1QtSJr3b3KcU=
|
||||
github.com/nwaples/rardecode/v2 v2.2.2/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
|
||||
github.com/nwaples/rardecode/v2 v2.2.3 h1:qaVuy3ChZDbAQZshPLjHeNJKF3Cru8uo9jmgveKIy2A=
|
||||
github.com/nwaples/rardecode/v2 v2.2.3/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
@@ -595,13 +591,11 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
|
||||
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||
github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk=
|
||||
github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
|
||||
github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
@@ -617,15 +611,15 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
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.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
|
||||
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
|
||||
github.com/prometheus/common v0.68.1 h1:omjRRl4QP4komogpXuhfeOiisQg7xdy8VM1UY+pStaY=
|
||||
github.com/prometheus/common v0.68.1/go.mod h1:ZzL3f6u94qUxh9p+tJTrF+FvBS1XXbbRAZCQkytAL0Y=
|
||||
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
|
||||
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
|
||||
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.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k=
|
||||
github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
|
||||
github.com/redis/go-redis/v9 v9.20.0 h1:WnQYxLkgO2xiXTCJY0ldIiI8dNqCDlQAG+AtaH7a2a0=
|
||||
github.com/redis/go-redis/v9 v9.20.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
|
||||
github.com/redis/rueidis v1.0.71 h1:pODtnAR5GAB7j4ekhldZ29HKOxe4Hph0GTDGk1ayEQY=
|
||||
github.com/redis/rueidis v1.0.71/go.mod h1:lfdcZzJ1oKGKL37vh9fO3ymwt+0TdjkkUCJxbgpmcgQ=
|
||||
github.com/redis/rueidis/rueidiscompat v1.0.71 h1:wNZ//kEjMZgBM0KCk7ncOX8KmAgROU2kDdDNpwheG4w=
|
||||
@@ -676,6 +670,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
||||
github.com/stangelandcl/ppmd v0.1.1 h1:c25QazhlWUn5nmR1QOzafKhQxBicAr7GGCKER2aJ8H8=
|
||||
github.com/stangelandcl/ppmd v0.1.1/go.mod h1:Rrv7M+/2P5jYr/GMLhBl7Ug3uJ1bUiVzr5LbbaV6xgY=
|
||||
github.com/stephens2424/writerset v1.0.2/go.mod h1:aS2JhsMn6eA7e82oNmW4rfsgAOp9COBTTl8mzkwADnc=
|
||||
github.com/steveyen/gtreap v0.1.0/go.mod h1:kl/5J7XbrOmlIbYIXdRHDDE5QxHqpk0cmkT7Z4dM9/Y=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -706,8 +702,6 @@ github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77ro
|
||||
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=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
@@ -715,13 +709,11 @@ github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
|
||||
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
|
||||
github.com/urfave/cli-docs/v3 v3.1.0 h1:Sa5xm19IpE5gpm6tZzXdfjdFxn67PnEsE4dpXF7vsKw=
|
||||
github.com/urfave/cli-docs/v3 v3.1.0/go.mod h1:59d+5Hz1h6GSGJ10cvcEkbIe3j233t4XDqI72UIx7to=
|
||||
github.com/urfave/cli/v3 v3.9.0 h1:AV9lIiPv3ukYnxunaCUsHnEozptYmDN2F0+yWqLMn/c=
|
||||
github.com/urfave/cli/v3 v3.9.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/urfave/cli/v3 v3.9.1 h1:OLU13atWZ0M+a4xmyBuBNOLZsSRYXyPeMeNjOvgYP54=
|
||||
github.com/urfave/cli/v3 v3.9.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/wneessen/go-mail v0.7.3 h1:g3DravXC5SMlVdboFrQA8Jx95A8sOzoBeS5F+vzNRK0=
|
||||
github.com/wneessen/go-mail v0.7.3/go.mod h1:QGhBX0yNbc1J+Mkjcu7z2rpj4B4l+BmDY8gYznPC9sk=
|
||||
github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0=
|
||||
github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
@@ -748,8 +740,8 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||
gitlab.com/gitlab-org/api/client-go/v2 v2.34.0 h1:3Gv+41azLlj97fUkhkaTCnIOm45mHDwLlAcTDEwHE2s=
|
||||
gitlab.com/gitlab-org/api/client-go/v2 v2.34.0/go.mod h1:T+hA9p13Fxyh4FkVbcEy36HlAGs37QBCifhh7Zt4+dg=
|
||||
gitlab.com/gitlab-org/api/client-go/v2 v2.38.0 h1:gZSMTTnLcUeY5mH4z3G6GEzbaBTOCUfBCAJXMRyuzEM=
|
||||
gitlab.com/gitlab-org/api/client-go/v2 v2.38.0/go.mod h1:SKUbKSS59KPt6WeGNJoYF8HDaf/rFMUSITlftj/HkLg=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
@@ -761,8 +753,8 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
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.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=
|
||||
go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q=
|
||||
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.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
@@ -783,14 +775,13 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
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.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
|
||||
golang.org/x/image v0.41.0 h1:8wS72eGJMJaBxK6okTzd4WaXumUlTVlb753MlsSvTCo=
|
||||
golang.org/x/image v0.41.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA=
|
||||
golang.org/x/image v0.42.0 h1:1gSs6ehNWXLbkHBIPcWztk3D/6aIA/8hauiAYtlodVY=
|
||||
golang.org/x/image v0.42.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
@@ -799,8 +790,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.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -816,9 +807,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
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.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -832,8 +822,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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
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=
|
||||
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -863,10 +853,9 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
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=
|
||||
@@ -875,10 +864,9 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
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.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -889,8 +877,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.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -902,14 +890,14 @@ 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.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
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=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401020348-3a24fdc17823 h1:YedBIttDguBl/zy2wJauEUm+DZZg4UXseWj0g/3N+yo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401020348-3a24fdc17823/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad h1:45WmJvIV6C2+O/jjLkPUH+F3aOj/1miDoU2DD0+NWbg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
|
||||
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
@@ -925,8 +913,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ini.v1 v1.67.2 h1:JtOSMb9OuaCZKr7h5D/h6iii14sK0hLbplTc6frx4Ss=
|
||||
gopkg.in/ini.v1 v1.67.2/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
|
||||
gopkg.in/ini.v1 v1.67.3 h1:iM9Lhz5MRSGhHVGGwCuzG9KO8PoirCXj/m/qTmOJJQw=
|
||||
gopkg.in/ini.v1 v1.67.3/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=
|
||||
@@ -940,20 +928,20 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=
|
||||
modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ=
|
||||
modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A=
|
||||
modernc.org/cc/v4 v4.28.4 h1:Hd/4Es+MBj+/7hSdZaisNyu6bv3V0Dp2MdllyfqaH+c=
|
||||
modernc.org/cc/v4 v4.28.4/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.34.4 h1:OVnSOWQjVKOYkFxoHYB+qQmSHK5gqMqARM+K9DpR/Ws=
|
||||
modernc.org/ccgo/v4 v4.34.4/go.mod h1:qdKqE8FNIYyysougB1RX9MxCzp5oJOcQXSobANJ4TuE=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc=
|
||||
modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU=
|
||||
modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs=
|
||||
modernc.org/libc v1.73.0 h1:Y/KmTxbIN5T3x+NFjYOzV/+Ha7wKClfIecmTCTuYlqQ=
|
||||
modernc.org/libc v1.73.0/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
@@ -962,8 +950,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.50.1 h1:l+cQvn0sd0zJJtfygGHuQJ5AjlrwXmWPw4KP3ZMwr9w=
|
||||
modernc.org/sqlite v1.50.1/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
|
||||
modernc.org/sqlite v1.52.0 h1:p4dhYh2tXZCiyaqHwRVJDjIGKWyXayiQpThxgDzJaxo=
|
||||
modernc.org/sqlite v1.52.0/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
@@ -15,9 +15,9 @@ import (
|
||||
"gitea.dev/modules/setting"
|
||||
|
||||
// register supported doc types
|
||||
_ "gitea.dev/modules/markup/asciicast"
|
||||
_ "gitea.dev/modules/markup/console"
|
||||
_ "gitea.dev/modules/markup/csv"
|
||||
_ "gitea.dev/modules/markup/jupyter"
|
||||
_ "gitea.dev/modules/markup/markdown"
|
||||
_ "gitea.dev/modules/markup/orgmode"
|
||||
|
||||
|
||||
@@ -111,6 +111,7 @@ func (opts FindRunOptions) ToOrders() string {
|
||||
|
||||
type StatusInfo struct {
|
||||
Status int
|
||||
StatusName string
|
||||
DisplayedStatus string
|
||||
}
|
||||
|
||||
@@ -122,6 +123,7 @@ func GetStatusInfoList(ctx context.Context, lang translation.Locale) []StatusInf
|
||||
for _, s := range allStatus {
|
||||
statusInfoList = append(statusInfoList, StatusInfo{
|
||||
Status: int(s),
|
||||
StatusName: s.String(),
|
||||
DisplayedStatus: s.LocaleString(lang),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/models/unittest"
|
||||
"gitea.dev/modules/translation"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -22,3 +23,15 @@ func TestGetRunWorkflowIDs(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, ids)
|
||||
}
|
||||
|
||||
func TestGetStatusInfoList(t *testing.T) {
|
||||
statusInfoList := GetStatusInfoList(t.Context(), translation.MockLocale{})
|
||||
|
||||
assert.Equal(t, []StatusInfo{
|
||||
{Status: int(StatusSuccess), StatusName: StatusSuccess.String(), DisplayedStatus: "actions.status.success"},
|
||||
{Status: int(StatusFailure), StatusName: StatusFailure.String(), DisplayedStatus: "actions.status.failure"},
|
||||
{Status: int(StatusWaiting), StatusName: StatusWaiting.String(), DisplayedStatus: "actions.status.waiting"},
|
||||
{Status: int(StatusRunning), StatusName: StatusRunning.String(), DisplayedStatus: "actions.status.running"},
|
||||
{Status: int(StatusCancelling), StatusName: StatusCancelling.String(), DisplayedStatus: "actions.status.cancelling"},
|
||||
}, statusInfoList)
|
||||
}
|
||||
|
||||
@@ -20,42 +20,6 @@ import (
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// ErrAccessTokenNotExist represents a "AccessTokenNotExist" kind of error.
|
||||
type ErrAccessTokenNotExist struct {
|
||||
Token string
|
||||
}
|
||||
|
||||
// IsErrAccessTokenNotExist checks if an error is a ErrAccessTokenNotExist.
|
||||
func IsErrAccessTokenNotExist(err error) bool {
|
||||
_, ok := err.(ErrAccessTokenNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrAccessTokenNotExist) Error() string {
|
||||
return fmt.Sprintf("access token does not exist [sha: %s]", err.Token)
|
||||
}
|
||||
|
||||
func (err ErrAccessTokenNotExist) Unwrap() error {
|
||||
return util.ErrNotExist
|
||||
}
|
||||
|
||||
// ErrAccessTokenEmpty represents a "AccessTokenEmpty" kind of error.
|
||||
type ErrAccessTokenEmpty struct{}
|
||||
|
||||
// IsErrAccessTokenEmpty checks if an error is a ErrAccessTokenEmpty.
|
||||
func IsErrAccessTokenEmpty(err error) bool {
|
||||
_, ok := err.(ErrAccessTokenEmpty)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrAccessTokenEmpty) Error() string {
|
||||
return "access token is empty"
|
||||
}
|
||||
|
||||
func (err ErrAccessTokenEmpty) Unwrap() error {
|
||||
return util.ErrInvalidArgument
|
||||
}
|
||||
|
||||
var successfulAccessTokenCache *lru.Cache[string, any]
|
||||
|
||||
// AccessToken represents a personal access token.
|
||||
@@ -134,21 +98,11 @@ func getAccessTokenIDFromCache(token string) int64 {
|
||||
|
||||
// GetAccessTokenBySHA returns access token by given token value
|
||||
func GetAccessTokenBySHA(ctx context.Context, token string) (*AccessToken, error) {
|
||||
if token == "" {
|
||||
return nil, ErrAccessTokenEmpty{}
|
||||
}
|
||||
// A token is defined as being SHA1 sum these are 40 hexadecimal bytes long
|
||||
if len(token) != 40 {
|
||||
return nil, ErrAccessTokenNotExist{token}
|
||||
}
|
||||
for _, x := range []byte(token) {
|
||||
if x < '0' || (x > '9' && x < 'a') || x > 'f' {
|
||||
return nil, ErrAccessTokenNotExist{token}
|
||||
}
|
||||
if len(token) < 8 {
|
||||
return nil, util.NewNotExistErrorf("access token not found")
|
||||
}
|
||||
|
||||
lastEight := token[len(token)-8:]
|
||||
|
||||
if id := getAccessTokenIDFromCache(token); id > 0 {
|
||||
accessToken := &AccessToken{
|
||||
TokenLastEight: lastEight,
|
||||
@@ -169,7 +123,7 @@ func GetAccessTokenBySHA(ctx context.Context, token string) (*AccessToken, error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(tokens) == 0 {
|
||||
return nil, ErrAccessTokenNotExist{token}
|
||||
return nil, util.NewNotExistErrorf("access token not found")
|
||||
}
|
||||
|
||||
for _, t := range tokens {
|
||||
@@ -181,7 +135,7 @@ func GetAccessTokenBySHA(ctx context.Context, token string) (*AccessToken, error
|
||||
return &t, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrAccessTokenNotExist{token}
|
||||
return nil, util.NewNotExistErrorf("access token not found")
|
||||
}
|
||||
|
||||
// AccessTokenByNameExists checks if a token name has been used already by a user.
|
||||
@@ -218,13 +172,11 @@ func UpdateAccessToken(ctx context.Context, t *AccessToken) error {
|
||||
|
||||
// DeleteAccessTokenByID deletes access token by given ID.
|
||||
func DeleteAccessTokenByID(ctx context.Context, id, userID int64) error {
|
||||
cnt, err := db.GetEngine(ctx).ID(id).Delete(&AccessToken{
|
||||
UID: userID,
|
||||
})
|
||||
cnt, err := db.GetEngine(ctx).ID(id).Delete(&AccessToken{UID: userID})
|
||||
if err != nil {
|
||||
return err
|
||||
} else if cnt != 1 {
|
||||
return ErrAccessTokenNotExist{}
|
||||
return util.NewNotExistErrorf("access token not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
auth_model "gitea.dev/models/auth"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/unittest"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -76,11 +77,11 @@ func TestGetAccessTokenBySHA(t *testing.T) {
|
||||
|
||||
_, err = auth_model.GetAccessTokenBySHA(t.Context(), "notahash")
|
||||
assert.Error(t, err)
|
||||
assert.True(t, auth_model.IsErrAccessTokenNotExist(err))
|
||||
assert.ErrorIs(t, err, util.ErrNotExist)
|
||||
|
||||
_, err = auth_model.GetAccessTokenBySHA(t.Context(), "")
|
||||
assert.Error(t, err)
|
||||
assert.True(t, auth_model.IsErrAccessTokenEmpty(err))
|
||||
assert.ErrorIs(t, err, util.ErrNotExist)
|
||||
}
|
||||
|
||||
func TestListAccessTokens(t *testing.T) {
|
||||
@@ -128,5 +129,5 @@ func TestDeleteAccessTokenByID(t *testing.T) {
|
||||
|
||||
err = auth_model.DeleteAccessTokenByID(t.Context(), 100, 100)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, auth_model.IsErrAccessTokenNotExist(err))
|
||||
assert.ErrorIs(t, err, util.ErrNotExist)
|
||||
}
|
||||
|
||||
+12
-7
@@ -28,9 +28,8 @@ var (
|
||||
registeredInitFuncs []func() error
|
||||
)
|
||||
|
||||
// Engine represents a xorm engine or session.
|
||||
type Engine interface {
|
||||
Table(tableNameOrBean any) *xorm.Session
|
||||
// SQLSession represents a common interface for engine and session to execute SQLs
|
||||
type SQLSession interface {
|
||||
Count(...any) (int64, error)
|
||||
Decr(column string, arg ...any) *xorm.Session
|
||||
Delete(...any) (int64, error)
|
||||
@@ -52,7 +51,6 @@ type Engine interface {
|
||||
Limit(limit int, start ...int) *xorm.Session
|
||||
NoAutoTime() *xorm.Session
|
||||
SumInt(bean any, columnName string) (res int64, err error)
|
||||
Sync(...any) error
|
||||
Select(string) *xorm.Session
|
||||
SetExpr(string, any) *xorm.Session
|
||||
NotIn(string, ...any) *xorm.Session
|
||||
@@ -61,12 +59,20 @@ type Engine interface {
|
||||
Distinct(...string) *xorm.Session
|
||||
Query(...any) ([]map[string][]byte, error)
|
||||
Cols(...string) *xorm.Session
|
||||
Table(tableNameOrBean any) *xorm.Session
|
||||
Context(ctx context.Context) *xorm.Session
|
||||
Ping() error
|
||||
QueryInterface(sqlOrArgs ...any) ([]map[string]any, error)
|
||||
IsTableExist(tableNameOrBean any) (bool, error)
|
||||
}
|
||||
|
||||
// Session represents a xorm session interface, used as an abstraction over *xorm.Session.
|
||||
// Engine represents a xorm engine
|
||||
type Engine interface {
|
||||
SQLSession
|
||||
Sync(...any) error
|
||||
Ping() error
|
||||
}
|
||||
|
||||
// Session represents a xorm session interface
|
||||
type Session interface {
|
||||
Engine
|
||||
And(query any, args ...any) *xorm.Session
|
||||
@@ -89,7 +95,6 @@ type EngineMigration interface {
|
||||
Dialect() dialects.Dialect
|
||||
DropTables(beans ...any) error
|
||||
NewSession() *xorm.Session
|
||||
QueryInterface(sqlOrArgs ...any) ([]map[string]any, error)
|
||||
SetMapper(mapper names.Mapper)
|
||||
SyncWithOptions(opts xorm.SyncOptions, beans ...any) (*xorm.SyncResult, error)
|
||||
TableInfo(bean any) (*schemas.Table, error)
|
||||
|
||||
+2
-3
@@ -24,10 +24,9 @@ type Paginator interface {
|
||||
}
|
||||
|
||||
// SetSessionPagination sets pagination for a database session
|
||||
func SetSessionPagination(sess Engine, p Paginator) Session {
|
||||
func SetSessionPagination(sess Engine, p Paginator) {
|
||||
skip, take := p.GetSkipTake()
|
||||
|
||||
return sess.Limit(take, skip)
|
||||
sess.Limit(take, skip)
|
||||
}
|
||||
|
||||
// ListOptions options to paginate results
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/translation"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
@@ -119,7 +120,7 @@ WHEN NOT MATCHED
|
||||
func GetNextCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) {
|
||||
_, err := git.NewIDFromString(sha)
|
||||
if err != nil {
|
||||
return 0, git.ErrInvalidSHA{SHA: sha}
|
||||
return 0, util.NewInvalidArgumentErrorf("invalid sha: %v", err)
|
||||
}
|
||||
|
||||
switch {
|
||||
|
||||
@@ -414,7 +414,7 @@ func (pr *PullRequest) getReviewedByLines(ctx context.Context, writer io.Writer)
|
||||
}
|
||||
|
||||
// GetGitHeadRefName returns git ref for hidden pull request branch
|
||||
func (pr *PullRequest) GetGitHeadRefName() string {
|
||||
func (pr *PullRequest) GetGitHeadRefName() string { // TODO: make it return RefName but not string
|
||||
return fmt.Sprintf("%s%d/head", git.PullPrefix, pr.Index)
|
||||
}
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ func PullRequests(ctx context.Context, baseRepoID int64, opts *PullRequestsOptio
|
||||
|
||||
findSession := listPullRequestStatement(ctx, baseRepoID, opts)
|
||||
applySorts(findSession, opts.SortType, 0)
|
||||
findSession = db.SetSessionPagination(findSession, opts)
|
||||
db.SetSessionPagination(findSession, opts)
|
||||
prs := make([]*PullRequest, 0, opts.PageSize)
|
||||
found := findSession.Find(&prs)
|
||||
return prs, maxResults, found
|
||||
|
||||
@@ -414,6 +414,8 @@ func prepareMigrationTasks() []*migration {
|
||||
newMigration(334, "Add cancelling support to action runners", v1_27.AddCancellingSupportToActionRunner),
|
||||
newMigration(335, "Add reusable workflow fields and action_run_attempt_job_id_index table for ActionRunJob", v1_27.AddReusableWorkflowFieldsToActionRunJob),
|
||||
newMigration(336, "Add ActionRunJobSummary table", v1_27.AddActionRunJobSummaryTable),
|
||||
newMigration(337, "Add visibility to team", v1_27.AddVisibilityToTeam),
|
||||
newMigration(338, "Expand legacy MSSQL issue/comment long-text columns", v1_27.ExpandIssueAndCommentLongTextFieldsForMSSQL),
|
||||
}
|
||||
return preparedMigrations
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"gitea.dev/models/db"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type VisibleType int
|
||||
|
||||
type teamWithVisibility struct {
|
||||
Visibility VisibleType `xorm:"NOT NULL DEFAULT 2"`
|
||||
}
|
||||
|
||||
func (teamWithVisibility) TableName() string {
|
||||
return "team"
|
||||
}
|
||||
|
||||
func AddVisibilityToTeam(x db.EngineMigration) error {
|
||||
if _, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||
IgnoreDropIndices: true,
|
||||
IgnoreConstrains: true,
|
||||
}, new(teamWithVisibility)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Owner teams must remain listable to all org members; new orgs create
|
||||
// them as "limited", so make existing owner teams limited too.
|
||||
// Filter on authorize=4 (AccessModeOwner) so a user-created team that
|
||||
// happens to share the name "owners" is not accidentally affected.
|
||||
_, err := x.Exec("UPDATE `team` SET visibility = ? WHERE lower_name = ? AND authorize = ?", 1, "owners", 4)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/migrations/base"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
type issueWithLongTextContent struct {
|
||||
Content string `xorm:"LONGTEXT"`
|
||||
}
|
||||
|
||||
func (issueWithLongTextContent) TableName() string {
|
||||
return "issue"
|
||||
}
|
||||
|
||||
type commentWithLongTextFields struct {
|
||||
Content string `xorm:"LONGTEXT"`
|
||||
PatchQuoted string `xorm:"LONGTEXT patch"`
|
||||
}
|
||||
|
||||
func (commentWithLongTextFields) TableName() string {
|
||||
return "comment"
|
||||
}
|
||||
|
||||
func isMSSQLMaxTextColumn(column *schemas.Column) bool {
|
||||
if column.Length != -1 {
|
||||
return false
|
||||
}
|
||||
return strings.EqualFold(column.SQLType.Name, schemas.Varchar) || strings.EqualFold(column.SQLType.Name, schemas.NVarchar)
|
||||
}
|
||||
|
||||
func modifyLongTextColumnsForMSSQL(x db.EngineMigration, bean any, columnNames ...string) error {
|
||||
table, err := x.TableInfo(bean)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, columnName := range columnNames {
|
||||
column := table.GetColumn(columnName)
|
||||
if column == nil {
|
||||
return fmt.Errorf("column %s does not exist in table %s", columnName, table.Name)
|
||||
}
|
||||
if isMSSQLMaxTextColumn(column) {
|
||||
continue
|
||||
}
|
||||
if err := base.ModifyColumn(x, table.Name, column); err != nil {
|
||||
return fmt.Errorf("modify %s.%s: %w", table.Name, columnName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExpandIssueAndCommentLongTextFieldsForMSSQL expands legacy MSSQL nvarchar(4000)
|
||||
// columns to nvarchar(max) so PR push comments and long issue content are not truncated.
|
||||
func ExpandIssueAndCommentLongTextFieldsForMSSQL(x db.EngineMigration) error {
|
||||
if x.Dialect().URI().DBType != schemas.MSSQL {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := modifyLongTextColumnsForMSSQL(x, new(issueWithLongTextContent), "content"); err != nil {
|
||||
return err
|
||||
}
|
||||
return modifyLongTextColumnsForMSSQL(x, new(commentWithLongTextFields), "content", "patch")
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/models/migrations/migrationtest"
|
||||
"gitea.dev/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type issueBeforeLongTextMSSQLMigration struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Content string `xorm:"VARCHAR(4000)"`
|
||||
}
|
||||
|
||||
func (issueBeforeLongTextMSSQLMigration) TableName() string {
|
||||
return "issue"
|
||||
}
|
||||
|
||||
type commentBeforeLongTextMSSQLMigration struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Content string `xorm:"VARCHAR(4000)"`
|
||||
Patch string `xorm:"VARCHAR(4000) patch"`
|
||||
}
|
||||
|
||||
func (commentBeforeLongTextMSSQLMigration) TableName() string {
|
||||
return "comment"
|
||||
}
|
||||
|
||||
func Test_ExpandIssueAndCommentLongTextFieldsForMSSQL(t *testing.T) {
|
||||
if !setting.Database.Type.IsMSSQL() {
|
||||
t.Skip("Only MSSQL needs to expand legacy nvarchar(4000) long-text columns")
|
||||
}
|
||||
|
||||
x, deferrable := migrationtest.PrepareTestEnv(t, 0, new(issueBeforeLongTextMSSQLMigration), new(commentBeforeLongTextMSSQLMigration))
|
||||
defer deferrable()
|
||||
|
||||
require.NoError(t, ExpandIssueAndCommentLongTextFieldsForMSSQL(x))
|
||||
require.NoError(t, ExpandIssueAndCommentLongTextFieldsForMSSQL(x))
|
||||
|
||||
longText := strings.Repeat("x", 5000)
|
||||
_, err := x.Insert(&issueBeforeLongTextMSSQLMigration{Content: longText})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = x.Insert(&commentBeforeLongTextMSSQLMigration{Content: longText, Patch: longText})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -370,6 +370,7 @@ func CreateOrganization(ctx context.Context, org *Organization, owner *user_mode
|
||||
NumMembers: 1,
|
||||
IncludesAllRepositories: true,
|
||||
CanCreateOrgRepo: true,
|
||||
Visibility: structs.VisibleTypeLimited,
|
||||
}
|
||||
if err = db.Insert(ctx, t); err != nil {
|
||||
return fmt.Errorf("insert owner team: %w", err)
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"gitea.dev/models/unit"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
@@ -81,9 +82,36 @@ type Team struct {
|
||||
Members []*user_model.User `xorm:"-"`
|
||||
NumRepos int
|
||||
NumMembers int
|
||||
Units []*TeamUnit `xorm:"-"`
|
||||
IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"`
|
||||
CanCreateOrgRepo bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Units []*TeamUnit `xorm:"-"`
|
||||
IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"`
|
||||
CanCreateOrgRepo bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 2"`
|
||||
}
|
||||
|
||||
func (t *Team) IsPublic() bool { return t.Visibility.IsPublic() }
|
||||
func (t *Team) IsLimited() bool { return t.Visibility.IsLimited() }
|
||||
func (t *Team) IsPrivate() bool { return t.Visibility.IsPrivate() }
|
||||
|
||||
// CanNonMemberReadMeta reports whether a non-member, non-owner doer may read
|
||||
// the team's metadata, based on the team's visibility tier and the parent org's
|
||||
// visibility. Privileged callers (site admins, org owners, team members) are
|
||||
// decided by the caller before reaching here.
|
||||
func (t *Team) CanNonMemberReadMeta(ctx context.Context, org, doer *user_model.User) (bool, error) {
|
||||
switch t.Visibility {
|
||||
case structs.VisibleTypePublic:
|
||||
return HasOrgOrUserVisible(ctx, org, doer), nil
|
||||
case structs.VisibleTypeLimited:
|
||||
return IsOrganizationMember(ctx, t.OrgID, doer.ID)
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func NormalizeTeamVisibility(s string) structs.VisibleType {
|
||||
if vt, ok := structs.VisibilityModes[s]; ok {
|
||||
return vt
|
||||
}
|
||||
return structs.VisibleTypePrivate
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/perm"
|
||||
"gitea.dev/models/unit"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/structs"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
@@ -50,9 +52,15 @@ type SearchTeamOptions struct {
|
||||
Keyword string
|
||||
OrgID int64
|
||||
IncludeDesc bool
|
||||
// IncludeVisibilities, when combined with UserID, also returns teams whose
|
||||
// visibility is in this list, even if UserID is not a member. Typical values:
|
||||
// - {limited,public} for org members
|
||||
// - {public} for signed-in users who are not org members
|
||||
// Leave empty to return only teams the user is a member of.
|
||||
IncludeVisibilities []structs.VisibleType
|
||||
}
|
||||
|
||||
func (opts *SearchTeamOptions) toCond() builder.Cond {
|
||||
func (opts *SearchTeamOptions) applyToSession(sess db.SQLSession) {
|
||||
cond := builder.NewCond()
|
||||
|
||||
if len(opts.Keyword) > 0 {
|
||||
@@ -68,11 +76,51 @@ func (opts *SearchTeamOptions) toCond() builder.Cond {
|
||||
cond = cond.And(builder.Eq{"`team`.org_id": opts.OrgID})
|
||||
}
|
||||
|
||||
if opts.UserID > 0 {
|
||||
switch {
|
||||
case opts.UserID > 0 && len(opts.IncludeVisibilities) > 0:
|
||||
sess = sess.Join("LEFT", "team_user", "team_user.team_id = team.id AND team_user.uid = ?", opts.UserID)
|
||||
cond = cond.And(builder.Or(
|
||||
builder.Eq{"team_user.uid": opts.UserID},
|
||||
builder.In("`team`.visibility", opts.IncludeVisibilities),
|
||||
))
|
||||
case opts.UserID > 0:
|
||||
sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id")
|
||||
cond = cond.And(builder.Eq{"team_user.uid": opts.UserID})
|
||||
case len(opts.IncludeVisibilities) > 0:
|
||||
cond = cond.And(builder.In("`team`.visibility", opts.IncludeVisibilities))
|
||||
}
|
||||
sess.Where(cond)
|
||||
}
|
||||
|
||||
return cond
|
||||
func VisibleTeamVisibilitiesFor(isOrgMember, isSignedIn bool) []structs.VisibleType {
|
||||
switch {
|
||||
case isOrgMember:
|
||||
return []structs.VisibleType{structs.VisibleTypeLimited, structs.VisibleTypePublic}
|
||||
case isSignedIn:
|
||||
return []structs.VisibleType{structs.VisibleTypePublic}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func ApplyTeamListFilter(ctx context.Context, orgID int64, viewer *user_model.User, isSignedIn bool, opts *SearchTeamOptions) error {
|
||||
if viewer.IsAdmin {
|
||||
return nil
|
||||
}
|
||||
isOwner, err := IsOrganizationOwner(ctx, orgID, viewer.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isOwner {
|
||||
return nil
|
||||
}
|
||||
isOrgMember, err := IsOrganizationMember(ctx, orgID, viewer.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.UserID = viewer.ID
|
||||
opts.IncludeVisibilities = VisibleTeamVisibilitiesFor(isOrgMember, isSignedIn)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SearchTeam search for teams. Caller is responsible to check permissions.
|
||||
@@ -80,15 +128,12 @@ func SearchTeam(ctx context.Context, opts *SearchTeamOptions) (TeamList, int64,
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
opts.SetDefaultValues()
|
||||
cond := opts.toCond()
|
||||
opts.applyToSession(sess)
|
||||
|
||||
if opts.UserID > 0 {
|
||||
sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id")
|
||||
}
|
||||
db.SetSessionPagination(sess, opts)
|
||||
|
||||
teams := make([]*Team, 0, opts.PageSize)
|
||||
count, err := sess.Where(cond).OrderBy("CASE WHEN name=? THEN '' ELSE lower_name END", OwnerTeamName).FindAndCount(&teams)
|
||||
count, err := sess.OrderBy("CASE WHEN name=? THEN '' ELSE lower_name END", OwnerTeamName).FindAndCount(&teams)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"gitea.dev/models/organization"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unittest"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/structs"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -38,6 +40,43 @@ func TestTeam_IsMember(t *testing.T) {
|
||||
assert.False(t, team.IsMember(t.Context(), unittest.NonexistentID))
|
||||
}
|
||||
|
||||
func TestTeam_CanNonMemberReadMeta(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // public org
|
||||
org35 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 35}) // private org
|
||||
member := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // member of org 3 and org 35
|
||||
outsider := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) // member of neither org
|
||||
|
||||
test := func(name string, team *organization.Team, org, doer *user_model.User, expected bool) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ok, err := team.CanNonMemberReadMeta(t.Context(), org, doer)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, ok)
|
||||
})
|
||||
}
|
||||
|
||||
// Public team is gated only by the parent org's visibility.
|
||||
publicTeam := &organization.Team{OrgID: 3, Visibility: structs.VisibleTypePublic}
|
||||
test("public team, public org, member", publicTeam, org3, member, true)
|
||||
test("public team, public org, outsider", publicTeam, org3, outsider, true)
|
||||
|
||||
// Public team inside a private org: only org members may see it.
|
||||
publicTeamPrivOrg := &organization.Team{OrgID: 35, Visibility: structs.VisibleTypePublic}
|
||||
test("public team, private org, org member", publicTeamPrivOrg, org35, member, true)
|
||||
test("public team, private org, outsider", publicTeamPrivOrg, org35, outsider, false)
|
||||
|
||||
// Limited team: any org member, but never outsiders.
|
||||
limitedTeam := &organization.Team{OrgID: 3, Visibility: structs.VisibleTypeLimited}
|
||||
test("limited team, org member", limitedTeam, org3, member, true)
|
||||
test("limited team, outsider", limitedTeam, org3, outsider, false)
|
||||
|
||||
// Private team is never visible to non-members; members/owners are admitted by the caller.
|
||||
privateTeam := &organization.Team{OrgID: 3, Visibility: structs.VisibleTypePrivate}
|
||||
test("private team, org member", privateTeam, org3, member, false)
|
||||
test("private team, outsider", privateTeam, org3, outsider, false)
|
||||
}
|
||||
|
||||
func TestTeam_GetRepositories(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
@@ -172,6 +211,52 @@ func TestGetUserOrgTeams(t *testing.T) {
|
||||
test(3, unittest.NonexistentID)
|
||||
}
|
||||
|
||||
func TestSearchTeamIncludeVisible(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
const orgID int64 = 3
|
||||
// User 5 is an org member but only belongs to team 1 (Owners) — make sure
|
||||
// they don't see team 2 (default private) but do see a freshly added
|
||||
// limited team they are not a member of.
|
||||
visible := &organization.Team{
|
||||
OrgID: orgID,
|
||||
LowerName: "visible-team",
|
||||
Name: "visible-team",
|
||||
AccessMode: 1, // read
|
||||
Visibility: structs.VisibleTypeLimited,
|
||||
}
|
||||
assert.NoError(t, db.Insert(t.Context(), visible))
|
||||
teams, _, err := organization.SearchTeam(t.Context(), &organization.SearchTeamOptions{
|
||||
OrgID: orgID,
|
||||
UserID: 2,
|
||||
IncludeVisibilities: organization.VisibleTeamVisibilitiesFor(true, true),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
ids := make(map[int64]bool, len(teams))
|
||||
for _, team := range teams {
|
||||
assert.Equal(t, orgID, team.OrgID)
|
||||
ids[team.ID] = true
|
||||
}
|
||||
// user 2 is in team 1 and team 2 in org 3, plus should see the new visible team.
|
||||
assert.True(t, ids[1], "expected to see team 1 (member)")
|
||||
assert.True(t, ids[2], "expected to see team 2 (member)")
|
||||
assert.True(t, ids[visible.ID], "expected to see visible team")
|
||||
|
||||
// user 5 is only an org member in team 1, must not see secret team 2 but must see the visible one.
|
||||
teams, _, err = organization.SearchTeam(t.Context(), &organization.SearchTeamOptions{
|
||||
OrgID: orgID,
|
||||
UserID: 5,
|
||||
IncludeVisibilities: organization.VisibleTeamVisibilitiesFor(true, true),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
ids = make(map[int64]bool, len(teams))
|
||||
for _, team := range teams {
|
||||
ids[team.ID] = true
|
||||
}
|
||||
assert.False(t, ids[2], "user 5 must not see private team 2")
|
||||
assert.True(t, ids[visible.ID], "user 5 must see the limited team")
|
||||
}
|
||||
|
||||
func TestHasTeamRepo(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
|
||||
@@ -783,7 +783,8 @@ func GetUserRepositories(ctx context.Context, opts SearchRepoOptions) (Repositor
|
||||
|
||||
sess = sess.Where(cond).OrderBy(opts.OrderBy.String())
|
||||
repos := make(RepositoryList, 0, opts.PageSize)
|
||||
return repos, count, db.SetSessionPagination(sess, &opts).Find(&repos)
|
||||
db.SetSessionPagination(sess, &opts)
|
||||
return repos, count, sess.Find(&repos)
|
||||
}
|
||||
|
||||
func GetOwnerRepositoriesByIDs(ctx context.Context, ownerID int64, repoIDs []int64) (RepositoryList, error) {
|
||||
|
||||
@@ -130,13 +130,9 @@ func (c *Commit) CommitsBeforeLimit(num int) ([]*Commit, error) {
|
||||
return c.repo.getCommitsBeforeLimit(c.ID, num)
|
||||
}
|
||||
|
||||
// CommitsBeforeUntil returns the commits between commitID to current revision
|
||||
func (c *Commit) CommitsBeforeUntil(commitID string) ([]*Commit, error) {
|
||||
endCommit, err := c.repo.GetCommit(commitID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.repo.CommitsBetween(c, endCommit)
|
||||
// CommitsBeforeUntil returns the commits in range "[cur, ref)"
|
||||
func (c *Commit) CommitsBeforeUntil(ref RefName) ([]*Commit, error) {
|
||||
return c.repo.CommitsBetween(c.ID.RefName(), ref, -1)
|
||||
}
|
||||
|
||||
// SearchCommitsOptions specify the parameters for SearchCommits
|
||||
@@ -257,11 +253,15 @@ func IsStringLikelyCommitID(objFmt ObjectFormat, s string, minLength ...int) boo
|
||||
if len(s) < minLen || len(s) > maxLen {
|
||||
return false
|
||||
}
|
||||
return isStringLowerHex(s)
|
||||
}
|
||||
|
||||
func isStringLowerHex(s string) bool {
|
||||
for _, c := range s {
|
||||
isHex := (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')
|
||||
if !isHex {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return len(s) > 0 // it accepts odd length because "shorten commit id" can be 7-chars
|
||||
}
|
||||
|
||||
@@ -70,9 +70,11 @@ func (c *CommitMessage) MessageTrailer() CommitMessageTrailerValues {
|
||||
|
||||
var commitMessageTrailerSplit = sync.OnceValue(func() *regexp.Regexp {
|
||||
// the sep is either something like "\n---\n" or "\n\n" in the body, or at the start of the body like "---\n"
|
||||
return regexp.MustCompile(`(?s)^(?P<content>.*?)(?P<sep>^|^\n|^-{3,}\n|\n-{3,}\n|\n\n)(?P<trailer>(?:[A-Za-z0-9][-A-Za-z0-9]*:[^\n]*\n?)*)$`)
|
||||
return regexp.MustCompile(`(?s)^(?P<content>.*?)(?P<sep>^|^\n|^-{3,}\n+|\n-{3,}\n+|\n\n)(?P<trailer>(?:[A-Za-z0-9][-A-Za-z0-9]*:[^\n]*\n?)*\n*)$`)
|
||||
})
|
||||
|
||||
// CommitMessageSplitTrailer tries to split the message by the trailer separator
|
||||
// content + sep + trailer will reconstruct the original message
|
||||
func CommitMessageSplitTrailer(s string) (content, sep, trailer string) {
|
||||
s = util.NormalizeStringEOL(s)
|
||||
re := commitMessageTrailerSplit()
|
||||
|
||||
@@ -26,8 +26,10 @@ func TestCommitMessageTrailer(t *testing.T) {
|
||||
{"a", "a", "", ""},
|
||||
{"a\n\nk", "a\n\nk", "", ""},
|
||||
{"a\n\nk:v", "a", "\n\n", "k:v"},
|
||||
{"a\n\nk:v\n\n", "a", "\n\n", "k:v\n\n"},
|
||||
{"a\n--\nk:v", "a\n--\nk:v", "", ""},
|
||||
{"a\n---\nk:v", "a", "\n---\n", "k:v"},
|
||||
{"a\n\n---\n\nk:v", "a\n", "\n---\n\n", "k:v"},
|
||||
|
||||
{"k: v", "", "", "k: v"},
|
||||
{"\nk:v", "", "\n", "k:v"},
|
||||
|
||||
@@ -199,3 +199,10 @@ func Test_GetCommitBranchStart(t *testing.T) {
|
||||
assert.NotEmpty(t, startCommitID)
|
||||
assert.Equal(t, "95bb4d39648ee7e325106df01a621c530863a653", startCommitID)
|
||||
}
|
||||
|
||||
func TestIsStringLikelyCommitID(t *testing.T) {
|
||||
assert.True(t, IsStringLikelyCommitID(nil, "abc", 3))
|
||||
assert.False(t, IsStringLikelyCommitID(nil, "abc", 4))
|
||||
assert.True(t, IsStringLikelyCommitID(nil, strings.Repeat("a", 64), 4))
|
||||
assert.False(t, IsStringLikelyCommitID(nil, strings.Repeat("a", 65), 4))
|
||||
}
|
||||
|
||||
@@ -445,6 +445,17 @@ func (c *Command) Start(ctx context.Context) (retErr error) {
|
||||
c.cmd.Stdout = c.cmdStdout
|
||||
c.cmd.Stdin = c.cmdStdin
|
||||
c.cmd.Stderr = c.cmdStderr
|
||||
c.cmd.Cancel = func() error {
|
||||
// Golang's default cmd.Cancel only calls Process.Kill(), but here we need to close the parent pipes together:
|
||||
// * for some commands like "git --batch-xxx", Windows git might have 2 processes (a wrapper and a real git process)
|
||||
// * on Windows, if parent process is killed (context canceled), the children process won't be killed, and the pipe handles are still open.
|
||||
// * if we don't close the parent pipes here, the children process won't exit.
|
||||
//
|
||||
// There is no such problem on POSIX, while it won't make things worse by closing the parent pipes also on POSIX.
|
||||
err := c.cmd.Process.Kill()
|
||||
c.closePipeFiles(c.parentPipeFiles)
|
||||
return err
|
||||
}
|
||||
return c.cmd.Start()
|
||||
}
|
||||
|
||||
|
||||
+13
-10
@@ -10,6 +10,7 @@ import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
)
|
||||
|
||||
@@ -69,9 +70,10 @@ func IsErrorCanceledOrKilled(err error) bool {
|
||||
return errors.Is(err, context.Canceled) || IsErrorSignalKilled(err)
|
||||
}
|
||||
|
||||
type StderrPrefix string
|
||||
|
||||
type StderrSubStr string
|
||||
type (
|
||||
StderrPrefix string
|
||||
StderrWildcard string
|
||||
)
|
||||
|
||||
const (
|
||||
StderrNotValidObjectName StderrPrefix = "fatal: not a valid object name"
|
||||
@@ -82,11 +84,11 @@ const (
|
||||
StderrNoSuchRemote1 StderrPrefix = "fatal: no such remote" // git < 2.30, exit status 128
|
||||
StderrNoSuchRemote2 StderrPrefix = "error: no such remote" // git >= 2.30. exit status 2
|
||||
|
||||
// fatal: ambiguous argument 'origin': unknown revision or path not in the working tree.
|
||||
StderrUnknownRevisionOrPath StderrSubStr = "unknown revision or path not in the working tree"
|
||||
StderrUnknownRevisionOrPath StderrWildcard = "fatal: *: unknown revision or path not in the working tree"
|
||||
StderrNoMergeBase StderrWildcard = "fatal: *: no merge base"
|
||||
)
|
||||
|
||||
func IsStderr[T StderrPrefix | StderrSubStr](err error, check T) bool {
|
||||
func IsStderr[T StderrPrefix | StderrWildcard](err error, check T) bool {
|
||||
stderr, ok := ErrorAsStderr(err)
|
||||
if !ok {
|
||||
return false
|
||||
@@ -100,9 +102,11 @@ func IsStderr[T StderrPrefix | StderrSubStr](err error, check T) bool {
|
||||
// Git is lowercasing the "fatal: Not a valid object name" error message
|
||||
// ref: https://lore.kernel.org/git/pull.2052.git.1771836302101.gitgitgadget@gmail.com
|
||||
return util.AsciiEqualFold(stderr[:checkLen], string(check))
|
||||
case StderrSubStr:
|
||||
return strings.Contains(stderr, string(check))
|
||||
case StderrWildcard:
|
||||
prefix, remaining, _ := strings.Cut(string(check), "*")
|
||||
return strings.HasPrefix(stderr, prefix) && strings.Contains(stderr, remaining)
|
||||
}
|
||||
setting.PanicInDevOrTesting("invalid stderr type %T", check)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -122,8 +126,7 @@ func wrapPipelineError(err error) error {
|
||||
}
|
||||
|
||||
func UnwrapPipelineError(err error) (error, bool) { //nolint:revive // this is for error unwrapping
|
||||
var pe pipelineError
|
||||
if errors.As(err, &pe) {
|
||||
if pe, ok := errors.AsType[pipelineError](err); ok {
|
||||
return pe.error, true
|
||||
}
|
||||
return nil, false
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package gitcmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsStderr(t *testing.T) {
|
||||
cases := []struct {
|
||||
check StderrWildcard
|
||||
stderr string
|
||||
}{
|
||||
{StderrUnknownRevisionOrPath, "fatal: ambiguous argument 'origin': unknown revision or path not in the working tree...."},
|
||||
{StderrNoMergeBase, "fatal: origin/main..HEAD: no merge base...."},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
assert.True(t, IsStderr(&runStdError{stderr: tc.stderr}, tc.check), "stderr: %s", tc.stderr)
|
||||
}
|
||||
}
|
||||
+13
-10
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
type ObjectID interface {
|
||||
String() string
|
||||
RefName() RefName
|
||||
IsZero() bool
|
||||
RawValue() []byte
|
||||
Type() ObjectFormat
|
||||
@@ -18,10 +19,16 @@ type ObjectID interface {
|
||||
|
||||
type Sha1Hash [20]byte
|
||||
|
||||
var _ ObjectID = (*Sha1Hash)(nil)
|
||||
|
||||
func (h *Sha1Hash) String() string {
|
||||
return hex.EncodeToString(h[:])
|
||||
}
|
||||
|
||||
func (h *Sha1Hash) RefName() RefName {
|
||||
return RefName(h.String())
|
||||
}
|
||||
|
||||
func (h *Sha1Hash) IsZero() bool {
|
||||
empty := Sha1Hash{}
|
||||
return bytes.Equal(empty[:], h[:])
|
||||
@@ -29,8 +36,6 @@ func (h *Sha1Hash) IsZero() bool {
|
||||
func (h *Sha1Hash) RawValue() []byte { return h[:] }
|
||||
func (*Sha1Hash) Type() ObjectFormat { return Sha1ObjectFormat }
|
||||
|
||||
var _ ObjectID = &Sha1Hash{}
|
||||
|
||||
func MustIDFromString(hexHash string) ObjectID {
|
||||
id, err := NewIDFromString(hexHash)
|
||||
if err != nil {
|
||||
@@ -41,10 +46,16 @@ func MustIDFromString(hexHash string) ObjectID {
|
||||
|
||||
type Sha256Hash [32]byte
|
||||
|
||||
var _ ObjectID = (*Sha256Hash)(nil)
|
||||
|
||||
func (h *Sha256Hash) String() string {
|
||||
return hex.EncodeToString(h[:])
|
||||
}
|
||||
|
||||
func (h *Sha256Hash) RefName() RefName {
|
||||
return RefName(h.String())
|
||||
}
|
||||
|
||||
func (h *Sha256Hash) IsZero() bool {
|
||||
empty := Sha256Hash{}
|
||||
return bytes.Equal(empty[:], h[:])
|
||||
@@ -93,11 +104,3 @@ func IsEmptyCommitID(commitID string) bool {
|
||||
func ComputeBlobHash(hashType ObjectFormat, content []byte) ObjectID {
|
||||
return hashType.ComputeHash(ObjectBlob, content)
|
||||
}
|
||||
|
||||
type ErrInvalidSHA struct {
|
||||
SHA string
|
||||
}
|
||||
|
||||
func (err ErrInvalidSHA) Error() string {
|
||||
return "invalid sha: " + err.SHA
|
||||
}
|
||||
|
||||
+8
-1
@@ -7,6 +7,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
)
|
||||
|
||||
@@ -72,6 +73,8 @@ const ForPrefix = "refs/for/"
|
||||
// RefName represents a full git reference name
|
||||
type RefName string
|
||||
|
||||
const RefNameHead = "HEAD"
|
||||
|
||||
func RefNameFromBranch(shortName string) RefName {
|
||||
return RefName(BranchPrefix + shortName)
|
||||
}
|
||||
@@ -81,6 +84,10 @@ func RefNameFromTag(shortName string) RefName {
|
||||
}
|
||||
|
||||
func RefNameFromCommit(shortName string) RefName {
|
||||
if !isStringLowerHex(shortName) {
|
||||
setting.PanicInDevOrTesting("BUG! invalid commit id %s", shortName)
|
||||
return RefName("refs/invalid-commit/" + shortName)
|
||||
}
|
||||
return RefName(shortName)
|
||||
}
|
||||
|
||||
@@ -161,7 +168,7 @@ func (ref RefName) ShortName() string {
|
||||
if ref.IsFor() {
|
||||
return ref.ForBranchName()
|
||||
}
|
||||
return string(ref) // usually it is a commit ID
|
||||
return string(ref) // usually it is a commit ID, or "HEAD"
|
||||
}
|
||||
|
||||
// RefGroup returns the group type of the reference
|
||||
|
||||
+21
-99
@@ -36,25 +36,17 @@ func (repo *Repository) GetCommit(ref string) (*Commit, error) {
|
||||
|
||||
// GetBranchCommit returns the last commit of given branch.
|
||||
func (repo *Repository) GetBranchCommit(name string) (*Commit, error) {
|
||||
commitID, err := repo.GetBranchCommitID(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return repo.GetCommit(commitID)
|
||||
return repo.GetCommit(RefNameFromBranch(name).String())
|
||||
}
|
||||
|
||||
// GetTagCommit get the commit of the specific tag via name
|
||||
func (repo *Repository) GetTagCommit(name string) (*Commit, error) {
|
||||
commitID, err := repo.GetTagCommitID(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return repo.GetCommit(commitID)
|
||||
return repo.GetCommit(RefNameFromTag(name).String())
|
||||
}
|
||||
|
||||
func (repo *Repository) getCommitByPathWithID(id ObjectID, relpath string) (*Commit, error) {
|
||||
// File name starts with ':' must be escaped.
|
||||
if relpath[0] == ':' {
|
||||
if strings.HasPrefix(relpath, ":") {
|
||||
relpath = `\` + relpath
|
||||
}
|
||||
|
||||
@@ -287,48 +279,29 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
|
||||
return commits, hasMore, err
|
||||
}
|
||||
|
||||
// FilesCountBetween return the number of files changed between two commits
|
||||
func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) {
|
||||
stdout, _, err := gitcmd.NewCommand("diff", "--name-only").
|
||||
AddDynamicArguments(startCommitID + "..." + endCommitID).
|
||||
WithDir(repo.Path).
|
||||
RunStdString(repo.Ctx)
|
||||
if err != nil && strings.Contains(err.Error(), "no merge base") {
|
||||
// git >= 2.28 now returns an error if startCommitID and endCommitID have become unrelated.
|
||||
// previously it would return the results of git diff --name-only startCommitID endCommitID so let's try that...
|
||||
stdout, _, err = gitcmd.NewCommand("diff", "--name-only").
|
||||
AddDynamicArguments(startCommitID, endCommitID).
|
||||
WithDir(repo.Path).
|
||||
RunStdString(repo.Ctx)
|
||||
// CommitsBetween returns a list that contains commits between [after, before). After is the first item in the slice.
|
||||
// If "before" and "after" are not related, it returns the all commits for the "after" commit.
|
||||
func (repo *Repository) CommitsBetween(afterRef, beforeRef RefName, limit int, optSkip ...int) ([]*Commit, error) {
|
||||
gitCmd := func() *gitcmd.Command {
|
||||
cmd := gitcmd.NewCommand("rev-list").WithDir(repo.Path)
|
||||
if limit >= 0 {
|
||||
cmd.AddOptionValues("--max-count", strconv.Itoa(limit))
|
||||
}
|
||||
if len(optSkip) > 0 {
|
||||
cmd.AddOptionValues("--skip", strconv.Itoa(optSkip[0]))
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(strings.Split(stdout, "\n")) - 1, nil
|
||||
}
|
||||
|
||||
// CommitsBetween returns a list that contains commits between [before, last).
|
||||
// If before is detached (removed by reset + push) it is not included.
|
||||
func (repo *Repository) CommitsBetween(last, before *Commit) ([]*Commit, error) {
|
||||
var stdout []byte
|
||||
var err error
|
||||
if before == nil {
|
||||
stdout, _, err = gitcmd.NewCommand("rev-list").
|
||||
AddDynamicArguments(last.ID.String()).
|
||||
WithDir(repo.Path).
|
||||
RunStdBytes(repo.Ctx)
|
||||
if beforeRef == "" {
|
||||
stdout, _, err = gitCmd().AddDynamicArguments(afterRef.String()).RunStdBytes(repo.Ctx)
|
||||
} else {
|
||||
stdout, _, err = gitcmd.NewCommand("rev-list").
|
||||
AddDynamicArguments(before.ID.String() + ".." + last.ID.String()).
|
||||
WithDir(repo.Path).
|
||||
RunStdBytes(repo.Ctx)
|
||||
if err != nil && strings.Contains(err.Error(), "no merge base") {
|
||||
stdout, _, err = gitCmd().AddDynamicArguments(beforeRef.String() + ".." + afterRef.String()).RunStdBytes(repo.Ctx)
|
||||
if gitcmd.IsStderr(err, gitcmd.StderrNoMergeBase) {
|
||||
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
|
||||
// previously it would return the results of git rev-list before last so let's try that...
|
||||
stdout, _, err = gitcmd.NewCommand("rev-list").
|
||||
AddDynamicArguments(before.ID.String(), last.ID.String()).
|
||||
WithDir(repo.Path).
|
||||
RunStdBytes(repo.Ctx)
|
||||
// if the beforeRef and afterRef are not related (no merge base), just get all commits pushed by afterRef
|
||||
stdout, _, err = gitCmd().AddDynamicArguments(afterRef.String()).RunStdBytes(repo.Ctx)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
@@ -337,57 +310,6 @@ func (repo *Repository) CommitsBetween(last, before *Commit) ([]*Commit, error)
|
||||
return repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout))
|
||||
}
|
||||
|
||||
// CommitsBetweenLimit returns a list that contains at most limit commits skipping the first skip commits between [before, last)
|
||||
func (repo *Repository) CommitsBetweenLimit(last, before *Commit, limit, skip int) ([]*Commit, error) {
|
||||
var stdout []byte
|
||||
var err error
|
||||
if before == nil {
|
||||
stdout, _, err = gitcmd.NewCommand("rev-list").
|
||||
AddOptionValues("--max-count", strconv.Itoa(limit)).
|
||||
AddOptionValues("--skip", strconv.Itoa(skip)).
|
||||
AddDynamicArguments(last.ID.String()).
|
||||
WithDir(repo.Path).
|
||||
RunStdBytes(repo.Ctx)
|
||||
} else {
|
||||
stdout, _, err = gitcmd.NewCommand("rev-list").
|
||||
AddOptionValues("--max-count", strconv.Itoa(limit)).
|
||||
AddOptionValues("--skip", strconv.Itoa(skip)).
|
||||
AddDynamicArguments(before.ID.String() + ".." + last.ID.String()).
|
||||
WithDir(repo.Path).
|
||||
RunStdBytes(repo.Ctx)
|
||||
if err != nil && strings.Contains(err.Error(), "no merge base") {
|
||||
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
|
||||
// previously it would return the results of git rev-list --max-count n before last so let's try that...
|
||||
stdout, _, err = gitcmd.NewCommand("rev-list").
|
||||
AddOptionValues("--max-count", strconv.Itoa(limit)).
|
||||
AddOptionValues("--skip", strconv.Itoa(skip)).
|
||||
AddDynamicArguments(before.ID.String(), last.ID.String()).
|
||||
WithDir(repo.Path).
|
||||
RunStdBytes(repo.Ctx)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout))
|
||||
}
|
||||
|
||||
// CommitsBetweenIDs return commits between twoe commits
|
||||
func (repo *Repository) CommitsBetweenIDs(last, before string) ([]*Commit, error) {
|
||||
lastCommit, err := repo.GetCommit(last)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if before == "" {
|
||||
return repo.CommitsBetween(lastCommit, nil)
|
||||
}
|
||||
beforeCommit, err := repo.GetCommit(before)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return repo.CommitsBetween(lastCommit, beforeCommit)
|
||||
}
|
||||
|
||||
// commitsBefore the limit is depth, not total number of returned commits.
|
||||
func (repo *Repository) commitsBefore(id ObjectID, limit int) ([]*Commit, error) {
|
||||
cmd := gitcmd.NewCommand("log", prettyLogFormat)
|
||||
|
||||
@@ -23,7 +23,10 @@ func (repo *Repository) GetRefCommitID(name string) (string, error) {
|
||||
}
|
||||
refName := plumbing.ReferenceName(name)
|
||||
if err := refName.Validate(); err != nil {
|
||||
return "", err
|
||||
// Match the nogogit behavior: an unresolvable/invalid ref name
|
||||
// is reported as not-existing rather than a generic validation error,
|
||||
// so callers can rely on IsErrNotExist regardless of build tag.
|
||||
return "", ErrNotExist{ID: name}
|
||||
}
|
||||
ref, err := repo.gogitRepo.Reference(refName, true)
|
||||
if err != nil {
|
||||
|
||||
@@ -85,15 +85,15 @@ func TestIsCommitInBranch(t *testing.T) {
|
||||
assert.False(t, result)
|
||||
}
|
||||
|
||||
func TestRepository_CommitsBetweenIDs(t *testing.T) {
|
||||
func TestRepository_CommitsBetween(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo4_commitsbetween")
|
||||
bareRepo1, err := OpenRepository(t.Context(), bareRepo1Path)
|
||||
assert.NoError(t, err)
|
||||
defer bareRepo1.Close()
|
||||
|
||||
cases := []struct {
|
||||
OldID string
|
||||
NewID string
|
||||
OldID RefName
|
||||
NewID RefName
|
||||
ExpectedCommits int
|
||||
}{
|
||||
{"fdc1b615bdcff0f0658b216df0c9209e5ecb7c78", "78a445db1eac62fe15e624e1137965969addf344", 1}, // com1 -> com2
|
||||
@@ -101,7 +101,7 @@ func TestRepository_CommitsBetweenIDs(t *testing.T) {
|
||||
{"78a445db1eac62fe15e624e1137965969addf344", "a78e5638b66ccfe7e1b4689d3d5684e42c97d7ca", 1}, // com2 -> com2_new
|
||||
}
|
||||
for i, c := range cases {
|
||||
commits, err := bareRepo1.CommitsBetweenIDs(c.NewID, c.OldID)
|
||||
commits, err := bareRepo1.CommitsBetween(c.NewID, c.OldID, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, commits, c.ExpectedCommits, "case %d", i)
|
||||
}
|
||||
|
||||
@@ -40,25 +40,16 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis
|
||||
separator = ".."
|
||||
}
|
||||
|
||||
// avoid: ambiguous argument 'refs/a...refs/b': unknown revision or path not in the working tree. Use '--': 'git <command> [<revision>...] -- [<file>...]'
|
||||
if err := gitcmd.NewCommand("diff", "-z", "--name-only").
|
||||
AddDynamicArguments(base + separator + head).
|
||||
AddArguments("--").
|
||||
WithDir(repo.Path).
|
||||
WithStdoutCopy(w).
|
||||
RunWithStderr(repo.Ctx); err != nil {
|
||||
if strings.Contains(err.Stderr(), "no merge base") {
|
||||
if gitcmd.IsStderr(err, gitcmd.StderrNoMergeBase) {
|
||||
// git >= 2.28 now returns an error if base and head have become unrelated.
|
||||
// previously it would return the results of git diff -z --name-only base head so let's try that...
|
||||
w = &lineCountWriter{}
|
||||
if err = gitcmd.NewCommand("diff", "-z", "--name-only").
|
||||
AddDynamicArguments(base, head).
|
||||
AddArguments("--").
|
||||
WithDir(repo.Path).
|
||||
WithStdoutCopy(w).
|
||||
RunWithStderr(repo.Ctx); err == nil {
|
||||
return w.numLines, nil
|
||||
}
|
||||
// it doesn't make sense to count the changed files in this case because UI won't display such diff
|
||||
return 0, nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"gitea.dev/modules/git/gitcmd"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
)
|
||||
|
||||
@@ -86,8 +87,11 @@ func (repo *Repository) UnstableGuessRefByShortName(shortName string) RefName {
|
||||
commit, err := repo.GetCommit(shortName)
|
||||
if err == nil {
|
||||
commitIDString := commit.ID.String()
|
||||
if strings.HasPrefix(commitIDString, shortName) {
|
||||
// make sure the "shortName" is either partial commit ID, or it is HEAD
|
||||
if strings.HasPrefix(commitIDString, shortName) || shortName == RefNameHead {
|
||||
return RefName(commitIDString)
|
||||
} else {
|
||||
setting.PanicInDevOrTesting("abuse of UnstableGuessRefByShortName, queried %s, got %s", shortName, commitIDString)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
|
||||
@@ -56,11 +56,10 @@ func GetCommitIDsBetweenReverse(ctx context.Context, repo Repository, startRef,
|
||||
return cmd
|
||||
}
|
||||
stdout, _, err := RunCmdString(ctx, repo, genCmd(startRef+".."+endRef))
|
||||
// example git error message: fatal: origin/main..HEAD: no merge base
|
||||
if err != nil && strings.Contains(err.Stderr(), "no merge base") {
|
||||
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
|
||||
if gitcmd.IsStderr(err, gitcmd.StderrNoMergeBase) {
|
||||
// if the start and end are not related (no merge base), just get all commits pushed by "end ref"
|
||||
// previously it would return the results of git rev-list before last so let's try that...
|
||||
stdout, _, err = RunCmdString(ctx, repo, genCmd(startRef, endRef))
|
||||
stdout, _, err = RunCmdString(ctx, repo, genCmd(endRef))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -40,7 +40,7 @@ type contextKey struct {
|
||||
}
|
||||
|
||||
// RepositoryFromContextOrOpen attempts to get the repository from the context or just opens it
|
||||
// The caller must call "defer gitRepo.Close()"
|
||||
// The caller must call Closer.Close()
|
||||
func RepositoryFromContextOrOpen(ctx context.Context, repo Repository) (*git.Repository, io.Closer, error) {
|
||||
reqCtx := reqctx.FromContext(ctx)
|
||||
if reqCtx != nil {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package htmlutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
@@ -88,6 +89,52 @@ func EscapeString(s string) template.HTML {
|
||||
return template.HTML(template.HTMLEscapeString(s))
|
||||
}
|
||||
|
||||
type HTMLWriter interface {
|
||||
OriginWriter() io.Writer
|
||||
WriteString(s string) HTMLWriter
|
||||
WriteHTML(s template.HTML) HTMLWriter
|
||||
WriteFormat(fmt template.HTML, args ...any) HTMLWriter
|
||||
Err() error
|
||||
}
|
||||
|
||||
type htmlWriter struct {
|
||||
w io.Writer
|
||||
errs []error
|
||||
}
|
||||
|
||||
func (h *htmlWriter) OriginWriter() io.Writer {
|
||||
return h.w
|
||||
}
|
||||
|
||||
func (h *htmlWriter) WriteString(s string) HTMLWriter {
|
||||
if _, err := io.WriteString(h.w, template.HTMLEscapeString(s)); err != nil {
|
||||
h.errs = append(h.errs, err)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *htmlWriter) WriteHTML(s template.HTML) HTMLWriter {
|
||||
if _, err := io.WriteString(h.w, string(s)); err != nil {
|
||||
h.errs = append(h.errs, err)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *htmlWriter) WriteFormat(fmt template.HTML, args ...any) HTMLWriter {
|
||||
if _, err := HTMLPrintf(h.w, fmt, args...); err != nil {
|
||||
h.errs = append(h.errs, err)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *htmlWriter) Err() error {
|
||||
return errors.Join(h.errs...)
|
||||
}
|
||||
|
||||
func NewHTMLWriter(w io.Writer) HTMLWriter {
|
||||
return &htmlWriter{w: w}
|
||||
}
|
||||
|
||||
type HTMLBuilder struct {
|
||||
sb strings.Builder
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package htmlutil
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -29,3 +30,11 @@ func TestHTMLBuilder(t *testing.T) {
|
||||
assert.Equal(t, "<<hr><span>>></span>", b.String())
|
||||
assert.Equal(t, template.HTML("<<hr><span>>></span>"), b.HTMLString())
|
||||
}
|
||||
|
||||
func TestHTMLWriter(t *testing.T) {
|
||||
sb := new(strings.Builder)
|
||||
w := NewHTMLWriter(sb)
|
||||
w.WriteString("<").WriteHTML("<hr>").WriteFormat("<span>%s%s</span>", ">", EscapeString(">"))
|
||||
assert.Equal(t, "<<hr><span>>></span>", sb.String())
|
||||
assert.NoError(t, w.Err())
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package asciicast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
"gitea.dev/modules/markup"
|
||||
"gitea.dev/modules/setting"
|
||||
)
|
||||
|
||||
func init() {
|
||||
markup.RegisterRenderer(Renderer{})
|
||||
}
|
||||
|
||||
// Renderer implements markup.Renderer for asciicast files.
|
||||
// See https://github.com/asciinema/asciinema/blob/develop/doc/asciicast-v2.md
|
||||
type Renderer struct{}
|
||||
|
||||
func (Renderer) Name() string {
|
||||
return "asciicast"
|
||||
}
|
||||
|
||||
func (Renderer) FileNamePatterns() []string {
|
||||
return []string{"*.cast"}
|
||||
}
|
||||
|
||||
const (
|
||||
playerClassName = "asciinema-player-container"
|
||||
playerSrcAttr = "data-asciinema-player-src"
|
||||
)
|
||||
|
||||
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
||||
return []setting.MarkupSanitizerRule{{Element: "div", AllowAttr: playerSrcAttr}}
|
||||
}
|
||||
|
||||
func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer) error {
|
||||
rawURL := fmt.Sprintf("%s/%s/%s/raw/%s/%s",
|
||||
setting.AppSubURL,
|
||||
url.PathEscape(ctx.RenderOptions.Metas["user"]),
|
||||
url.PathEscape(ctx.RenderOptions.Metas["repo"]),
|
||||
ctx.RenderOptions.Metas["RefTypeNameSubURL"],
|
||||
url.PathEscape(ctx.RenderOptions.RelativePath),
|
||||
)
|
||||
return ctx.RenderInternal.FormatWithSafeAttrs(output, `<div class="%s" %s="%s"></div>`, playerClassName, playerSrcAttr, rawURL)
|
||||
}
|
||||
Vendored
+5
@@ -48,6 +48,11 @@ func RegisterRenderers() {
|
||||
},
|
||||
})
|
||||
|
||||
markup.RegisterRenderer(&frontendRenderer{
|
||||
name: "asciicast",
|
||||
patterns: []string{"*.cast"},
|
||||
})
|
||||
|
||||
for _, renderer := range setting.ExternalMarkupRenderers {
|
||||
markup.RegisterRenderer(&Renderer{renderer})
|
||||
}
|
||||
|
||||
Vendored
+3
-3
@@ -5,6 +5,7 @@ package external
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
|
||||
@@ -54,14 +55,13 @@ func (p *frontendRenderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
||||
func (p *frontendRenderer) GetExternalRendererOptions() (ret markup.ExternalRendererOptions) {
|
||||
ret.SanitizerDisabled = true
|
||||
ret.DisplayInIframe = true
|
||||
ret.ContentSandbox = "allow-scripts allow-forms allow-modals allow-popups allow-downloads"
|
||||
ret.ContentSandbox = setting.MarkupRenderDefaultSandbox
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p *frontendRenderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
||||
if ctx.RenderOptions.StandalonePageOptions == nil {
|
||||
opts := p.GetExternalRendererOptions()
|
||||
return markup.RenderIFrame(ctx, &opts, output)
|
||||
return errors.New("should only be rendered in standalone page")
|
||||
}
|
||||
|
||||
content, err := util.ReadWithLimit(input, int(setting.UI.MaxDisplayFileSize))
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"metadata": {},
|
||||
"nbformat": 4,
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"source": ["print('very-looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong')"],
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "execute_result",
|
||||
"text": ["very-looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong ...\n"]
|
||||
},
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stdout",
|
||||
"text": ["stdout 1 ...\n", "stdout 2 ...\n"]
|
||||
},
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stderr",
|
||||
"text": ["stderr ...\n"]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": ["data text 1\n", "data text 2\n"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"image/svg+xml": ["<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"2000\" height=\"20\"><rect width=\"2000\" height=\"20\" x=\"0\" y=\"0\" rx=\"5\" ry=\"5\" fill=\"red\"/></svg>"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/html": "<a href='/'>HTML Link</a>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/latex": "$$a=1$$"
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": "plain text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"output_type": "error",
|
||||
"ename": "Error Name",
|
||||
"traceback": ["stacktrace 1", "stacktrace 2"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "unknown-cell"
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"# h1\n", "## h2\n", "### h3\n", "\n", "paragraph 1\n", "\n",
|
||||
"very-looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong\n",
|
||||
"- list item 1\n", "- list item 2\n", "\n", "```python\n", "print('code block')\n", "```\n",
|
||||
"<table><tr><th>th1</th><th>th2</th></tr><tr><td>td1</td><td>td2</td></tr></table>\n"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package jupyter
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gitea.dev/modules/highlight"
|
||||
"gitea.dev/modules/htmlutil"
|
||||
"gitea.dev/modules/json"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/markup"
|
||||
"gitea.dev/modules/markup/markdown"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
markup.RegisterRenderer(renderer{})
|
||||
}
|
||||
|
||||
// Renderer implements markup.Renderer for Jupyter notebooks
|
||||
type renderer struct{}
|
||||
|
||||
var (
|
||||
_ markup.Renderer = (*renderer)(nil)
|
||||
_ markup.PostProcessRenderer = (*renderer)(nil)
|
||||
_ markup.ExternalRenderer = (*renderer)(nil) // FIXME: this is not an external render, need to refactor the framework in the future
|
||||
)
|
||||
|
||||
type mimeHandler struct {
|
||||
Mime string
|
||||
Fn func(w htmlutil.HTMLWriter, data string) error
|
||||
}
|
||||
|
||||
func renderCellCodeOutputTextPlain(w htmlutil.HTMLWriter, text string) error {
|
||||
w.WriteFormat(`<div class="cell-output-text"><pre>%s</pre></div>`, text)
|
||||
return w.Err()
|
||||
}
|
||||
|
||||
func renderCellCodeOutputUnsupported(w htmlutil.HTMLWriter, message string) error {
|
||||
w.WriteFormat(`<div class="cell-output-unsupported">%s</div>`, message)
|
||||
return w.Err()
|
||||
}
|
||||
|
||||
var dataMimeHandlers = sync.OnceValue(func() []mimeHandler {
|
||||
renderImage := func(w htmlutil.HTMLWriter, subtype, payload string) error {
|
||||
w.WriteFormat(`<div class="cell-output-image"><img src="data:image/%s;base64,%s"></div>`, subtype, payload)
|
||||
return w.Err()
|
||||
}
|
||||
renderUnsupportedOutput := func(message string) func(htmlutil.HTMLWriter, string) error {
|
||||
return func(w htmlutil.HTMLWriter, _ string) error {
|
||||
return renderCellCodeOutputUnsupported(w, message)
|
||||
}
|
||||
}
|
||||
return []mimeHandler{
|
||||
// Images (PNG, JPEG, SVG)
|
||||
{"image/png", func(w htmlutil.HTMLWriter, d string) error {
|
||||
return renderImage(w, "png", d)
|
||||
}},
|
||||
{"image/jpeg", func(w htmlutil.HTMLWriter, d string) error {
|
||||
return renderImage(w, "jpeg", d)
|
||||
}},
|
||||
{"image/svg+xml", func(w htmlutil.HTMLWriter, d string) error {
|
||||
return renderImage(w, "svg+xml", base64.StdEncoding.EncodeToString(util.UnsafeStringToBytes(d)))
|
||||
}},
|
||||
|
||||
// Rich & Math Layouts
|
||||
{"text/html", func(w htmlutil.HTMLWriter, d string) error {
|
||||
// To future developers: don't allow custom CSS classes or attributes,
|
||||
// because ".link-action" or "data-fetch-xxx" can send POST requests and lead to XSS.
|
||||
// If you'd really like to support more, do remember to correctly sanitize the values.
|
||||
w.WriteFormat(`<div class="cell-output-html">%s</div>`, markup.Sanitize(d))
|
||||
return w.Err()
|
||||
}},
|
||||
{"text/latex", func(w htmlutil.HTMLWriter, d string) error {
|
||||
w.WriteFormat(`<div class="cell-output-latex"><pre><code class="language-math display">%s</code></pre></div>`, trimMathDelimiters(d))
|
||||
return w.Err()
|
||||
}},
|
||||
{"text/plain", renderCellCodeOutputTextPlain},
|
||||
|
||||
// Security Placeholders
|
||||
{"application/javascript", renderUnsupportedOutput("[JavaScript output - execution disabled for security]")},
|
||||
{"application/vnd.plotly.v1+json", renderUnsupportedOutput("[Plotly output - interactive plots not supported]")},
|
||||
{"application/vnd.jupyter.widget-view+json", renderUnsupportedOutput("[Jupyter widget - interactive widgets not supported]")},
|
||||
}
|
||||
})
|
||||
|
||||
func (renderer) Name() string {
|
||||
return "jupyter-render"
|
||||
}
|
||||
|
||||
func (renderer) NeedPostProcess() bool { return true }
|
||||
|
||||
func (renderer) GetExternalRendererOptions() markup.ExternalRendererOptions {
|
||||
return markup.ExternalRendererOptions{
|
||||
// HINT: no need to let markup render sanitize the output because there are many special CSS class names, inline attributes.
|
||||
// This render must guarantee that the output is safe and no XSS
|
||||
SanitizerDisabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (renderer) FileNamePatterns() []string {
|
||||
return []string{"*.ipynb"}
|
||||
}
|
||||
|
||||
func (renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Notebook structures
|
||||
type Notebook struct {
|
||||
Cells []Cell `json:"cells"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
Nbformat int `json:"nbformat"`
|
||||
}
|
||||
|
||||
type Cell struct {
|
||||
CellType string `json:"cell_type"`
|
||||
Source any `json:"source"` // string or []string
|
||||
Outputs []Output `json:"outputs,omitempty"`
|
||||
ExecutionCount any `json:"execution_count,omitempty"` // int or null
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type Output struct {
|
||||
OutputType string `json:"output_type"`
|
||||
Data map[string]any `json:"data,omitempty"`
|
||||
Text any `json:"text,omitempty"` // string or []string
|
||||
Name string `json:"name,omitempty"`
|
||||
Traceback any `json:"traceback,omitempty"` // []string
|
||||
Ename string `json:"ename,omitempty"`
|
||||
Evalue string `json:"evalue,omitempty"`
|
||||
}
|
||||
|
||||
// Render renders Jupyter notebook to HTML
|
||||
func (renderer) Render(ctx *markup.RenderContext, input io.Reader, outputWriter io.Writer) error {
|
||||
htmlWriter := htmlutil.NewHTMLWriter(outputWriter)
|
||||
// the size is (should be) checked and/or limited by the caller to avoid OOM
|
||||
var notebook Notebook
|
||||
if err := json.NewDecoder(input).Decode(¬ebook); err != nil {
|
||||
htmlWriter.WriteFormat(`<div class="ui error message">Failed to parse notebook JSON: %v</div>`, err)
|
||||
return htmlWriter.Err()
|
||||
}
|
||||
|
||||
// Check nbformat version
|
||||
if notebook.Nbformat < 4 {
|
||||
msg := htmlutil.HTMLFormat("This notebook uses an older format (nbformat %d). Only nbformat 4+ is supported for rendering. Please upgrade the notebook in Jupyter or view the raw JSON.", notebook.Nbformat)
|
||||
htmlWriter.WriteFormat(`<div class="file-not-rendered-prompt">%s</div>`, msg)
|
||||
return htmlWriter.Err()
|
||||
}
|
||||
|
||||
// Detect language
|
||||
language := "python" // default
|
||||
if metadata, ok := notebook.Metadata["language_info"].(map[string]any); ok {
|
||||
if name, ok := metadata["name"].(string); ok {
|
||||
language = name
|
||||
}
|
||||
} else if kernelSpec, ok := notebook.Metadata["kernelspec"].(map[string]any); ok {
|
||||
if lang, ok := kernelSpec["language"].(string); ok {
|
||||
language = lang
|
||||
}
|
||||
}
|
||||
|
||||
// Start rendering
|
||||
htmlWriter.WriteHTML(`<div class="jupyter-notebook">`)
|
||||
|
||||
// limiting the cell rendering to 100 cells
|
||||
cells := notebook.Cells
|
||||
truncated := false
|
||||
const maxRenderedCells = 100
|
||||
|
||||
if len(cells) > maxRenderedCells {
|
||||
cells = cells[:maxRenderedCells] // Slice down to exactly 100 elements instantly at the pointer layer
|
||||
truncated = true
|
||||
}
|
||||
|
||||
for _, cell := range cells {
|
||||
if err := renderCell(ctx, htmlWriter, cell, language); err != nil {
|
||||
log.Warn("Failed to render cell: %v", err) // TODO: RENDER-LOG-HANDLING: see other comments
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if truncated {
|
||||
renderCellPrompt(htmlWriter, "Warning:", "Output truncated. This notebook contains too many cells to display efficiently.")
|
||||
}
|
||||
|
||||
htmlWriter.WriteHTML(`</div>`)
|
||||
return htmlWriter.Err()
|
||||
}
|
||||
|
||||
func renderCellCode(output htmlutil.HTMLWriter, cell Cell, language string) error {
|
||||
source := joinSource(cell.Source)
|
||||
var executionCount *int64
|
||||
if cell.ExecutionCount != nil {
|
||||
if count, err := util.ToInt64(cell.ExecutionCount); err == nil {
|
||||
executionCount = &count
|
||||
}
|
||||
}
|
||||
|
||||
output.WriteHTML(`<div class="cell-line">`)
|
||||
{
|
||||
if executionCount != nil {
|
||||
output.WriteFormat(`<div class="cell-left cell-prompt">In [%d]:</div>`, *executionCount)
|
||||
} else {
|
||||
output.WriteHTML(`<div class="cell-left cell-prompt">In [ ]:</div>`)
|
||||
}
|
||||
|
||||
// Highlight code
|
||||
lexer := highlight.DetectChromaLexerByFileName("", language)
|
||||
output.WriteFormat(`<div class="cell-right cell-input"><pre><code class="chroma language-%s">`, strings.ToLower(language))
|
||||
output.WriteHTML(highlight.RenderCodeByLexer(lexer, source))
|
||||
output.WriteHTML("</code></pre></div>")
|
||||
}
|
||||
output.WriteHTML(`</div>`)
|
||||
|
||||
// Render outputs
|
||||
if len(cell.Outputs) > 0 {
|
||||
hasExecutionResult := false
|
||||
for _, out := range cell.Outputs {
|
||||
if out.OutputType == "execute_result" {
|
||||
hasExecutionResult = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
output.WriteHTML(`<div class="cell-line">`)
|
||||
{
|
||||
if hasExecutionResult && executionCount != nil {
|
||||
output.WriteFormat(`<div class="cell-left cell-prompt">Out [%d]:</div>`, *executionCount)
|
||||
} else {
|
||||
output.WriteHTML(`<div class="cell-left cell-prompt"></div>`)
|
||||
}
|
||||
|
||||
output.WriteHTML(`<div class="cell-right cell-output">`)
|
||||
for _, out := range cell.Outputs {
|
||||
renderCellCodeOutput(output, out)
|
||||
}
|
||||
output.WriteHTML(`</div>`)
|
||||
}
|
||||
output.WriteHTML(`</div>`)
|
||||
}
|
||||
|
||||
return output.Err()
|
||||
}
|
||||
|
||||
func renderCellPrompt(output htmlutil.HTMLWriter, left, right template.HTML) {
|
||||
output.WriteFormat(`
|
||||
<div class="notebook-cell">
|
||||
<div class="cell-line">
|
||||
<div class="cell-left cell-prompt">%s</div>
|
||||
<div class="cell-right cell-prompt">%s</div>
|
||||
</div>
|
||||
</div>`, left, right)
|
||||
}
|
||||
|
||||
func renderCell(ctx *markup.RenderContext, output htmlutil.HTMLWriter, cell Cell, language string) error {
|
||||
switch cell.CellType {
|
||||
case "markdown":
|
||||
output.WriteHTML(`
|
||||
<div class="notebook-cell cell-type-markdown">
|
||||
<div class="cell-line">
|
||||
<div class="cell-left cell-prompt"></div>
|
||||
<div class="cell-right">`)
|
||||
if err := renderCellMarkdown(ctx, output, joinSource(cell.Source)); err != nil {
|
||||
return err
|
||||
}
|
||||
output.WriteHTML(`
|
||||
</div>
|
||||
</div>
|
||||
</div>`)
|
||||
case "code":
|
||||
output.WriteHTML(`<div class="notebook-cell cell-type-code">`)
|
||||
if err := renderCellCode(output, cell, language); err != nil {
|
||||
return err
|
||||
}
|
||||
output.WriteHTML(`</div>`)
|
||||
default:
|
||||
renderCellPrompt(output, "Cell:", htmlutil.HTMLFormat("[Cell type %s - unsupported, skipped]", cell.CellType))
|
||||
}
|
||||
return output.Err()
|
||||
}
|
||||
|
||||
func renderCellMarkdown(rctx *markup.RenderContext, output htmlutil.HTMLWriter, source string) error {
|
||||
markdownCtx := markup.NewRenderContext(rctx)
|
||||
// make sure the markdown render use the same options and helper to generate correct contents (e.g.: links)
|
||||
markdownCtx.RenderOptions = rctx.RenderOptions
|
||||
markdownCtx.RenderHelper = rctx.RenderHelper
|
||||
output.WriteHTML(`<div class="embedded-markdown">`)
|
||||
if err := markdown.Render(markdownCtx, strings.NewReader(source), output.OriginWriter()); err != nil {
|
||||
return err
|
||||
}
|
||||
output.WriteHTML(`</div>`)
|
||||
return output.Err()
|
||||
}
|
||||
|
||||
func renderCellCodeOutput(output htmlutil.HTMLWriter, out Output) {
|
||||
if out.Data != nil {
|
||||
// Iterate through our priority list to find the best matching MIME handler available
|
||||
for _, h := range dataMimeHandlers() {
|
||||
if rawPayload, exists := out.Data[h.Mime]; exists {
|
||||
var stringPayload string
|
||||
|
||||
// Flatten the polymorphic JSON input (string or []any) into a single clean string
|
||||
switch v := rawPayload.(type) {
|
||||
case string:
|
||||
stringPayload = v
|
||||
case []any:
|
||||
stringPayload = joinSource(v)
|
||||
default:
|
||||
_ = renderCellCodeOutputUnsupported(output, fmt.Sprintf("[Data output - unsupported data type %T for mime type %s]", rawPayload, h.Mime))
|
||||
continue
|
||||
}
|
||||
|
||||
if err := h.Fn(output, stringPayload); err != nil {
|
||||
// TODO: RENDER-LOG-HANDLING: outputting render's error to sever's log is not a proper approach
|
||||
// The errors can be:
|
||||
// * unsupported element (cell, data, etc): it should render the message on the UI to tell users that the content is not supported, or ignore them if they are ignore-able
|
||||
// * logic error: it should report to server logs
|
||||
// * network error: io.Writer tries to write to the HTTP connection, so the error can also be a network error, such error should be ignored
|
||||
log.Error("Jupyter rendering engine failed for MIME type %s: %v", h.Mime, err)
|
||||
}
|
||||
|
||||
// Return immediately after rendering the top matching priority format
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stream output
|
||||
if out.OutputType == "stream" && out.Text != nil {
|
||||
streamName := util.Iif(out.Name == "stderr", "stderr", "stdout")
|
||||
output.WriteFormat(`<pre class="cell-output-stream stream-%s">%s</pre>`, streamName, joinSource(out.Text))
|
||||
return
|
||||
}
|
||||
|
||||
// Error output
|
||||
if out.OutputType == "error" {
|
||||
traceback := ""
|
||||
if tb, ok := out.Traceback.([]any); ok {
|
||||
lines := make([]string, len(tb))
|
||||
for i, line := range tb {
|
||||
lines[i] = fmt.Sprint(line)
|
||||
}
|
||||
traceback = strings.Join(lines, "\n")
|
||||
}
|
||||
if traceback == "" && out.Ename != "" {
|
||||
traceback = fmt.Sprintf("%s: %s", out.Ename, out.Evalue)
|
||||
}
|
||||
output.WriteFormat(`<pre class="cell-output-error">%s</pre>`, traceback)
|
||||
return
|
||||
}
|
||||
|
||||
// Generic text output
|
||||
if out.Text != nil {
|
||||
_ = renderCellCodeOutputTextPlain(output, joinSource(out.Text))
|
||||
}
|
||||
}
|
||||
|
||||
func joinSource(source any) string {
|
||||
switch v := source.(type) {
|
||||
case nil:
|
||||
return ""
|
||||
case string:
|
||||
return v
|
||||
case []any:
|
||||
// the "source slice item" has EOL ("\n"), so just join them together
|
||||
parts := make([]string, len(v))
|
||||
for i, part := range v {
|
||||
parts[i] = fmt.Sprint(part)
|
||||
}
|
||||
return strings.Join(parts, "")
|
||||
default:
|
||||
return fmt.Sprint(v)
|
||||
}
|
||||
}
|
||||
|
||||
// trimMathDelimiters strips a single pair of surrounding math delimiters ("$$...$$" or "$...$"),
|
||||
// so the inner expression is handled by the math post-processor. Unlike strings.Trim, it does not
|
||||
// eat unrelated "$" characters elsewhere in multi-expression content.
|
||||
func trimMathDelimiters(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
if t, ok := strings.CutPrefix(s, "$$"); ok {
|
||||
return strings.TrimSuffix(t, "$$")
|
||||
}
|
||||
if t, ok := strings.CutPrefix(s, "$"); ok {
|
||||
return strings.TrimSuffix(t, "$")
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package jupyter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modules/markup"
|
||||
"gitea.dev/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRender(t *testing.T) {
|
||||
r := renderer{}
|
||||
|
||||
t.Run("Basic notebook", func(t *testing.T) {
|
||||
input := `{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"source": ["print('hello')"],
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stdout",
|
||||
"text": ["hello\n"]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {},
|
||||
"nbformat": 4
|
||||
}`
|
||||
|
||||
var output strings.Builder
|
||||
ctx := &markup.RenderContext{}
|
||||
err := r.Render(ctx, strings.NewReader(input), &output)
|
||||
|
||||
assert.NoError(t, err)
|
||||
result := output.String()
|
||||
assert.Contains(t, result, `<div class="jupyter-notebook">`)
|
||||
assert.Contains(t, result, `<div class="notebook-cell cell-type-code">`)
|
||||
assert.Contains(t, result, `In [1]:`)
|
||||
assert.Contains(t, result, `print`)
|
||||
assert.Contains(t, result, `hello`)
|
||||
assert.Contains(t, result, `stream-stdout`)
|
||||
})
|
||||
|
||||
t.Run("Markdown cell with XSS Protection", func(t *testing.T) {
|
||||
input := `{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"# Title\n",
|
||||
"Some text\n",
|
||||
"[click me](javascript:alert(1))\n",
|
||||
"<script>alert('dangerous')</script>"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {},
|
||||
"nbformat": 4
|
||||
}`
|
||||
|
||||
var output strings.Builder
|
||||
ctx := markup.NewRenderContext(t.Context())
|
||||
err := r.Render(ctx, strings.NewReader(input), &output)
|
||||
|
||||
assert.NoError(t, err)
|
||||
result := output.String()
|
||||
|
||||
// Assert normal markup still renders correctly
|
||||
assert.Contains(t, result, `<div class="notebook-cell cell-type-markdown">`)
|
||||
assert.Contains(t, result, `Title`)
|
||||
assert.Contains(t, result, `Some text`)
|
||||
assert.Contains(t, result, `click me`)
|
||||
|
||||
// CRITICAL SECURITY ASSERTIONS: Ensure XSS vectors are completely stripped
|
||||
assert.NotContains(t, result, `javascript:alert`)
|
||||
assert.NotContains(t, result, `<script>`)
|
||||
})
|
||||
|
||||
t.Run("Cell limit truncation guardrail", func(t *testing.T) {
|
||||
// Generate an oversized notebook containing 105 cells dynamically
|
||||
var cellBlocks []string
|
||||
for range 105 {
|
||||
cellBlocks = append(cellBlocks, `{"cell_type": "markdown", "source": ["cell text"]}`)
|
||||
}
|
||||
input := fmt.Sprintf(`{"cells": [%s], "metadata": {}, "nbformat": 4}`, strings.Join(cellBlocks, ","))
|
||||
|
||||
var output strings.Builder
|
||||
ctx := markup.NewRenderContext(t.Context())
|
||||
err := r.Render(ctx, strings.NewReader(input), &output)
|
||||
|
||||
assert.NoError(t, err)
|
||||
result := output.String()
|
||||
|
||||
// Verify it halts rendering gracefully and shows the truncation warning
|
||||
assert.Contains(t, result, "Output truncated.")
|
||||
assert.Contains(t, result, "This notebook contains too many cells to display efficiently.")
|
||||
|
||||
// Count occurrences of the rendered cells to ensure it sliced down to exactly 100 elements
|
||||
assert.Equal(t, 100, strings.Count(result, `class="notebook-cell cell-type-markdown"`))
|
||||
})
|
||||
|
||||
t.Run("Image output", func(t *testing.T) {
|
||||
input := `{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"source": ["import matplotlib.pyplot as plt"],
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "display_data",
|
||||
"data": {
|
||||
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {},
|
||||
"nbformat": 4
|
||||
}`
|
||||
|
||||
var output strings.Builder
|
||||
ctx := markup.NewRenderContext(t.Context())
|
||||
err := r.Render(ctx, strings.NewReader(input), &output)
|
||||
|
||||
assert.NoError(t, err)
|
||||
result := output.String()
|
||||
assert.Contains(t, result, `<img src="data:image/png;base64,`)
|
||||
assert.Contains(t, result, `iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==`)
|
||||
})
|
||||
|
||||
t.Run("HTML output with style tag", func(t *testing.T) {
|
||||
input := `{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"source": ["import pandas as pd"],
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "execute_result",
|
||||
"data": {
|
||||
"text/html": ["<style scoped>.dataframe tbody tr th { vertical-align: top; }</style><table class=\"dataframe\"><tr><td>1</td></tr></table>"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {},
|
||||
"nbformat": 4
|
||||
}`
|
||||
|
||||
var output strings.Builder
|
||||
ctx := markup.NewRenderContext(t.Context())
|
||||
err := r.Render(ctx, strings.NewReader(input), &output)
|
||||
|
||||
assert.NoError(t, err)
|
||||
result := output.String()
|
||||
assert.NotContains(t, result, `<style scoped>`)
|
||||
assert.Contains(t, result, `<table><tr><td>1</td></tr></table>`)
|
||||
assert.Contains(t, result, `<td>1</td>`)
|
||||
})
|
||||
|
||||
t.Run("Error output", func(t *testing.T) {
|
||||
input := `{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"source": ["raise ValueError('test error')"],
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "error",
|
||||
"ename": "ValueError",
|
||||
"evalue": "test error",
|
||||
"traceback": ["ValueError: test error"]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {},
|
||||
"nbformat": 4
|
||||
}`
|
||||
|
||||
var output strings.Builder
|
||||
ctx := markup.NewRenderContext(t.Context())
|
||||
err := r.Render(ctx, strings.NewReader(input), &output)
|
||||
|
||||
assert.NoError(t, err)
|
||||
result := output.String()
|
||||
assert.Contains(t, result, `ValueError: test error`)
|
||||
assert.Contains(t, result, `cell-output-error`)
|
||||
})
|
||||
|
||||
t.Run("Old nbformat version", func(t *testing.T) {
|
||||
input := `{
|
||||
"cells": [],
|
||||
"metadata": {},
|
||||
"nbformat": 3
|
||||
}`
|
||||
|
||||
var output strings.Builder
|
||||
ctx := markup.NewRenderContext(t.Context())
|
||||
err := r.Render(ctx, strings.NewReader(input), &output)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Regexp(t, `<div class="file-not-rendered-prompt">This notebook uses an older format.*</div>`, output.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestJoinSource(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input any
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "String input",
|
||||
input: "hello world",
|
||||
expected: "hello world",
|
||||
},
|
||||
{
|
||||
name: "Array input",
|
||||
input: []any{"line1\n", "line2\n", "line3"},
|
||||
expected: "line1\nline2\nline3",
|
||||
},
|
||||
{
|
||||
name: "Empty array",
|
||||
input: []any{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "Single element array",
|
||||
input: []any{"single"},
|
||||
expected: "single",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := joinSource(tt.input)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegrationAndSanitization(t *testing.T) {
|
||||
// A mock malicious Jupyter notebook containing an XSS injection attempt
|
||||
// inside a text/html output cell (e.g., pretending to be a poisoned Pandas DataFrame).
|
||||
maliciousNotebook := `{
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2,
|
||||
"metadata": {},
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"source": ["a=1"],
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "execute_result",
|
||||
"execution_count": 1,
|
||||
"data": {
|
||||
"text/html": [
|
||||
"<div><script>alert('XSS Vector')</script><table class=\"dataframe\"><tr><td>Safe Content</td></tr></table></div>"
|
||||
]
|
||||
},
|
||||
"metadata": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
var output strings.Builder
|
||||
ctx := markup.NewRenderContext(t.Context())
|
||||
ctx.RenderOptions.MarkupType = "jupyter-render"
|
||||
err := markup.Render(ctx, strings.NewReader(maliciousNotebook), &output)
|
||||
assert.NoError(t, err)
|
||||
const expected = `
|
||||
<div class="jupyter-notebook">
|
||||
<div class="notebook-cell cell-type-code">
|
||||
<div class="cell-line">
|
||||
<div class="cell-left cell-prompt">In [1]:</div>
|
||||
<div class="cell-right cell-input">
|
||||
<pre><code class="chroma language-python">
|
||||
<span class="n">a</span><span class="o">=</span><span class="mi">1</span>
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cell-line">
|
||||
<div class="cell-left cell-prompt">Out [1]:</div>
|
||||
<div class="cell-right cell-output">
|
||||
<div class="cell-output-html">
|
||||
<div><table><tbody><tr><td>Safe Content</td></tr></tbody></table></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
assert.Equal(t, test.NormalizeHTMLSpaces(expected), test.NormalizeHTMLSpaces(output.String()))
|
||||
}
|
||||
@@ -65,17 +65,17 @@ func newParserContext(ctx *markup.RenderContext) parser.Context {
|
||||
return pc
|
||||
}
|
||||
|
||||
type GlodmarkRender struct {
|
||||
type GoldmarkRender struct {
|
||||
ctx *markup.RenderContext
|
||||
|
||||
goldmarkMarkdown goldmark.Markdown
|
||||
}
|
||||
|
||||
func (r *GlodmarkRender) Convert(source []byte, writer io.Writer, opts ...parser.ParseOption) error {
|
||||
func (r *GoldmarkRender) Convert(source []byte, writer io.Writer, opts ...parser.ParseOption) error {
|
||||
return r.goldmarkMarkdown.Convert(source, writer, opts...)
|
||||
}
|
||||
|
||||
func (r *GlodmarkRender) highlightingRenderer(w util.BufWriter, c highlighting.CodeBlockContext, entering bool) {
|
||||
func (r *GoldmarkRender) highlightingRenderer(w util.BufWriter, c highlighting.CodeBlockContext, entering bool) {
|
||||
if entering {
|
||||
languageBytes, _ := c.Language()
|
||||
languageStr := giteautil.IfZero(string(languageBytes), "text")
|
||||
@@ -136,10 +136,10 @@ func goldmarkDefaultParser() parser.Parser {
|
||||
}
|
||||
|
||||
// SpecializedMarkdown sets up the Gitea specific markdown extensions
|
||||
func SpecializedMarkdown(ctx *markup.RenderContext) *GlodmarkRender {
|
||||
func SpecializedMarkdown(ctx *markup.RenderContext) *GoldmarkRender {
|
||||
// TODO: it could use a pool to cache the renderers to reuse them with different contexts
|
||||
// at the moment it is fast enough (see the benchmarks)
|
||||
r := &GlodmarkRender{ctx: ctx}
|
||||
r := &GoldmarkRender{ctx: ctx}
|
||||
r.goldmarkMarkdown = goldmark.New(
|
||||
goldmark.WithParser(goldmarkDefaultParser()),
|
||||
goldmark.WithExtensions(
|
||||
|
||||
@@ -211,11 +211,11 @@ func RenderIFrame(ctx *RenderContext, opts *ExternalRendererOptions, output io.W
|
||||
ctx.RenderOptions.Metas["RefTypeNameSubURL"],
|
||||
util.PathEscapeSegments(ctx.RenderOptions.RelativePath),
|
||||
)
|
||||
var extraAttrs template.HTML
|
||||
if opts.ContentSandbox != "" {
|
||||
extraAttrs = htmlutil.HTMLFormat(` sandbox="%s"`, opts.ContentSandbox)
|
||||
}
|
||||
_, err := htmlutil.HTMLPrintf(output, `<iframe data-src="%s" data-global-init="initExternalRenderIframe" class="external-render-iframe"%s></iframe>`, src, extraAttrs)
|
||||
|
||||
// The render response should always have correct "sandbox" limits (no same-origin),
|
||||
// otherwise the "render link" direct access can still cause XSS without iframe.
|
||||
// So here we do not need to set sandbox attribute on the iframe.
|
||||
_, err := htmlutil.HTMLPrintf(output, `<iframe data-src="%s" data-global-init="initExternalRenderIframe" class="external-render-iframe"></iframe>`, src)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,7 @@ func TestRenderIFrame(t *testing.T) {
|
||||
WithRelativePath("tree-path").
|
||||
WithMetas(map[string]string{"user": "test-owner", "repo": "test-repo", "RefTypeNameSubURL": "src/branch/master"})
|
||||
|
||||
// the value is read from config RENDER_CONTENT_SANDBOX, empty means "disabled"
|
||||
ret := render(ctx, ExternalRendererOptions{ContentSandbox: ""})
|
||||
// iframe doesn't need sandbox, the sandbox is set in render's response header
|
||||
ret := render(ctx, ExternalRendererOptions{ContentSandbox: "any"})
|
||||
assert.Equal(t, `<iframe data-src="/test-owner/test-repo/render/src/branch/master/tree-path" data-global-init="initExternalRenderIframe" class="external-render-iframe"></iframe>`, ret)
|
||||
|
||||
ret = render(ctx, ExternalRendererOptions{ContentSandbox: "allow"})
|
||||
assert.Equal(t, `<iframe data-src="/test-owner/test-repo/render/src/branch/master/tree-path" data-global-init="initExternalRenderIframe" class="external-render-iframe" sandbox="allow"></iframe>`, ret)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -20,6 +22,7 @@ const (
|
||||
|
||||
var (
|
||||
ErrInvalidStructure = util.NewInvalidArgumentErrorf("package has invalid structure")
|
||||
ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
|
||||
ErrGoModFileTooLarge = util.NewInvalidArgumentErrorf("go.mod file is too large")
|
||||
)
|
||||
|
||||
@@ -54,6 +57,13 @@ func ParsePackage(r io.ReaderAt, size int64) (*Package, error) {
|
||||
Name: strings.TrimSuffix(nameAndVersion, "@"+parts[1]),
|
||||
Version: versionParts[0],
|
||||
}
|
||||
|
||||
// the version is taken verbatim from the zip path and later written
|
||||
// one per line into the @v/list proxy response, so it has to be a
|
||||
// valid module version (no newlines or other stray characters)
|
||||
if !semver.IsValid(p.Version) {
|
||||
return nil, ErrInvalidVersion
|
||||
}
|
||||
}
|
||||
|
||||
if len(versionParts) > 1 {
|
||||
|
||||
@@ -59,6 +59,16 @@ func TestParsePackage(t *testing.T) {
|
||||
assert.Equal(t, "module gitea.com/go-gitea/gitea", p.GoMod)
|
||||
})
|
||||
|
||||
t.Run("InvalidVersion", func(t *testing.T) {
|
||||
data := createArchive(map[string][]byte{
|
||||
packageName + "@v1.0.0\nv99.0.0/go.mod": []byte("module " + packageName),
|
||||
})
|
||||
|
||||
p, err := ParsePackage(data, int64(data.Len()))
|
||||
assert.Nil(t, p)
|
||||
assert.ErrorIs(t, err, ErrInvalidVersion)
|
||||
})
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
data := createArchive(map[string][]byte{
|
||||
packageName + "@" + packageVersion + "/subdir/go.mod": []byte("invalid"),
|
||||
|
||||
@@ -48,9 +48,7 @@ type SSHLogOption struct {
|
||||
|
||||
// HookPostReceiveResult represents an individual result from PostReceive
|
||||
type HookPostReceiveResult struct {
|
||||
Results []HookPostReceiveBranchResult
|
||||
RepoWasEmpty bool
|
||||
Err string
|
||||
Results []HookPostReceiveBranchResult
|
||||
}
|
||||
|
||||
// HookPostReceiveBranchResult represents an individual branch result from PostReceive
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
// SyncResult describes a reference update detected during sync.
|
||||
type SyncResult struct {
|
||||
RefName git.RefName
|
||||
OldCommitID string
|
||||
NewCommitID string
|
||||
OldCommitID git.RefName
|
||||
NewCommitID git.RefName
|
||||
}
|
||||
|
||||
// SyncRepoBranches synchronizes branch table with repository branches
|
||||
@@ -104,7 +104,7 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository,
|
||||
syncResults = append(syncResults, &SyncResult{
|
||||
RefName: git.RefNameFromBranch(branch),
|
||||
OldCommitID: "",
|
||||
NewCommitID: commit.ID.String(),
|
||||
NewCommitID: commit.ID.RefName(),
|
||||
})
|
||||
} else if commit.ID.String() != dbb.CommitID || dbb.IsDeleted {
|
||||
toUpdate = append(toUpdate, &git_model.Branch{
|
||||
@@ -118,8 +118,8 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository,
|
||||
})
|
||||
syncResults = append(syncResults, &SyncResult{
|
||||
RefName: git.RefNameFromBranch(branch),
|
||||
OldCommitID: dbb.CommitID,
|
||||
NewCommitID: commit.ID.String(),
|
||||
OldCommitID: git.RefNameFromCommit(dbb.CommitID),
|
||||
NewCommitID: commit.ID.RefName(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,7 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository,
|
||||
toRemove = append(toRemove, dbBranch.ID)
|
||||
syncResults = append(syncResults, &SyncResult{
|
||||
RefName: git.RefNameFromBranch(dbBranch.Name),
|
||||
OldCommitID: dbBranch.CommitID,
|
||||
OldCommitID: git.RefNameFromCommit(dbBranch.CommitID),
|
||||
NewCommitID: "",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,9 +13,12 @@ type PushUpdateOptions struct {
|
||||
PusherName string
|
||||
RepoUserName string
|
||||
RepoName string
|
||||
RefFullName git.RefName // branch, tag or other name to push
|
||||
OldCommitID string
|
||||
NewCommitID string
|
||||
|
||||
// FIXME: this struct's design is not right, the changed commits should be in a separate slice
|
||||
|
||||
RefFullName git.RefName // branch, tag or other name to push
|
||||
OldCommitID string
|
||||
NewCommitID string
|
||||
}
|
||||
|
||||
// IsNewRef return true if it's a first-time push to a branch, tag or etc.
|
||||
|
||||
@@ -210,7 +210,7 @@ func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitR
|
||||
syncResults = append(syncResults, &SyncResult{
|
||||
RefName: git.RefNameFromTag(tag.Name),
|
||||
OldCommitID: "",
|
||||
NewCommitID: tag.Object.String(),
|
||||
NewCommitID: tag.Object.RefName(),
|
||||
})
|
||||
}
|
||||
for _, deleteID := range deletes {
|
||||
@@ -220,20 +220,20 @@ func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitR
|
||||
}
|
||||
syncResults = append(syncResults, &SyncResult{
|
||||
RefName: git.RefNameFromTag(release.TagName),
|
||||
OldCommitID: release.Sha1,
|
||||
OldCommitID: git.RefNameFromCommit(release.Sha1),
|
||||
NewCommitID: "",
|
||||
})
|
||||
}
|
||||
for _, tag := range updates {
|
||||
release := dbReleasesByTag[tag.Name]
|
||||
oldSha := ""
|
||||
var oldCommitID git.RefName
|
||||
if release != nil {
|
||||
oldSha = release.Sha1
|
||||
oldCommitID = git.RefNameFromCommit(release.Sha1)
|
||||
}
|
||||
syncResults = append(syncResults, &SyncResult{
|
||||
RefName: git.RefNameFromTag(tag.Name),
|
||||
OldCommitID: oldSha,
|
||||
NewCommitID: tag.Object.String(),
|
||||
OldCommitID: oldCommitID,
|
||||
NewCommitID: tag.Object.RefName(),
|
||||
})
|
||||
}
|
||||
//
|
||||
|
||||
@@ -237,6 +237,10 @@ func fileExtensionsToPatterns(sectionName string, extensions []string) []string
|
||||
return patterns
|
||||
}
|
||||
|
||||
// MarkupRenderDefaultSandbox only contains a safe set of "sandbox allow" values, it is used to protect users from XSS attack,
|
||||
// DO NOT USE "allow-same-origin" by default: if there is XSS in rendered content, same-origin makes the frame page can access parent window and send requests with user's credentials.
|
||||
const MarkupRenderDefaultSandbox = "allow-scripts allow-forms allow-modals allow-popups allow-downloads"
|
||||
|
||||
func newMarkupRenderer(name string, sec ConfigSection) {
|
||||
if !sec.Key("ENABLED").MustBool(false) {
|
||||
return
|
||||
@@ -269,9 +273,7 @@ func newMarkupRenderer(name string, sec ConfigSection) {
|
||||
renderContentMode = RenderContentModeSanitized
|
||||
}
|
||||
|
||||
// ATTENTION! at the moment, only a safe set like "allow-scripts" are allowed for sandbox mode.
|
||||
// "allow-same-origin" should NEVER be used, it leads to XSS attack: makes the JS in iframe can access parent window's config and send requests with user's credentials.
|
||||
renderContentSandbox := sec.Key("RENDER_CONTENT_SANDBOX").MustString("allow-scripts allow-popups")
|
||||
renderContentSandbox := sec.Key("RENDER_CONTENT_SANDBOX").MustString(MarkupRenderDefaultSandbox)
|
||||
if renderContentSandbox == "disabled" {
|
||||
renderContentSandbox = ""
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ var Security = struct {
|
||||
// TODO: move more settings to this struct in future
|
||||
XFrameOptions string
|
||||
XContentTypeOptions string
|
||||
|
||||
ContentSecurityPolicyGeneral string // it only supports empty (default policy) or "unset", maybe it can support more in the future
|
||||
}{
|
||||
XFrameOptions: "SAMEORIGIN",
|
||||
XContentTypeOptions: "nosniff",
|
||||
@@ -150,13 +152,12 @@ func loadSecurityFrom(rootCfg ConfigProvider) {
|
||||
SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20)
|
||||
|
||||
deprecatedSetting(rootCfg, "cors", "X_FRAME_OPTIONS", "security", "X_FRAME_OPTIONS", "v1.26.0")
|
||||
if sec.HasKey("X_FRAME_OPTIONS") {
|
||||
Security.XFrameOptions = sec.Key("X_FRAME_OPTIONS").MustString(Security.XFrameOptions)
|
||||
} else {
|
||||
if !sec.HasKey("X_FRAME_OPTIONS") {
|
||||
Security.XFrameOptions = rootCfg.Section("cors").Key("X_FRAME_OPTIONS").MustString(Security.XFrameOptions)
|
||||
}
|
||||
|
||||
Security.XContentTypeOptions = sec.Key("X_CONTENT_TYPE_OPTIONS").MustString(Security.XContentTypeOptions)
|
||||
if err := sec.MapTo(&Security); err != nil {
|
||||
log.Fatal("Failed to map security settings: %v", err)
|
||||
}
|
||||
|
||||
twoFactorAuth := sec.Key("TWO_FACTOR_AUTH").String()
|
||||
switch twoFactorAuth {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLoadSecurityFrom(t *testing.T) {
|
||||
cfg, err := NewConfigProviderFromData(`[security]
|
||||
X_FRAME_OPTIONS = DENY
|
||||
X_CONTENT_TYPE_OPTIONS = unset
|
||||
CONTENT_SECURITY_POLICY_GENERAL = "script-src *; foo"`)
|
||||
assert.NoError(t, err)
|
||||
loadSecurityFrom(cfg)
|
||||
assert.Equal(t, "DENY", Security.XFrameOptions)
|
||||
assert.Equal(t, "unset", Security.XContentTypeOptions)
|
||||
assert.Equal(t, `"script-src *`, Security.ContentSecurityPolicyGeneral) // holy shit ini package bug
|
||||
}
|
||||
@@ -63,20 +63,24 @@ func (s *Sitemap) Add(u URL) {
|
||||
// WriteTo writes the sitemap to a response
|
||||
func (s *Sitemap) WriteTo(w io.Writer) (int64, error) {
|
||||
if l := len(s.URLs); l > urlsLimit {
|
||||
return 0, fmt.Errorf("The sitemap contains %d URLs, but only %d are allowed", l, urlsLimit)
|
||||
return 0, fmt.Errorf("sitemap contains %d URLs, but only %d are allowed", l, urlsLimit)
|
||||
}
|
||||
if l := len(s.Sitemaps); l > urlsLimit {
|
||||
return 0, fmt.Errorf("The sitemap contains %d sub-sitemaps, but only %d are allowed", l, urlsLimit)
|
||||
return 0, fmt.Errorf("sitemap contains %d sub-sitemaps, but only %d are allowed", l, urlsLimit)
|
||||
}
|
||||
buf := bytes.NewBufferString(xml.Header)
|
||||
if err := xml.NewEncoder(buf).Encode(s); err != nil {
|
||||
encoder := xml.NewEncoder(buf)
|
||||
defer encoder.Close()
|
||||
if err := encoder.Encode(s); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_ = encoder.Flush()
|
||||
if err := buf.WriteByte('\n'); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// FIXME: such limit is not right, the content has been written, it would have already caused OOM
|
||||
if buf.Len() > sitemapFileLimit {
|
||||
return 0, fmt.Errorf("The sitemap has %d bytes, but only %d are allowed", buf.Len(), sitemapFileLimit)
|
||||
return 0, fmt.Errorf("sitemap has %d bytes, but only %d are allowed", buf.Len(), sitemapFileLimit)
|
||||
}
|
||||
return buf.WriteTo(w)
|
||||
}
|
||||
|
||||
@@ -61,14 +61,14 @@ func TestNewSitemap(t *testing.T) {
|
||||
{
|
||||
name: "too many urls",
|
||||
urls: make([]URL, 50001),
|
||||
wantErr: "The sitemap contains 50001 URLs, but only 50000 are allowed",
|
||||
wantErr: "sitemap contains 50001 URLs, but only 50000 are allowed",
|
||||
},
|
||||
{
|
||||
name: "too big file",
|
||||
urls: []URL{
|
||||
{URL: strings.Repeat("b", 50*1024*1024+1)},
|
||||
},
|
||||
wantErr: "The sitemap has 52428932 bytes, but only 52428800 are allowed",
|
||||
wantErr: "sitemap has 52428932 bytes, but only 52428800 are allowed",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -137,14 +137,14 @@ func TestNewSitemapIndex(t *testing.T) {
|
||||
{
|
||||
name: "too many sitemaps",
|
||||
urls: make([]URL, 50001),
|
||||
wantErr: "The sitemap contains 50001 sub-sitemaps, but only 50000 are allowed",
|
||||
wantErr: "sitemap contains 50001 sub-sitemaps, but only 50000 are allowed",
|
||||
},
|
||||
{
|
||||
name: "too big file",
|
||||
urls: []URL{
|
||||
{URL: strings.Repeat("b", 50*1024*1024+1)},
|
||||
},
|
||||
wantErr: "The sitemap has 52428952 bytes, but only 52428800 are allowed",
|
||||
wantErr: "sitemap has 52428952 bytes, but only 52428800 are allowed",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -4,6 +4,20 @@
|
||||
|
||||
package structs
|
||||
|
||||
// TeamVisibility controls who can list a team within its organization.
|
||||
// - "public": visible to any signed-in user (still bounded by org visibility)
|
||||
// - "limited": visible to any member of the parent organization
|
||||
// - "private": visible only to team members and org owners
|
||||
//
|
||||
// swagger:enum TeamVisibility
|
||||
type TeamVisibility string
|
||||
|
||||
const (
|
||||
TeamVisibilityPublic TeamVisibility = "public"
|
||||
TeamVisibilityLimited TeamVisibility = "limited"
|
||||
TeamVisibilityPrivate TeamVisibility = "private"
|
||||
)
|
||||
|
||||
// Team represents a team in an organization
|
||||
type Team struct {
|
||||
// The unique identifier of the team
|
||||
@@ -24,6 +38,11 @@ type Team struct {
|
||||
UnitsMap map[string]string `json:"units_map"`
|
||||
// Whether the team can create repositories in the organization
|
||||
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
||||
// Team visibility within the organization. "private" teams are only
|
||||
// listable by members and org owners; "limited" teams are listable by
|
||||
// any organization member; "public" teams are listable by any signed-in
|
||||
// user.
|
||||
Visibility TeamVisibility `json:"visibility"`
|
||||
}
|
||||
|
||||
// CreateTeamOption options for creating a team
|
||||
@@ -42,6 +61,8 @@ type CreateTeamOption struct {
|
||||
UnitsMap map[string]string `json:"units_map"`
|
||||
// Whether the team can create repositories in the organization
|
||||
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
||||
// Team visibility within the organization. Defaults to "private".
|
||||
Visibility TeamVisibility `json:"visibility" binding:"OmitEmpty;In(public,limited,private)"`
|
||||
}
|
||||
|
||||
// EditTeamOption options for editing a team
|
||||
@@ -60,4 +81,7 @@ type EditTeamOption struct {
|
||||
UnitsMap map[string]string `json:"units_map"`
|
||||
// Whether the team can create repositories in the organization
|
||||
CanCreateOrgRepo *bool `json:"can_create_org_repo"`
|
||||
// Team visibility within the organization. When omitted, visibility is
|
||||
// left unchanged.
|
||||
Visibility *TeamVisibility `json:"visibility" binding:"OmitEmpty;In(public,limited,private)"`
|
||||
}
|
||||
|
||||
@@ -117,23 +117,48 @@ type ActionWorkflowRun struct {
|
||||
// RunAttempt is 1-based for runs created after ActionRunAttempt was introduced.
|
||||
// A value of 0 is a legacy-only sentinel for runs created before attempts existed
|
||||
// and indicates no corresponding /attempts/{n} resource is available.
|
||||
RunAttempt int64 `json:"run_attempt"`
|
||||
RunNumber int64 `json:"run_number"`
|
||||
RepositoryID int64 `json:"repository_id,omitempty"`
|
||||
HeadSha string `json:"head_sha"`
|
||||
HeadBranch string `json:"head_branch,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Actor *User `json:"actor,omitempty"`
|
||||
TriggerActor *User `json:"trigger_actor,omitempty"`
|
||||
Repository *Repository `json:"repository,omitempty"`
|
||||
HeadRepository *Repository `json:"head_repository,omitempty"`
|
||||
Conclusion string `json:"conclusion,omitempty"`
|
||||
RunAttempt int64 `json:"run_attempt"`
|
||||
RunNumber int64 `json:"run_number"`
|
||||
RepositoryID int64 `json:"repository_id,omitempty"`
|
||||
HeadSha string `json:"head_sha"`
|
||||
HeadBranch string `json:"head_branch,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Actor *User `json:"actor,omitempty"`
|
||||
TriggerActor *User `json:"trigger_actor,omitempty"`
|
||||
Repository *Repository `json:"repository,omitempty"`
|
||||
HeadRepository *Repository `json:"head_repository,omitempty"`
|
||||
Conclusion string `json:"conclusion,omitempty"`
|
||||
PullRequests []*PullRequestMinimal `json:"pull_requests"`
|
||||
// swagger:strfmt date-time
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
// swagger:strfmt date-time
|
||||
CompletedAt time.Time `json:"completed_at"`
|
||||
}
|
||||
|
||||
// PullRequestMinimal is the minimal information about a pull request, as
|
||||
// returned in the `pull_requests` field of a workflow run.
|
||||
type PullRequestMinimal struct {
|
||||
ID int64 `json:"id"`
|
||||
Number int64 `json:"number"`
|
||||
URL string `json:"url"`
|
||||
Head PullRequestMinimalHead `json:"head"`
|
||||
Base PullRequestMinimalHead `json:"base"`
|
||||
}
|
||||
|
||||
// PullRequestMinimalHead is a minimal description of one side of a pull request.
|
||||
type PullRequestMinimalHead struct {
|
||||
Ref string `json:"ref"`
|
||||
SHA string `json:"sha"`
|
||||
Repo PullRequestMinimalHeadRepo `json:"repo"`
|
||||
}
|
||||
|
||||
// PullRequestMinimalHeadRepo is a minimal description of the repository on one side of a pull request.
|
||||
type PullRequestMinimalHeadRepo struct {
|
||||
ID int64 `json:"id"`
|
||||
URL string `json:"url"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// ActionWorkflowRunsResponse returns ActionWorkflowRuns
|
||||
type ActionWorkflowRunsResponse struct {
|
||||
Entries []*ActionWorkflowRun `json:"workflow_runs"`
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package structs
|
||||
|
||||
import "time"
|
||||
|
||||
// CurrentAccessToken represents the metadata of the currently authenticated token.
|
||||
// swagger:model CurrentAccessToken
|
||||
type CurrentAccessToken struct {
|
||||
// The unique identifier of the access token
|
||||
ID int64 `json:"id"`
|
||||
// The name of the access token
|
||||
Name string `json:"name"`
|
||||
// The scopes granted to this access token
|
||||
Scopes []string `json:"scopes"`
|
||||
// The timestamp when the token was created
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
// The timestamp when the token was last used
|
||||
LastUsedAt time.Time `json:"last_used_at"`
|
||||
// The owner of the access token
|
||||
User *UserMeta `json:"user"`
|
||||
}
|
||||
|
||||
// UserMeta represents minimal user information for the token owner.
|
||||
type UserMeta struct {
|
||||
// The unique identifier of the user
|
||||
ID int64 `json:"id"`
|
||||
// The username of the user
|
||||
Login string `json:"login"`
|
||||
}
|
||||
@@ -12,12 +12,16 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gitea.dev/modules/json"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
// RedirectURL returns the redirect URL of a http response.
|
||||
@@ -182,3 +186,48 @@ func ExternalServiceHTTP(t TestingT, envVarName, def string) string {
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
var normalizeHTMLSpacesRegexp = sync.OnceValue(func() (ret struct {
|
||||
afterRt, beforeLt *regexp.Regexp
|
||||
},
|
||||
) {
|
||||
ret.afterRt = regexp.MustCompile(`>\s*`)
|
||||
ret.beforeLt = regexp.MustCompile(`\s*<`)
|
||||
return ret
|
||||
})
|
||||
|
||||
func NormalizeHTMLSpaces(s string) string {
|
||||
vars := normalizeHTMLSpacesRegexp()
|
||||
s = vars.afterRt.ReplaceAllString(s, ">\n")
|
||||
s = vars.beforeLt.ReplaceAllString(s, "\n<")
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
|
||||
func NormalizeHTMLAttributes(t TestingT, s string) string {
|
||||
nodes, err := html.Parse(strings.NewReader(s))
|
||||
if err != nil {
|
||||
t.Errorf("failed to parse expected HTML: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
var normalize func(n *html.Node)
|
||||
normalize = func(n *html.Node) {
|
||||
slices.SortFunc(n.Attr, func(a, b html.Attribute) int {
|
||||
if cmp := strings.Compare(a.Namespace, b.Namespace); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
if cmp := strings.Compare(a.Key, b.Key); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
return strings.Compare(a.Val, b.Val)
|
||||
})
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
normalize(c)
|
||||
}
|
||||
}
|
||||
var sb strings.Builder
|
||||
if err = html.Render(&sb, nodes); err != nil {
|
||||
t.Errorf("failed to render HTML: %v", err)
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
@@ -2865,6 +2865,14 @@
|
||||
"org.teams.all_repositories_read_permission_desc": "This team grants <strong>Read</strong> access to <strong>all repositories</strong>: members can view and clone repositories.",
|
||||
"org.teams.all_repositories_write_permission_desc": "This team grants <strong>Write</strong> access to <strong>all repositories</strong>: members can read from and push to repositories.",
|
||||
"org.teams.all_repositories_admin_permission_desc": "This team grants <strong>Admin</strong> access to <strong>all repositories</strong>: members can read from, push to and add collaborators to repositories.",
|
||||
"org.teams.visibility": "Visibility",
|
||||
"org.teams.visibility_private": "Private",
|
||||
"org.teams.visibility_private_helper": "Visible only to team members and organization owners.",
|
||||
"org.teams.visibility_limited": "Limited",
|
||||
"org.teams.visibility_limited_helper": "Visible to all members of this organization.",
|
||||
"org.teams.visibility_public": "Public",
|
||||
"org.teams.visibility_public_helper": "Visible to any signed-in user.",
|
||||
"org.teams.owners_visibility_fixed": "The Owners team visibility cannot be changed.",
|
||||
"org.teams.invite.title": "You have been invited to join team <strong>%s</strong> in organization <strong>%s</strong>.",
|
||||
"org.teams.invite.by": "Invited by %s",
|
||||
"org.teams.invite.description": "Please click the button below to join the team.",
|
||||
|
||||
@@ -2205,10 +2205,10 @@
|
||||
"repo.settings.trust_model.collaborator.desc": "Déanfar sínithe bailí ó chomhoibritheoirí an stórais seo a mharcáil mar \"iontaofa\", cibé acu a mheaitseálann siad an tiomnóir nó nach meaitseálann. Seachas sin, déanfar sínithe bailí a mharcáil mar \"neamhiontaofa\" má mheaitseálann an síniú an tiomnóir agus \"gan mheaitseáil\" mura bhfuil.",
|
||||
"repo.settings.trust_model.committer": "Coimisitheoir",
|
||||
"repo.settings.trust_model.committer.long": "Tiomnaithe: Sínithe muiníne a mheaitseálann tiomnóirí. Meaitseálann sé seo iompar GitHub agus cuirfidh sé iallach ar thiomnóirí atá sínithe ag Gitea Gitea a bheith mar an tiomnóir.",
|
||||
"repo.settings.trust_model.committer.desc": "Ní mharcálfar sínithe bailí mar \"iontaofa\" ach amháin má mheaitseálann siad an tiomnaí, nó marcálfar iad mar \"gan mheaitseáil\". Cuireann sé seo iallach ar Gitea a bheith ina tiomnaí ar thiomnuithe sínithe, agus an tiomnaí iarbhír marcáilte mar Chomhúdaraithe ag: agus Co-thiomnaithe ag: leantóir sa tiomnú. Caithfidh eochair réamhshocraithe Gitea a bheith ag teacht le húsáideoir sa bhunachar sonraí.",
|
||||
"repo.settings.trust_model.committer.desc": "Ní mharcálfar sínithe bailí mar \"iontaofa\" ach amháin má mheaitseálann siad an tiomnóir, nó marcálfar iad mar \"gan mheaitseáil\". Cuireann sé seo iallach ar Gitea a bheith ina thiomnóir ar thiomnuithe sínithe, agus an tiomnóir iarbhír marcáilte mar leantóir Co-authored-by: sa thiomnú. Caithfidh eochair réamhshocraithe Gitea a bheith ag teacht le húsáideoir sa bhunachar sonraí.",
|
||||
"repo.settings.trust_model.collaboratorcommitter": "Comhoibritheo+Coimiteoir",
|
||||
"repo.settings.trust_model.collaboratorcommitter.long": "Comhoibrí+Coiste: sínithe muiníne ó chomhoibrithe a mheaitseálann an tiomnóir",
|
||||
"repo.settings.trust_model.collaboratorcommitter.desc": "Marcálfar sínithe bailí ó chomhoibritheoirí an stórais seo mar \"iontaofa\" má mheaitseálann siad an tiomnaí. Seachas sin, marcálfar sínithe bailí mar \"neamhiontaofa\" má mheaitseálann an síniú an tiomnaí agus \"gan mheaitseáil\" murach sin. Cuirfidh sé seo iallach ar Gitea a bheith marcáilte mar an tiomnaí ar thiomnuithe sínithe, agus an tiomnaí iarbhír marcáilte mar Chomhúdaraithe ag: agus Co-Tiomnaithe ag: leantóir sa tiomnú. Ní mór don eochair réamhshocraithe Gitea a bheith ag teacht le húsáideoir sa bhunachar sonraí.",
|
||||
"repo.settings.trust_model.collaboratorcommitter.desc": "Marcálfar sínithe bailí ó chomhoibritheoirí an stórais seo mar \"iontaofa\" má mheaitseálann siad an tiomnóir. Seachas sin, marcálfar sínithe bailí mar \"neamhiontaofa\" má mheaitseálann an síniú an tiomnóir agus \"gan mheaitseáil\" murach sin. Cuirfidh sé seo iallach ar Gitea a bheith marcáilte mar an tiomnóir ar thiomnuithe sínithe, agus an tiomnóir iarbhír marcáilte mar leantóir Co-Authored-By: sa tiomnú. Ní mór don eochair réamhshocraithe Gitea a bheith ag teacht le húsáideoir sa bhunachar sonraí.",
|
||||
"repo.settings.wiki_delete": "Scrios Sonraí Vicí",
|
||||
"repo.settings.wiki_delete_desc": "Tá sonraí wiki stóras a scriosadh buan agus ní féidir iad a chur ar ais.",
|
||||
"repo.settings.wiki_delete_notices_1": "- Scriosfaidh agus díchumasóidh sé seo an stóras vicí do %s go buan.",
|
||||
@@ -2599,6 +2599,9 @@
|
||||
"repo.diff.review.reject": "Iarr athruithe",
|
||||
"repo.diff.review.self_approve": "Ní féidir le húdair iarratais tarraing a n-iarratas tarraingthe féin a chead",
|
||||
"repo.diff.committed_by": "tiomanta ag",
|
||||
"repo.diff.coauthored_by": "comhúdaraithe ag",
|
||||
"repo.commits.avatar_stack_and": "agus",
|
||||
"repo.commits.avatar_stack_people": "%d duine",
|
||||
"repo.diff.protected": "Cosanta",
|
||||
"repo.diff.image.side_by_side": "Taobh le Taobh",
|
||||
"repo.diff.image.swipe": "Scaoil",
|
||||
@@ -2862,6 +2865,14 @@
|
||||
"org.teams.all_repositories_read_permission_desc": "Tugann an fhoireann seo rochtain do <strong>Léamh</strong> ar <strong>gach stórais</strong>: is féidir le baill amharc ar stórais agus iad a chlónáil.",
|
||||
"org.teams.all_repositories_write_permission_desc": "Tugann an fhoireann seo rochtain do <strong>Scríobh</strong> ar <strong>gach stórais</strong>: is féidir le baill léamh ó stórais agus iad a bhrú chucu.",
|
||||
"org.teams.all_repositories_admin_permission_desc": "Tugann an fhoireann seo rochtain <strong>Riarthóra</strong> ar <strong>gach stóras</strong>: is féidir le comhaltaí léamh, brú a dhéanamh agus comhoibritheoirí a chur le stórtha.",
|
||||
"org.teams.visibility": "Infheictheacht",
|
||||
"org.teams.visibility_private": "Príobháideach",
|
||||
"org.teams.visibility_private_helper": "Le feiceáil ag baill foirne agus úinéirí eagraíochta amháin.",
|
||||
"org.teams.visibility_limited": "Teoranta",
|
||||
"org.teams.visibility_limited_helper": "Infheicthe ag gach ball den eagraíocht seo.",
|
||||
"org.teams.visibility_public": "Poiblí",
|
||||
"org.teams.visibility_public_helper": "Infheicthe ag aon úsáideoir atá sínithe isteach.",
|
||||
"org.teams.owners_visibility_fixed": "Ní féidir infheictheacht fhoireann na nÚinéirí a athrú.",
|
||||
"org.teams.invite.title": "Tugadh cuireadh duit dul isteach i bhfoireann <strong>%s</strong> san eagraíocht <strong>%s</strong>.",
|
||||
"org.teams.invite.by": "Ar cuireadh ó %s",
|
||||
"org.teams.invite.description": "Cliceáil ar an gcnaipe thíos le do thoil chun dul isteach san fhoireann.",
|
||||
@@ -3774,6 +3785,7 @@
|
||||
"actions.runs.no_matching_online_runner_helper": "Gan aon reathaí ar líne a mheaitseáil le lipéad: %s",
|
||||
"actions.runs.no_job_without_needs": "Caithfidh post amháin ar a laghad a bheith sa sreabhadh oibre gan spleáchas.",
|
||||
"actions.runs.no_job": "Caithfidh post amháin ar a laghad a bheith sa sreabhadh oibre",
|
||||
"actions.runs.invalid_reusable_workflow_uses": "Sreabhadh oibre in-athúsáidte neamhbhailí \"úsáidí\": %s",
|
||||
"actions.runs.actor": "Aisteoir",
|
||||
"actions.runs.status": "Stádas",
|
||||
"actions.runs.actors_no_select": "Gach aisteoir",
|
||||
@@ -3794,13 +3806,17 @@
|
||||
"actions.runs.view_workflow_file": "Féach ar chomhad sreabha oibre",
|
||||
"actions.runs.summary": "Achoimre",
|
||||
"actions.runs.all_jobs": "Gach post",
|
||||
"actions.runs.job_summaries": "Achoimrí poist",
|
||||
"actions.runs.expand_caller_jobs": "Taispeáin poist an ghlaoiteora sreabha oibre in-athúsáidte seo",
|
||||
"actions.runs.collapse_caller_jobs": "Folaigh poist an ghlaoiteora sreabha oibre in-athúsáidte seo",
|
||||
"actions.runs.attempt": "Iarracht",
|
||||
"actions.runs.latest": "Is déanaí",
|
||||
"actions.runs.latest_attempt": "An iarracht is déanaí",
|
||||
"actions.runs.triggered_via": "Spreagtha trí %s",
|
||||
"actions.runs.total_duration": "Fad iomlán:",
|
||||
"actions.runs.rerun_triggered": "Athrith spreagtha",
|
||||
"actions.runs.back_to_pull_request": "Ar ais chuig an iarratas tarraingthe",
|
||||
"actions.runs.back_to_workflow": "Ar ais chuig an sreabhadh oibre",
|
||||
"actions.runs.total_duration": "Fad iomlán",
|
||||
"actions.runs.workflow_dependencies": "Spleáchais ar Shreabhadh Oibre",
|
||||
"actions.runs.graph_jobs_count_1": "%d post",
|
||||
"actions.runs.graph_jobs_count_n": "%d poist",
|
||||
|
||||
@@ -1321,6 +1321,7 @@
|
||||
"repo.editor.fork_branch_exists": "分支「%s」已存在于您的派生仓库中,请选择一个新的分支名称。",
|
||||
"repo.commits.desc": "浏览代码修改历史",
|
||||
"repo.commits.commits": "次代码提交",
|
||||
"repo.commits.history_enable_follow_renames": "包含重命名",
|
||||
"repo.commits.no_commits": "没有共同的提交。「%s」和「%s」的历史完全不同。",
|
||||
"repo.commits.nothing_to_compare": "没有差异可显示。",
|
||||
"repo.commits.search.tooltip": "您可以在关键词前加上前缀,如「author:」、「committer:」、「after:」或「before:」,例如「retrin author:Alice before:2019-01-13」。",
|
||||
@@ -2204,10 +2205,10 @@
|
||||
"repo.settings.trust_model.collaborator.desc": "此仓库中协作者的有效签名将被标记为「可信」(无论它们是否是提交者),签名只符合提交者时将标记为「不可信」,都不匹配时标记为「不匹配」。",
|
||||
"repo.settings.trust_model.committer": "提交者",
|
||||
"repo.settings.trust_model.committer.long": "提交者: 信任与提交者相符的签名(这符合 GitHub 的行为并将强制 Gitea 签名的提交以 Gitea 为提交者)。",
|
||||
"repo.settings.trust_model.committer.desc": "有效签名只有和提交者相匹配才会被标记为「受信任」,否则它们将被标记为「不匹配」。这强制 Gitea 成为签名提交的提交者,而实际提交者被加上 Co-authored-by: 和 Co-committed-by: 的标记。 默认的 Gitea 密钥必须匹配数据库中的一名用户。",
|
||||
"repo.settings.trust_model.committer.desc": "有效签名只有和提交者相匹配才会被标记为「受信任」,否则它们将被标记为「不匹配」。这意味着在已签名的提交中,Gitea 必须作为提交者,而实际的提交者则在提交信息中通过 `Co-authored-by:` 字段进行标注。 默认的 Gitea 密钥必须与数据库中的用户相匹配。",
|
||||
"repo.settings.trust_model.collaboratorcommitter": "协作者+提交者",
|
||||
"repo.settings.trust_model.collaboratorcommitter.long": "协作者+提交者:信任协作者同时是提交者的签名",
|
||||
"repo.settings.trust_model.collaboratorcommitter.desc": "此仓库中协作者的有效签名在他同时是提交者时将被标记为「可信」,签名只匹配了提交者时将标记为「不可信」,都不匹配时标记为「不匹配」。这会强制 Gitea 成为签名者和提交者,实际的提交者将被标记于提交消息结尾处的「Co-Authored-By:」和「Co-Committed-By:」。默认的 Gitea 签名密钥必须匹配数据库中的一个用户密钥。",
|
||||
"repo.settings.trust_model.collaboratorcommitter.desc": "此仓库中协作者的有效签名在他同时是提交者时将被标记为「受信任」,签名只匹配了提交者时将标记为「不可信」,都不匹配时标记为「不匹配」。这将强制使 Gitea 显示为已签名提交的提交者,而实际提交者则在提交信息中以 `Co-Authored-By:` 尾注的形式标出。默认的 Gitea 密钥必须与数据库中的用户相匹配。",
|
||||
"repo.settings.wiki_delete": "删除百科数据",
|
||||
"repo.settings.wiki_delete_desc": "删除仓库百科数据是永久性的,无法撤消。",
|
||||
"repo.settings.wiki_delete_notices_1": "- 这将永久删除和禁用 %s 的百科。",
|
||||
@@ -2598,6 +2599,9 @@
|
||||
"repo.diff.review.reject": "请求变更",
|
||||
"repo.diff.review.self_approve": "合并请求作者不能批准自己的合并请求",
|
||||
"repo.diff.committed_by": "提交者",
|
||||
"repo.diff.coauthored_by": "共同撰写人",
|
||||
"repo.commits.avatar_stack_and": "和",
|
||||
"repo.commits.avatar_stack_people": "%d 人",
|
||||
"repo.diff.protected": "受保护的",
|
||||
"repo.diff.image.side_by_side": "双排",
|
||||
"repo.diff.image.swipe": "滑动",
|
||||
@@ -2725,6 +2729,7 @@
|
||||
"graphs.code_frequency.what": "代码频率",
|
||||
"graphs.contributors.what": "贡献",
|
||||
"graphs.recent_commits.what": "最近的提交",
|
||||
"graphs.chart_zoom_hint": "拖动:缩放,Shift+拖动:平移,双击:重置缩放",
|
||||
"org.org_name_holder": "组织名称",
|
||||
"org.org_full_name_holder": "组织全名",
|
||||
"org.org_name_helper": "组织名字应该简单明了。",
|
||||
@@ -2860,6 +2865,14 @@
|
||||
"org.teams.all_repositories_read_permission_desc": "此团队授予<strong>读取</strong><strong>所有仓库</strong>的访问权限: 成员可以查看和克隆仓库。",
|
||||
"org.teams.all_repositories_write_permission_desc": "此团队授予<strong>修改</strong><strong>所有仓库</strong>的访问权限: 成员可以查看和推送至仓库。",
|
||||
"org.teams.all_repositories_admin_permission_desc": "该团队拥有 <strong>管理</strong> <strong>所有仓库</strong>的权限:团队成员可以读取、克隆、推送以及添加其它仓库协作者。",
|
||||
"org.teams.visibility": "可见性",
|
||||
"org.teams.visibility_private": "私有",
|
||||
"org.teams.visibility_private_helper": "仅对团队成员和组织所有者可见。",
|
||||
"org.teams.visibility_limited": "受限",
|
||||
"org.teams.visibility_limited_helper": "对组织所有成员可见。",
|
||||
"org.teams.visibility_public": "公开",
|
||||
"org.teams.visibility_public_helper": "对任何登录用户可见。",
|
||||
"org.teams.owners_visibility_fixed": "所有者的团队可见性无法更改。",
|
||||
"org.teams.invite.title": "您已被邀请加入组织 <strong>%s</strong> 中的团队 <strong>%s</strong>。",
|
||||
"org.teams.invite.by": "邀请人 %s",
|
||||
"org.teams.invite.description": "请点击下面的按钮加入团队。",
|
||||
@@ -3772,6 +3785,7 @@
|
||||
"actions.runs.no_matching_online_runner_helper": "没有匹配 %s 标签的在线运行器",
|
||||
"actions.runs.no_job_without_needs": "工作流必须包含至少一个没有依赖关系的作业。",
|
||||
"actions.runs.no_job": "工作流必须包含至少一个作业",
|
||||
"actions.runs.invalid_reusable_workflow_uses": "无效的可复用工作流「uses」:%s",
|
||||
"actions.runs.actor": "操作者",
|
||||
"actions.runs.status": "状态",
|
||||
"actions.runs.actors_no_select": "所有操作者",
|
||||
@@ -3792,11 +3806,27 @@
|
||||
"actions.runs.view_workflow_file": "查看工作流文件",
|
||||
"actions.runs.summary": "摘要",
|
||||
"actions.runs.all_jobs": "所有任务",
|
||||
"actions.runs.job_summaries": "任务摘要",
|
||||
"actions.runs.expand_caller_jobs": "显示此可复用工作流调用者的任务",
|
||||
"actions.runs.collapse_caller_jobs": "隐藏此可复用工作流调用者的任务",
|
||||
"actions.runs.attempt": "尝试",
|
||||
"actions.runs.latest": "最新",
|
||||
"actions.runs.latest_attempt": "最新尝试",
|
||||
"actions.runs.triggered_via": "通过 %s 触发",
|
||||
"actions.runs.total_duration": "总耗时:",
|
||||
"actions.runs.rerun_triggered": "重新运行已触发",
|
||||
"actions.runs.back_to_pull_request": "返回合并请求",
|
||||
"actions.runs.back_to_workflow": "返回工作流",
|
||||
"actions.runs.total_duration": "总耗时",
|
||||
"actions.runs.workflow_dependencies": "工作流依赖项",
|
||||
"actions.runs.graph_jobs_count_1": "%d 个任务",
|
||||
"actions.runs.graph_jobs_count_n": "%d 个任务",
|
||||
"actions.runs.graph_dependencies_count_1": "%d 个依赖项",
|
||||
"actions.runs.graph_dependencies_count_n": "%d 个依赖项",
|
||||
"actions.runs.graph_success_rate": "%s 成功",
|
||||
"actions.runs.graph_zoom_in": "放大(在图上 Ctrl/Cmd + 滚动)",
|
||||
"actions.runs.graph_zoom_max": "已为 100% 缩放",
|
||||
"actions.runs.graph_zoom_out": "缩小(在图上 Ctrl/Cmd + 滚动)",
|
||||
"actions.runs.graph_reset_view": "重置视图",
|
||||
"actions.workflow.disable": "禁用工作流",
|
||||
"actions.workflow.disable_success": "工作流「%s」已成功禁用。",
|
||||
"actions.workflow.enable": "启用工作流",
|
||||
|
||||
+12
-12
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@11.5.1",
|
||||
"packageManager": "pnpm@11.5.3",
|
||||
"engines": {
|
||||
"node": ">= 22.18.0",
|
||||
"pnpm": ">= 11.0.0"
|
||||
@@ -17,10 +17,10 @@
|
||||
"@codemirror/language": "6.12.3",
|
||||
"@codemirror/language-data": "6.5.2",
|
||||
"@codemirror/legacy-modes": "6.5.3",
|
||||
"@codemirror/lint": "6.9.6",
|
||||
"@codemirror/lint": "6.9.7",
|
||||
"@codemirror/search": "6.7.0",
|
||||
"@codemirror/state": "6.6.0",
|
||||
"@codemirror/view": "6.43.0",
|
||||
"@codemirror/view": "6.43.1",
|
||||
"@deltablot/dropzone": "7.4.3",
|
||||
"@github/markdown-toolbar-element": "2.2.3",
|
||||
"@github/paste-markdown": "1.5.3",
|
||||
@@ -28,7 +28,7 @@
|
||||
"@lezer/highlight": "1.2.3",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-rc2",
|
||||
"@mermaid-js/layout-elk": "0.2.1",
|
||||
"@primer/octicons": "19.28.0",
|
||||
"@primer/octicons": "19.28.1",
|
||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||
"@replit/codemirror-lang-nix": "6.0.1",
|
||||
"@replit/codemirror-lang-svelte": "6.0.0",
|
||||
@@ -47,7 +47,7 @@
|
||||
"cropperjs": "1.6.2",
|
||||
"dayjs": "1.11.21",
|
||||
"easymde": "2.21.0",
|
||||
"esbuild": "0.28.0",
|
||||
"esbuild": "0.28.1",
|
||||
"idiomorph": "0.7.4",
|
||||
"jquery": "4.0.0",
|
||||
"js-yaml": "4.2.0",
|
||||
@@ -69,7 +69,7 @@
|
||||
"vanilla-colorful": "0.7.2",
|
||||
"vite": "8.0.16",
|
||||
"vite-string-plugin": "2.0.4",
|
||||
"vue": "3.5.35",
|
||||
"vue": "3.5.37",
|
||||
"vue-bar-graph": "2.2.0",
|
||||
"vue-chartjs": "5.3.3"
|
||||
},
|
||||
@@ -80,10 +80,10 @@
|
||||
"@stylistic/eslint-plugin": "5.10.0",
|
||||
"@stylistic/stylelint-plugin": "5.2.0",
|
||||
"@types/codemirror": "5.60.17",
|
||||
"@types/jquery": "4.0.0",
|
||||
"@types/jquery": "4.0.1",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/katex": "0.16.8",
|
||||
"@types/node": "25.9.1",
|
||||
"@types/node": "25.9.3",
|
||||
"@types/pdfobject": "2.2.5",
|
||||
"@types/sortablejs": "1.15.9",
|
||||
"@types/swagger-ui-dist": "3.30.6",
|
||||
@@ -105,13 +105,13 @@
|
||||
"eslint-plugin-vue-scoped-css": "3.1.1",
|
||||
"eslint-plugin-wc": "3.1.0",
|
||||
"globals": "17.6.0",
|
||||
"happy-dom": "20.10.1",
|
||||
"happy-dom": "20.10.2",
|
||||
"jiti": "2.7.0",
|
||||
"markdownlint-cli": "0.48.0",
|
||||
"material-icon-theme": "5.35.0",
|
||||
"postcss-html": "1.8.1",
|
||||
"spectral-cli-bundle": "1.0.8",
|
||||
"stylelint": "17.12.0",
|
||||
"stylelint": "17.13.0",
|
||||
"stylelint-config-recommended": "18.0.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "3.0.0",
|
||||
"stylelint-declaration-strict-value": "1.11.1",
|
||||
@@ -119,8 +119,8 @@
|
||||
"svgo": "4.0.1",
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "8.61.1",
|
||||
"updates": "17.17.3",
|
||||
"updates": "17.18.0",
|
||||
"vitest": "4.1.8",
|
||||
"vue-tsc": "3.3.3"
|
||||
"vue-tsc": "3.3.4"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+294
-300
File diff suppressed because it is too large
Load Diff
+1
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-stack-add" width="16" height="16" aria-hidden="true"><path d="M7.122.392a1.75 1.75 0 0 1 1.756 0l5.003 2.902c.83.481.83 1.68 0 2.162L8.878 8.358a1.75 1.75 0 0 1-1.756 0L2.119 5.456a1.25 1.25 0 0 1 0-2.162ZM8.125 1.69a.25.25 0 0 0-.25 0L3.244 4.375 7.875 7.06a.25.25 0 0 0 .25 0l4.63-2.685ZM1.602 7.789a.75.75 0 0 1 1.024-.272l5.249 3.044a.749.749 0 1 1-.753 1.296L1.874 8.813a.75.75 0 0 1-.272-1.024m0 3.5a.75.75 0 0 1 1.024-.272l5.249 3.044a.749.749 0 1 1-.753 1.296l-5.248-3.044a.75.75 0 0 1-.272-1.024M11.75 15.25v-2h-2a.75.75 0 0 1 0-1.5h2v-2a.75.75 0 0 1 1.5 0v2h2a.75.75 0 0 1 0 1.5h-2v2a.75.75 0 0 1-1.5 0"/></svg>
|
||||
|
After Width: | Height: | Size: 700 B |
+1
-1
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16" class="svg octicon-stack-check" width="16" height="16" aria-hidden="true"><path fill="#010409" d="M7.122.392a1.75 1.75 0 0 1 1.756 0l5.003 2.902c.83.481.83 1.68 0 2.162L8.878 8.358a1.75 1.75 0 0 1-1.756 0L2.12 5.456a1.25 1.25 0 0 1 0-2.162zM8.125 1.69a.25.25 0 0 0-.25 0l-4.63 2.685 4.63 2.685a.25.25 0 0 0 .25 0l4.63-2.685zM1.602 7.79a.75.75 0 0 1 1.024-.273l5.249 3.044a.75.75 0 0 1-.753 1.297L1.874 8.814a.75.75 0 0 1-.272-1.025M1.602 11.29a.75.75 0 0 1 1.024-.273l5.249 3.044a.75.75 0 0 1-.753 1.297l-5.248-3.044a.75.75 0 0 1-.272-1.025M14.701 10.49a.75.75 0 1 1 1.098 1.02l-3.719 4a.75.75 0 0 1-1.075.024l-1.781-1.752a.751.751 0 0 1 1.052-1.069l1.23 1.21z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-stack-check" width="16" height="16" aria-hidden="true"><path d="M7.122.392a1.75 1.75 0 0 1 1.756 0l5.003 2.902c.83.481.83 1.68 0 2.162L8.878 8.358a1.75 1.75 0 0 1-1.756 0L2.12 5.456a1.25 1.25 0 0 1 0-2.162zM8.125 1.69a.25.25 0 0 0-.25 0l-4.63 2.685 4.63 2.685a.25.25 0 0 0 .25 0l4.63-2.685zM1.602 7.79a.75.75 0 0 1 1.024-.273l5.249 3.044a.75.75 0 0 1-.753 1.297L1.874 8.814a.75.75 0 0 1-.272-1.025M1.602 11.29a.75.75 0 0 1 1.024-.273l5.249 3.044a.75.75 0 0 1-.753 1.297l-5.248-3.044a.75.75 0 0 1-.272-1.025M14.701 10.49a.75.75 0 1 1 1.098 1.02l-3.719 4a.75.75 0 0 1-1.075.024l-1.781-1.752a.751.751 0 0 1 1.052-1.069l1.23 1.21z"/></svg>
|
||||
|
Before Width: | Height: | Size: 741 B After Width: | Height: | Size: 714 B |
+1
-1
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16" class="svg octicon-stack-remove" width="16" height="16" aria-hidden="true"><path fill="#010409" d="M14.72 10.22a.75.75 0 0 1 1.06 1.06L14.06 13l1.72 1.72a.75.75 0 1 1-1.06 1.06L13 14.06l-1.72 1.72a.75.75 0 0 1-1.06-1.06L11.938 13l-1.72-1.72a.75.75 0 0 1 1.06-1.06L13 11.94zM1.601 11.29a.75.75 0 0 1 1.025-.273l5.249 3.044a.75.75 0 0 1-.753 1.297l-5.248-3.045A.75.75 0 0 1 1.6 11.29M1.601 7.79a.75.75 0 0 1 1.025-.273l5.249 3.044a.75.75 0 0 1-.753 1.297L1.874 8.814A.75.75 0 0 1 1.6 7.789"/><path fill="#010409" fill-rule="evenodd" d="M7.122.393a1.75 1.75 0 0 1 1.755 0l5.003 2.901c.83.482.83 1.68 0 2.162L8.877 8.358a1.75 1.75 0 0 1-1.755 0L2.119 5.456a1.25 1.25 0 0 1 0-2.162zM8.125 1.69a.25.25 0 0 0-.25 0L3.244 4.375l4.63 2.686a.25.25 0 0 0 .25 0l4.63-2.686z" clip-rule="evenodd"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-stack-remove" width="16" height="16" aria-hidden="true"><path d="M14.72 10.22a.75.75 0 0 1 1.06 1.06L14.06 13l1.72 1.72a.75.75 0 1 1-1.06 1.06L13 14.06l-1.72 1.72a.75.75 0 0 1-1.06-1.06L11.938 13l-1.72-1.72a.75.75 0 0 1 1.06-1.06L13 11.94zM1.601 11.29a.75.75 0 0 1 1.025-.273l5.249 3.044a.75.75 0 0 1-.753 1.297l-5.248-3.045A.75.75 0 0 1 1.6 11.29M1.601 7.79a.75.75 0 0 1 1.025-.273l5.249 3.044a.75.75 0 0 1-.753 1.297L1.874 8.814A.75.75 0 0 1 1.6 7.789"/><path fill-rule="evenodd" d="M7.122.393a1.75 1.75 0 0 1 1.755 0l5.003 2.901c.83.482.83 1.68 0 2.162L8.877 8.358a1.75 1.75 0 0 1-1.755 0L2.119 5.456a1.25 1.25 0 0 1 0-2.162zM8.125 1.69a.25.25 0 0 0-.25 0L3.244 4.375l4.63 2.686a.25.25 0 0 0 .25 0l4.63-2.686z" clip-rule="evenodd"/></svg>
|
||||
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 820 B |
+1
-1
@@ -5,7 +5,7 @@ requires-python = ">=3.10"
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"djlint==1.36.4",
|
||||
"djlint==1.39.0",
|
||||
"yamllint==1.38.0",
|
||||
"zizmor==1.25.2",
|
||||
]
|
||||
|
||||
@@ -99,5 +99,5 @@ func ListWorkflowRuns(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
shared.ListRuns(ctx, 0, 0)
|
||||
shared.ListRuns(ctx, 0, 0, "")
|
||||
}
|
||||
|
||||
+94
-32
@@ -88,6 +88,7 @@ import (
|
||||
"gitea.dev/routers/api/v1/packages"
|
||||
"gitea.dev/routers/api/v1/repo"
|
||||
"gitea.dev/routers/api/v1/settings"
|
||||
"gitea.dev/routers/api/v1/token"
|
||||
"gitea.dev/routers/api/v1/user"
|
||||
"gitea.dev/routers/common"
|
||||
"gitea.dev/services/actions"
|
||||
@@ -504,41 +505,79 @@ func reqOrgOwnership() func(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
// reqTeamMembership user should be an team member, or a site admin
|
||||
func reqTeamMembership() func(ctx *context.APIContext) {
|
||||
func teamAccessPrivileged(ctx *context.APIContext) (orgID int64, privileged, ok bool) {
|
||||
if ctx.IsUserSiteAdmin() {
|
||||
return 0, true, true
|
||||
}
|
||||
if ctx.Org.Team == nil {
|
||||
setting.PanicInDevOrTesting("teamAccess: unprepared context")
|
||||
ctx.APIErrorInternal(errors.New("teamAccess: unprepared context"))
|
||||
return 0, false, false
|
||||
}
|
||||
|
||||
orgID = ctx.Org.Team.OrgID
|
||||
isOwner, err := organization.IsOrganizationOwner(ctx, orgID, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return 0, false, false
|
||||
} else if isOwner {
|
||||
return orgID, true, true
|
||||
}
|
||||
|
||||
isTeamMember, err := organization.IsTeamMember(ctx, orgID, ctx.Org.Team.ID, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return 0, false, false
|
||||
}
|
||||
return orgID, isTeamMember, true
|
||||
}
|
||||
|
||||
func denyNonTeamMember(ctx *context.APIContext, orgID int64) {
|
||||
isOrgMember, err := organization.IsOrganizationMember(ctx, orgID, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
} else if isOrgMember {
|
||||
ctx.APIError(http.StatusForbidden, "Must be a team member")
|
||||
} else {
|
||||
ctx.APIErrorNotFound()
|
||||
}
|
||||
}
|
||||
|
||||
// reqTeamReadAccess allows callers who can list the team to read its metadata.
|
||||
// Non-members are admitted by the team's visibility tier and parent org visibility.
|
||||
// Not sufficient for mutations — use reqOrgOwnership() or reqTeamMembership() for those.
|
||||
func reqTeamReadAccess() func(ctx *context.APIContext) {
|
||||
return func(ctx *context.APIContext) {
|
||||
if ctx.IsUserSiteAdmin() {
|
||||
orgID, privileged, ok := teamAccessPrivileged(ctx)
|
||||
if !ok || privileged {
|
||||
return
|
||||
}
|
||||
if ctx.Org.Team == nil {
|
||||
setting.PanicInDevOrTesting("reqTeamMembership: unprepared context")
|
||||
ctx.APIErrorInternal(errors.New("reqTeamMembership: unprepared context"))
|
||||
if ctx.Org.Organization == nil {
|
||||
setting.PanicInDevOrTesting("reqTeamReadAccess: organization not loaded")
|
||||
ctx.APIErrorInternal(errors.New("reqTeamReadAccess: organization not loaded"))
|
||||
return
|
||||
}
|
||||
|
||||
orgID := ctx.Org.Team.OrgID
|
||||
isOwner, err := organization.IsOrganizationOwner(ctx, orgID, ctx.Doer.ID)
|
||||
visible, err := ctx.Org.Team.CanNonMemberReadMeta(ctx, ctx.Org.Organization.AsUser(), ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
} else if isOwner {
|
||||
return
|
||||
}
|
||||
if !visible {
|
||||
// Not admitted by visibility: 403 for org members, 404 otherwise.
|
||||
denyNonTeamMember(ctx, orgID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isTeamMember, err := organization.IsTeamMember(ctx, orgID, ctx.Org.Team.ID, ctx.Doer.ID); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
} else if !isTeamMember {
|
||||
isOrgMember, err := organization.IsOrganizationMember(ctx, orgID, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
} else if isOrgMember {
|
||||
ctx.APIError(http.StatusForbidden, "Must be a team member")
|
||||
} else {
|
||||
ctx.APIErrorNotFound()
|
||||
}
|
||||
// reqTeamMembership user should be a team member, or a site admin
|
||||
func reqTeamMembership() func(ctx *context.APIContext) {
|
||||
return func(ctx *context.APIContext) {
|
||||
orgID, privileged, ok := teamAccessPrivileged(ctx)
|
||||
if !ok || privileged {
|
||||
return
|
||||
}
|
||||
denyNonTeamMember(ctx, orgID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -648,6 +687,17 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) {
|
||||
}
|
||||
return
|
||||
}
|
||||
if ctx.Org.Organization == nil {
|
||||
ctx.Org.Organization, err = organization.GetOrgByID(ctx, ctx.Org.Team.OrgID)
|
||||
if err != nil {
|
||||
if organization.IsErrOrgNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -976,6 +1026,11 @@ func Routes() *web.Router {
|
||||
})
|
||||
})
|
||||
|
||||
// Token introspection and deletion endpoint
|
||||
m.Combo("/token").
|
||||
Get(reqToken(), token.GetCurrentToken).
|
||||
Delete(reqToken(), token.DeleteCurrentToken)
|
||||
|
||||
// Notifications (requires 'notifications' scope)
|
||||
// The notifications API is not available for public-only tokens because a user's notifications mix
|
||||
// public and private repository events in the same mailbox.
|
||||
@@ -1188,6 +1243,7 @@ func Routes() *web.Router {
|
||||
m.Group("/actions/workflows", func() {
|
||||
m.Get("", repo.ActionsListRepositoryWorkflows)
|
||||
m.Get("/{workflow_id}", repo.ActionsGetWorkflow)
|
||||
m.Get("/{workflow_id}/runs", repo.ActionsListWorkflowRuns)
|
||||
m.Put("/{workflow_id}/disable", reqRepoWriter(unit.TypeActions), repo.ActionsDisableWorkflow)
|
||||
m.Put("/{workflow_id}/enable", reqRepoWriter(unit.TypeActions), repo.ActionsEnableWorkflow)
|
||||
m.Post("/{workflow_id}/dispatches", reqRepoWriter(unit.TypeActions), bind(api.CreateActionWorkflowDispatch{}), repo.ActionsDispatchWorkflow)
|
||||
@@ -1696,25 +1752,31 @@ func Routes() *web.Router {
|
||||
}, reqToken(), reqOrgOwnership())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true), checkTokenPublicOnly())
|
||||
m.Group("/teams/{teamid}", func() {
|
||||
m.Combo("").Get(reqToken(), org.GetTeam).
|
||||
Patch(reqToken(), reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
|
||||
m.Combo("").Patch(reqToken(), reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
|
||||
Delete(reqToken(), reqOrgOwnership(), org.DeleteTeam)
|
||||
m.Group("", func() {
|
||||
m.Get("", org.GetTeam)
|
||||
m.Group("/members", func() {
|
||||
m.Get("", reqOrgMembership(), org.GetTeamMembers)
|
||||
m.Combo("/{username}").Get(reqOrgMembership(), org.GetTeamMember)
|
||||
})
|
||||
m.Group("/repos", func() {
|
||||
m.Get("", org.GetTeamRepos)
|
||||
m.Combo("/{org}/{reponame}").Get(org.GetTeamRepo)
|
||||
})
|
||||
m.Get("/activities/feeds", org.ListTeamActivityFeeds)
|
||||
}, reqTeamReadAccess())
|
||||
m.Group("/members", func() {
|
||||
m.Get("", reqToken(), org.GetTeamMembers)
|
||||
m.Combo("/{username}").
|
||||
Get(reqToken(), org.GetTeamMember).
|
||||
Put(reqToken(), reqOrgOwnership(), org.AddTeamMember).
|
||||
Delete(reqToken(), reqOrgOwnership(), org.RemoveTeamMember)
|
||||
})
|
||||
m.Group("/repos", func() {
|
||||
m.Get("", reqToken(), org.GetTeamRepos)
|
||||
m.Combo("/{org}/{reponame}").
|
||||
Put(reqToken(), org.AddTeamRepository).
|
||||
Delete(reqToken(), org.RemoveTeamRepository).
|
||||
Get(reqToken(), org.GetTeamRepo)
|
||||
Put(reqToken(), reqTeamMembership(), org.AddTeamRepository).
|
||||
Delete(reqToken(), reqTeamMembership(), org.RemoveTeamRepository)
|
||||
})
|
||||
m.Get("/activities/feeds", org.ListTeamActivityFeeds)
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership(), checkTokenPublicOnly())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), checkTokenPublicOnly())
|
||||
|
||||
m.Group("/admin", func() {
|
||||
m.Group("/cron", func() {
|
||||
|
||||
@@ -679,7 +679,7 @@ func (Action) ListWorkflowRuns(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.ListRuns(ctx, ctx.Org.Organization.ID, 0)
|
||||
shared.ListRuns(ctx, ctx.Org.Organization.ID, 0, "")
|
||||
}
|
||||
|
||||
var _ actions_service.API = new(Action)
|
||||
|
||||
+30
-17
@@ -55,10 +55,15 @@ func ListTeams(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{
|
||||
opts := &organization.SearchTeamOptions{
|
||||
ListOptions: listOptions,
|
||||
OrgID: ctx.Org.Organization.ID,
|
||||
})
|
||||
}
|
||||
if err := organization.ApplyTeamListFilter(ctx, ctx.Org.Organization.ID, ctx.Doer, ctx.IsSigned, opts); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
teams, count, err := organization.SearchTeam(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -218,6 +223,7 @@ func CreateTeam(ctx *context.APIContext) {
|
||||
IncludesAllRepositories: form.IncludesAllRepositories,
|
||||
CanCreateOrgRepo: form.CanCreateOrgRepo,
|
||||
AccessMode: teamPermission,
|
||||
Visibility: organization.NormalizeTeamVisibility(string(form.Visibility)),
|
||||
}
|
||||
|
||||
if team.AccessMode < perm.AccessModeAdmin {
|
||||
@@ -295,6 +301,10 @@ func EditTeam(ctx *context.APIContext) {
|
||||
team.Description = *form.Description
|
||||
}
|
||||
|
||||
if form.Visibility != nil && !team.IsOwnerTeam() {
|
||||
team.Visibility = organization.NormalizeTeamVisibility(string(*form.Visibility))
|
||||
}
|
||||
|
||||
isAuthChanged := false
|
||||
isIncludeAllChanged := false
|
||||
if !team.IsOwnerTeam() && len(form.Permission) != 0 {
|
||||
@@ -387,15 +397,6 @@ func GetTeamMembers(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
isMember, err := organization.IsOrganizationMember(ctx, ctx.Org.Team.OrgID, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
} else if !isMember && !ctx.Doer.IsAdmin {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
teamMembers, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{
|
||||
ListOptions: listOptions,
|
||||
@@ -574,14 +575,20 @@ func GetTeamRepos(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
repos := make([]*api.Repository, len(teamRepos))
|
||||
for i, repo := range teamRepos {
|
||||
repos := make([]*api.Repository, 0, len(teamRepos))
|
||||
for _, repo := range teamRepos {
|
||||
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
repos[i] = convert.ToRepo(ctx, repo, permission)
|
||||
// A team's repo list is reachable by non-team-members through the team's
|
||||
// visibility tier, so never expose repos (incl. their names) the doer
|
||||
// cannot access.
|
||||
if !permission.HasAnyUnitAccessOrPublicAccess() {
|
||||
continue
|
||||
}
|
||||
repos = append(repos, convert.ToRepo(ctx, repo, permission))
|
||||
}
|
||||
ctx.SetLinkHeader(int64(team.NumRepos), listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(int64(team.NumRepos))
|
||||
@@ -633,6 +640,12 @@ func GetTeamRepo(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
// The team may be reachable by a non-team-member via its visibility tier;
|
||||
// don't confirm the existence of a repo the doer cannot access.
|
||||
if !permission.HasAnyUnitAccessOrPublicAccess() {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToRepo(ctx, repo, permission))
|
||||
}
|
||||
@@ -806,9 +819,9 @@ func SearchTeam(ctx *context.APIContext) {
|
||||
ListOptions: listOptions,
|
||||
}
|
||||
|
||||
// Only admin is allowed to search for all teams
|
||||
if !ctx.Doer.IsAdmin {
|
||||
opts.UserID = ctx.Doer.ID
|
||||
if err := organization.ApplyTeamListFilter(ctx, ctx.Org.Organization.ID, ctx.Doer, ctx.IsSigned, opts); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
teams, maxResults, err := organization.SearchTeam(ctx, opts)
|
||||
|
||||
@@ -772,6 +772,11 @@ func (Action) ListWorkflowRuns(ctx *context.APIContext) {
|
||||
// description: triggering sha of the workflow run
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: exclude_pull_requests
|
||||
// in: query
|
||||
// description: if true, the `pull_requests` field on each returned run is emptied
|
||||
// type: boolean
|
||||
// required: false
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
@@ -790,7 +795,7 @@ func (Action) ListWorkflowRuns(ctx *context.APIContext) {
|
||||
|
||||
repoID := ctx.Repo.Repository.ID
|
||||
|
||||
shared.ListRuns(ctx, 0, repoID)
|
||||
shared.ListRuns(ctx, 0, repoID, "")
|
||||
}
|
||||
|
||||
var _ actions_service.API = new(Action)
|
||||
@@ -967,6 +972,97 @@ func ActionsGetWorkflow(ctx *context.APIContext) {
|
||||
ctx.JSON(http.StatusOK, workflow)
|
||||
}
|
||||
|
||||
func ActionsListWorkflowRuns(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs repository ActionsListWorkflowRuns
|
||||
// ---
|
||||
// summary: List runs for a workflow
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: workflow_id
|
||||
// in: path
|
||||
// description: id of the workflow, must be the workflow file name (e.g. `build.yml`)
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: event
|
||||
// in: query
|
||||
// description: workflow event name
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: branch
|
||||
// in: query
|
||||
// description: workflow branch
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: status
|
||||
// in: query
|
||||
// description: workflow status (pending, queued, in_progress, failure, success, skipped)
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: actor
|
||||
// in: query
|
||||
// description: triggered by user
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: head_sha
|
||||
// in: query
|
||||
// description: triggering sha of the workflow run
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: exclude_pull_requests
|
||||
// in: query
|
||||
// description: if true, the `pull_requests` field on each returned run is emptied
|
||||
// type: boolean
|
||||
// required: false
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/WorkflowRunsList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
workflowID := ctx.PathParam("workflow_id")
|
||||
// Existing runs prove the workflow is/was valid and cover historical workflows
|
||||
// whose file was later removed. Fall back to a git lookup for never-run workflows.
|
||||
runExists, err := db.Exist[actions_model.ActionRun](ctx, actions_model.FindRunOptions{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
WorkflowID: workflowID,
|
||||
}.ToConds())
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if !runExists {
|
||||
if _, err := convert.GetActionWorkflow(ctx, ctx.Repo.GitRepo, ctx.Repo.Repository, workflowID); err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
shared.ListRuns(ctx, 0, ctx.Repo.Repository.ID, workflowID)
|
||||
}
|
||||
|
||||
func ActionsDisableWorkflow(ctx *context.APIContext) {
|
||||
// swagger:operation PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/disable repository ActionsDisableWorkflow
|
||||
// ---
|
||||
@@ -1238,7 +1334,7 @@ func GetWorkflowRun(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil)
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil, false)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -1287,7 +1383,7 @@ func GetWorkflowRunAttempt(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, attempt)
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, attempt, false)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -1342,7 +1438,7 @@ func RerunWorkflowRun(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil)
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil, false)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
git_service "gitea.dev/services/git"
|
||||
)
|
||||
|
||||
// CompareDiff compare two branches or commits
|
||||
@@ -18,8 +19,12 @@ func CompareDiff(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/compare/{basehead} repository repoCompareDiff
|
||||
// ---
|
||||
// summary: Get commit comparison information
|
||||
// description: |
|
||||
// By default returns JSON commit comparison information. The raw diff or patch can be
|
||||
// requested with the `output` query parameter set to `diff` or `patch` respectively.
|
||||
// produces:
|
||||
// - application/json
|
||||
// - text/plain
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
@@ -33,9 +38,16 @@ func CompareDiff(ctx *context.APIContext) {
|
||||
// required: true
|
||||
// - name: basehead
|
||||
// in: path
|
||||
// description: compare two branches or commits
|
||||
// description: compare two refs as `base...head` (or `base..head`); refs may be branches, tags, full or short SHAs, including branch names that contain slashes.
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: output
|
||||
// in: query
|
||||
// description: return the raw comparison as `diff` or `patch` instead of JSON
|
||||
// type: string
|
||||
// enum:
|
||||
// - diff
|
||||
// - patch
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Compare"
|
||||
@@ -57,6 +69,16 @@ func CompareDiff(ctx *context.APIContext) {
|
||||
}
|
||||
defer closer()
|
||||
|
||||
// ?output=diff|patch returns the raw output, otherwise the JSON comparison is returned.
|
||||
switch ctx.FormString("output") {
|
||||
case "diff":
|
||||
downloadCompareDiffOrPatch(ctx, compareInfo, false)
|
||||
return
|
||||
case "patch":
|
||||
downloadCompareDiffOrPatch(ctx, compareInfo, true)
|
||||
return
|
||||
}
|
||||
|
||||
verification := ctx.FormString("verification") == "" || ctx.FormBool("verification")
|
||||
files := ctx.FormString("files") == "" || ctx.FormBool("files")
|
||||
|
||||
@@ -88,3 +110,20 @@ func CompareDiff(ctx *context.APIContext) {
|
||||
Commits: apiCommits,
|
||||
})
|
||||
}
|
||||
|
||||
// downloadCompareDiffOrPatch writes a comparison's raw diff or patch to the response.
|
||||
func downloadCompareDiffOrPatch(ctx *context.APIContext, compareInfo *git_service.CompareInfo, patch bool) {
|
||||
ctx.Resp.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
compareArg := compareInfo.BaseCommitID + compareInfo.CompareSeparator + compareInfo.HeadCommitID
|
||||
|
||||
var err error
|
||||
if patch {
|
||||
err = compareInfo.HeadGitRepo.GetPatch(compareArg, ctx.Resp)
|
||||
} else {
|
||||
err = compareInfo.HeadGitRepo.GetDiff(compareArg, ctx.Resp)
|
||||
}
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ func ListTrackedTimes(ctx *context.APIContext) {
|
||||
user, err := user_model.GetUserByName(ctx, qUser)
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
return
|
||||
} else if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -499,6 +500,7 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) {
|
||||
user, err := user_model.GetUserByName(ctx, qUser)
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusNotFound, err.Error())
|
||||
return
|
||||
} else if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
|
||||
@@ -135,8 +135,9 @@ func convertToInternal(s string) ([]actions_model.Status, error) {
|
||||
// ownerID == 0 and repoID != 0 means all runs for the given repo
|
||||
// ownerID != 0 and repoID == 0 means all runs for the given user/org
|
||||
// ownerID != 0 and repoID != 0 undefined behavior
|
||||
// workflowID filters runs by workflow file name (e.g. "build.yml"), empty means no filter
|
||||
// Access rights are checked at the API route level
|
||||
func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
func ListRuns(ctx *context.APIContext, ownerID, repoID int64, workflowID string) {
|
||||
if ownerID != 0 && repoID != 0 {
|
||||
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||
}
|
||||
@@ -144,6 +145,7 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
opts := actions_model.FindRunOptions{
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
WorkflowID: workflowID,
|
||||
ListOptions: listOptions,
|
||||
}
|
||||
|
||||
@@ -172,6 +174,7 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
if headSHA := ctx.FormString("head_sha"); headSHA != "" {
|
||||
opts.CommitSHA = headSHA
|
||||
}
|
||||
excludePullRequests := ctx.FormBool("exclude_pull_requests")
|
||||
|
||||
runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, opts)
|
||||
if err != nil {
|
||||
@@ -203,7 +206,7 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
res.Entries = make([]*api.ActionWorkflowRun, len(runs))
|
||||
for i := range runs {
|
||||
// TODO: load run attempts in batch
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, runs[i], nil)
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, runs[i], nil, excludePullRequests)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
|
||||
@@ -20,3 +20,10 @@ type swaggerResponseAccessToken struct {
|
||||
// in:body
|
||||
Body api.AccessToken `json:"body"`
|
||||
}
|
||||
|
||||
// CurrentAccessToken represents the currently authenticated access token.
|
||||
// swagger:response CurrentAccessToken
|
||||
type swaggerResponseCurrentAccessToken struct {
|
||||
// in:body
|
||||
Body api.CurrentAccessToken `json:"body"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package token
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
auth_model "gitea.dev/models/auth"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/auth/httpauth"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
// GetCurrentToken returns metadata about the currently authenticated token.
|
||||
func GetCurrentToken(ctx *context.APIContext) {
|
||||
// swagger:operation GET /token miscellaneous getCurrentToken
|
||||
// ---
|
||||
// summary: Get the currently authenticated token
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/CurrentAccessToken"
|
||||
accessToken, err := getToken(ctx)
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get user info
|
||||
user, err := user_model.GetUserByID(ctx, accessToken.UID)
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, &api.CurrentAccessToken{
|
||||
ID: accessToken.ID,
|
||||
Name: accessToken.Name,
|
||||
Scopes: accessToken.Scope.StringSlice(),
|
||||
CreatedAt: accessToken.CreatedUnix.AsTime(),
|
||||
LastUsedAt: accessToken.UpdatedUnix.AsTime(),
|
||||
User: &api.UserMeta{
|
||||
ID: user.ID,
|
||||
Login: user.Name,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteCurrentToken deletes the currently authenticated token.
|
||||
func DeleteCurrentToken(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /token miscellaneous deleteCurrentToken
|
||||
// ---
|
||||
// summary: Delete the currently authenticated token
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "204":
|
||||
// description: token deleted
|
||||
accessToken, err := getToken(ctx)
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete the token
|
||||
err = auth_model.DeleteAccessTokenByID(ctx, accessToken.ID, accessToken.UID)
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// getToken retrieves an access token from the API context's Authorization header and validates it against the database.
|
||||
// Returns nil if the token is invalid and handles the response
|
||||
func getToken(ctx *context.APIContext) (*auth_model.AccessToken, error) {
|
||||
authHeader := ctx.Req.Header.Get("Authorization")
|
||||
parsed, ok := httpauth.ParseAuthorizationHeader(authHeader)
|
||||
if !ok || parsed.BearerToken == nil {
|
||||
return nil, util.NewNotExistErrorf("invalid access token")
|
||||
}
|
||||
return auth_model.GetAccessTokenBySHA(ctx, parsed.BearerToken.Token)
|
||||
}
|
||||
@@ -407,7 +407,7 @@ func ListWorkflowRuns(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.ListRuns(ctx, ctx.Doer.ID, 0)
|
||||
shared.ListRuns(ctx, ctx.Doer.ID, 0, "")
|
||||
}
|
||||
|
||||
// ListWorkflowJobs lists workflow jobs
|
||||
|
||||
@@ -191,17 +191,9 @@ func DeleteAccessToken(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if tokenID == 0 {
|
||||
ctx.APIErrorInternal(nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := auth_model.DeleteAccessTokenByID(ctx, tokenID, ctx.ContextUser.ID); err != nil {
|
||||
if auth_model.IsErrAccessTokenNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user