mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-09-27 22:12:04 +00:00
Merge branch 'forgejo' into syncfork
This commit is contained in:
commit
24ee728804
1640 changed files with 50075 additions and 31636 deletions
11
.air.toml
11
.air.toml
|
@ -8,6 +8,15 @@ delay = 1000
|
|||
include_ext = ["go", "tmpl"]
|
||||
include_file = ["main.go"]
|
||||
include_dir = ["cmd", "models", "modules", "options", "routers", "services"]
|
||||
exclude_dir = ["modules/git/tests", "services/gitdiff/testdata", "modules/avatar/testdata", "models/fixtures", "models/migrations/fixtures", "modules/migration/file_format_testdata", "modules/avatar/identicon/testdata"]
|
||||
exclude_dir = [
|
||||
"models/fixtures",
|
||||
"models/migrations/fixtures",
|
||||
"modules/avatar/identicon/testdata",
|
||||
"modules/avatar/testdata",
|
||||
"modules/git/tests",
|
||||
"modules/migration/file_format_testdata",
|
||||
"routers/private/tests",
|
||||
"services/gitdiff/testdata",
|
||||
]
|
||||
exclude_regex = ["_test.go$", "_gen.go$"]
|
||||
stop_on_error = true
|
||||
|
|
|
@ -84,7 +84,6 @@ package "code.gitea.io/gitea/models/repo"
|
|||
func (*releaseSorter).Swap
|
||||
func SortReleases
|
||||
func FindReposMapByIDs
|
||||
func RepositoryListOfMap
|
||||
func (SearchOrderBy).String
|
||||
func IsErrTopicNotExist
|
||||
func (ErrTopicNotExist).Error
|
||||
|
@ -162,9 +161,6 @@ package "code.gitea.io/gitea/modules/cache"
|
|||
package "code.gitea.io/gitea/modules/charset"
|
||||
func (*BreakWriter).Write
|
||||
|
||||
package "code.gitea.io/gitea/modules/context"
|
||||
func GetPrivateContext
|
||||
|
||||
package "code.gitea.io/gitea/modules/emoji"
|
||||
func ReplaceCodes
|
||||
|
||||
|
@ -181,6 +177,7 @@ package "code.gitea.io/gitea/modules/git"
|
|||
func (ErrExecTimeout).Error
|
||||
func (ErrUnsupportedVersion).Error
|
||||
func SetUpdateHook
|
||||
func GetObjectFormatOfRepo
|
||||
func openRepositoryWithDefaultContext
|
||||
func IsTagExist
|
||||
func ToEntryMode
|
||||
|
@ -192,6 +189,7 @@ package "code.gitea.io/gitea/modules/gitgraph"
|
|||
|
||||
package "code.gitea.io/gitea/modules/gitrepo"
|
||||
func GetBranchCommitID
|
||||
func GetWikiDefaultBranch
|
||||
|
||||
package "code.gitea.io/gitea/modules/graceful"
|
||||
func (*Manager).TerminateContext
|
||||
|
@ -224,7 +222,6 @@ package "code.gitea.io/gitea/modules/markup/markdown"
|
|||
func IsSummary
|
||||
func IsTaskCheckBoxListItem
|
||||
func IsIcon
|
||||
func IsColorPreview
|
||||
func RenderRawString
|
||||
|
||||
package "code.gitea.io/gitea/modules/markup/markdown/math"
|
||||
|
@ -274,13 +271,19 @@ package "code.gitea.io/gitea/modules/sync"
|
|||
|
||||
package "code.gitea.io/gitea/modules/testlogger"
|
||||
func (*testLoggerWriterCloser).pushT
|
||||
func (*testLoggerWriterCloser).Write
|
||||
func (*testLoggerWriterCloser).Log
|
||||
func (*testLoggerWriterCloser).recordError
|
||||
func (*testLoggerWriterCloser).printMsg
|
||||
func (*testLoggerWriterCloser).popT
|
||||
func (*testLoggerWriterCloser).Close
|
||||
func (*testLoggerWriterCloser).Reset
|
||||
func PrintCurrentTest
|
||||
func Printf
|
||||
func NewTestLoggerWriter
|
||||
func (*TestLogEventWriter).Base
|
||||
func (*TestLogEventWriter).GetLevel
|
||||
func (*TestLogEventWriter).GetWriterName
|
||||
func (*TestLogEventWriter).GetWriterType
|
||||
func (*TestLogEventWriter).Run
|
||||
|
||||
package "code.gitea.io/gitea/modules/timeutil"
|
||||
func GetExecutableModTime
|
||||
|
@ -292,11 +295,9 @@ package "code.gitea.io/gitea/modules/translation"
|
|||
func (MockLocale).TrString
|
||||
func (MockLocale).Tr
|
||||
func (MockLocale).TrN
|
||||
func (MockLocale).TrSize
|
||||
func (MockLocale).PrettyNumber
|
||||
|
||||
package "code.gitea.io/gitea/modules/util"
|
||||
func UnsafeStringToBytes
|
||||
|
||||
package "code.gitea.io/gitea/modules/util/filebuffer"
|
||||
func CreateFromReader
|
||||
|
||||
|
@ -315,6 +316,9 @@ package "code.gitea.io/gitea/routers/web/org"
|
|||
func getActionIssues
|
||||
func UpdateIssueProject
|
||||
|
||||
package "code.gitea.io/gitea/services/context"
|
||||
func GetPrivateContext
|
||||
|
||||
package "code.gitea.io/gitea/services/convert"
|
||||
func ToSecret
|
||||
|
||||
|
@ -326,7 +330,6 @@ package "code.gitea.io/gitea/services/pull"
|
|||
|
||||
package "code.gitea.io/gitea/services/repository"
|
||||
func IsErrForkAlreadyExist
|
||||
func UpdateRepositoryUnits
|
||||
|
||||
package "code.gitea.io/gitea/services/repository/archiver"
|
||||
func ArchiveRepository
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
"features": {
|
||||
// installs nodejs into container
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version":"20"
|
||||
"version": "20"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/git-lfs:1.1.0": {},
|
||||
"ghcr.io/devcontainers-contrib/features/poetry:2": {},
|
||||
"ghcr.io/devcontainers/features/python:1": {}
|
||||
"ghcr.io/devcontainers/features/python:1": {
|
||||
"version": "3.12"
|
||||
}
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
|
@ -22,7 +24,7 @@
|
|||
"DavidAnson.vscode-markdownlint",
|
||||
"Vue.volar",
|
||||
"ms-azuretools.vscode-docker",
|
||||
"zixuanchen.vitest-explorer",
|
||||
"vitest.explorer",
|
||||
"qwtel.sqlite-viewer",
|
||||
"GitHub.vscode-pull-request-github"
|
||||
]
|
||||
|
|
|
@ -14,7 +14,7 @@ _test
|
|||
|
||||
# MS VSCode
|
||||
.vscode
|
||||
__debug_bin
|
||||
__debug_bin*
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
|
@ -62,7 +62,6 @@ cpu.out
|
|||
/data
|
||||
/indexers
|
||||
/log
|
||||
/public/img/avatar
|
||||
/tests/integration/gitea-integration-*
|
||||
/tests/integration/indexers-*
|
||||
/tests/e2e/gitea-e2e-*
|
||||
|
@ -71,13 +70,13 @@ cpu.out
|
|||
/tests/e2e/test-artifacts
|
||||
/tests/e2e/test-snapshots
|
||||
/tests/*.ini
|
||||
/node_modules
|
||||
/yarn.lock
|
||||
/yarn-error.log
|
||||
/npm-debug.log*
|
||||
/public/assets/js
|
||||
/public/assets/css
|
||||
/public/assets/fonts
|
||||
/public/assets/img/avatar
|
||||
/public/assets/img/webpack
|
||||
/vendor
|
||||
/web_src/fomantic/node_modules
|
||||
|
|
|
@ -12,6 +12,7 @@ plugins:
|
|||
- "@eslint-community/eslint-plugin-eslint-comments"
|
||||
- "@stylistic/eslint-plugin-js"
|
||||
- eslint-plugin-array-func
|
||||
- eslint-plugin-github
|
||||
- eslint-plugin-i
|
||||
- eslint-plugin-jquery
|
||||
- eslint-plugin-no-jquery
|
||||
|
@ -41,10 +42,6 @@ overrides:
|
|||
worker: true
|
||||
rules:
|
||||
no-restricted-globals: [2, addEventListener, blur, close, closed, confirm, defaultStatus, defaultstatus, error, event, external, find, focus, frameElement, frames, history, innerHeight, innerWidth, isFinite, isNaN, length, locationbar, menubar, moveBy, moveTo, name, onblur, onerror, onfocus, onload, onresize, onunload, open, opener, opera, outerHeight, outerWidth, pageXOffset, pageYOffset, parent, print, removeEventListener, resizeBy, resizeTo, screen, screenLeft, screenTop, screenX, screenY, scroll, scrollbars, scrollBy, scrollTo, scrollX, scrollY, status, statusbar, stop, toolbar, top]
|
||||
- files: ["build/generate-images.js"]
|
||||
rules:
|
||||
i/no-unresolved: [0]
|
||||
i/no-extraneous-dependencies: [0]
|
||||
- files: ["*.config.*"]
|
||||
rules:
|
||||
i/no-unused-modules: [0]
|
||||
|
@ -122,7 +119,7 @@ rules:
|
|||
"@stylistic/js/arrow-spacing": [2, {before: true, after: true}]
|
||||
"@stylistic/js/block-spacing": [0]
|
||||
"@stylistic/js/brace-style": [2, 1tbs, {allowSingleLine: true}]
|
||||
"@stylistic/js/comma-dangle": [2, only-multiline]
|
||||
"@stylistic/js/comma-dangle": [2, always-multiline]
|
||||
"@stylistic/js/comma-spacing": [2, {before: false, after: true}]
|
||||
"@stylistic/js/comma-style": [2, last]
|
||||
"@stylistic/js/computed-property-spacing": [2, never]
|
||||
|
@ -170,7 +167,7 @@ rules:
|
|||
"@stylistic/js/semi-spacing": [2, {before: false, after: true}]
|
||||
"@stylistic/js/semi-style": [2, last]
|
||||
"@stylistic/js/space-before-blocks": [2, always]
|
||||
"@stylistic/js/space-before-function-paren": [0]
|
||||
"@stylistic/js/space-before-function-paren": [2, {anonymous: ignore, named: never, asyncArrow: always}]
|
||||
"@stylistic/js/space-in-parens": [2, never]
|
||||
"@stylistic/js/space-infix-ops": [2]
|
||||
"@stylistic/js/space-unary-ops": [2]
|
||||
|
@ -209,6 +206,29 @@ rules:
|
|||
func-names: [0]
|
||||
func-style: [0]
|
||||
getter-return: [2]
|
||||
github/a11y-aria-label-is-well-formatted: [0]
|
||||
github/a11y-no-title-attribute: [0]
|
||||
github/a11y-no-visually-hidden-interactive-element: [0]
|
||||
github/a11y-role-supports-aria-props: [0]
|
||||
github/a11y-svg-has-accessible-name: [0]
|
||||
github/array-foreach: [0]
|
||||
github/async-currenttarget: [2]
|
||||
github/async-preventdefault: [2]
|
||||
github/authenticity-token: [0]
|
||||
github/get-attribute: [0]
|
||||
github/js-class-name: [0]
|
||||
github/no-blur: [0]
|
||||
github/no-d-none: [0]
|
||||
github/no-dataset: [2]
|
||||
github/no-dynamic-script-tag: [2]
|
||||
github/no-implicit-buggy-globals: [2]
|
||||
github/no-inner-html: [0]
|
||||
github/no-innerText: [2]
|
||||
github/no-then: [2]
|
||||
github/no-useless-passive: [2]
|
||||
github/prefer-observers: [2]
|
||||
github/require-passive-events: [2]
|
||||
github/unescaped-html-literal: [0]
|
||||
grouped-accessor-pairs: [2]
|
||||
guard-for-in: [0]
|
||||
id-blacklist: [0]
|
||||
|
@ -259,20 +279,20 @@ rules:
|
|||
i/unambiguous: [0]
|
||||
init-declarations: [0]
|
||||
jquery/no-ajax-events: [2]
|
||||
jquery/no-ajax: [0]
|
||||
jquery/no-ajax: [2]
|
||||
jquery/no-animate: [2]
|
||||
jquery/no-attr: [0]
|
||||
jquery/no-attr: [2]
|
||||
jquery/no-bind: [2]
|
||||
jquery/no-class: [0]
|
||||
jquery/no-clone: [2]
|
||||
jquery/no-closest: [0]
|
||||
jquery/no-css: [0]
|
||||
jquery/no-css: [2]
|
||||
jquery/no-data: [0]
|
||||
jquery/no-deferred: [2]
|
||||
jquery/no-delegate: [2]
|
||||
jquery/no-each: [0]
|
||||
jquery/no-extend: [2]
|
||||
jquery/no-fade: [0]
|
||||
jquery/no-fade: [2]
|
||||
jquery/no-filter: [0]
|
||||
jquery/no-find: [0]
|
||||
jquery/no-global-eval: [2]
|
||||
|
@ -283,15 +303,15 @@ rules:
|
|||
jquery/no-in-array: [2]
|
||||
jquery/no-is-array: [2]
|
||||
jquery/no-is-function: [2]
|
||||
jquery/no-is: [0]
|
||||
jquery/no-is: [2]
|
||||
jquery/no-load: [2]
|
||||
jquery/no-map: [0]
|
||||
jquery/no-map: [2]
|
||||
jquery/no-merge: [2]
|
||||
jquery/no-param: [2]
|
||||
jquery/no-parent: [0]
|
||||
jquery/no-parents: [0]
|
||||
jquery/no-parse-html: [2]
|
||||
jquery/no-prop: [0]
|
||||
jquery/no-prop: [2]
|
||||
jquery/no-proxy: [2]
|
||||
jquery/no-ready: [2]
|
||||
jquery/no-serialize: [2]
|
||||
|
@ -372,12 +392,12 @@ rules:
|
|||
no-irregular-whitespace: [2]
|
||||
no-iterator: [2]
|
||||
no-jquery/no-ajax-events: [2]
|
||||
no-jquery/no-ajax: [0]
|
||||
no-jquery/no-ajax: [2]
|
||||
no-jquery/no-and-self: [2]
|
||||
no-jquery/no-animate-toggle: [2]
|
||||
no-jquery/no-animate: [2]
|
||||
no-jquery/no-append-html: [0]
|
||||
no-jquery/no-attr: [0]
|
||||
no-jquery/no-append-html: [2]
|
||||
no-jquery/no-attr: [2]
|
||||
no-jquery/no-bind: [2]
|
||||
no-jquery/no-box-model: [2]
|
||||
no-jquery/no-browser: [2]
|
||||
|
@ -389,7 +409,7 @@ rules:
|
|||
no-jquery/no-constructor-attributes: [2]
|
||||
no-jquery/no-contains: [2]
|
||||
no-jquery/no-context-prop: [2]
|
||||
no-jquery/no-css: [0]
|
||||
no-jquery/no-css: [2]
|
||||
no-jquery/no-data: [0]
|
||||
no-jquery/no-deferred: [2]
|
||||
no-jquery/no-delegate: [2]
|
||||
|
@ -420,14 +440,14 @@ rules:
|
|||
no-jquery/no-is-numeric: [2]
|
||||
no-jquery/no-is-plain-object: [2]
|
||||
no-jquery/no-is-window: [2]
|
||||
no-jquery/no-is: [0]
|
||||
no-jquery/no-is: [2]
|
||||
no-jquery/no-jquery-constructor: [0]
|
||||
no-jquery/no-live: [2]
|
||||
no-jquery/no-load-shorthand: [2]
|
||||
no-jquery/no-load: [2]
|
||||
no-jquery/no-map-collection: [0]
|
||||
no-jquery/no-map-util: [2]
|
||||
no-jquery/no-map: [0]
|
||||
no-jquery/no-map: [2]
|
||||
no-jquery/no-merge: [2]
|
||||
no-jquery/no-node-name: [2]
|
||||
no-jquery/no-noop: [2]
|
||||
|
@ -442,7 +462,7 @@ rules:
|
|||
no-jquery/no-parse-html: [2]
|
||||
no-jquery/no-parse-json: [2]
|
||||
no-jquery/no-parse-xml: [2]
|
||||
no-jquery/no-prop: [0]
|
||||
no-jquery/no-prop: [2]
|
||||
no-jquery/no-proxy: [2]
|
||||
no-jquery/no-ready-shorthand: [2]
|
||||
no-jquery/no-ready: [2]
|
||||
|
@ -463,7 +483,7 @@ rules:
|
|||
no-jquery/no-visibility: [2]
|
||||
no-jquery/no-when: [2]
|
||||
no-jquery/no-wrap: [2]
|
||||
no-jquery/variable-pattern: [0]
|
||||
no-jquery/variable-pattern: [2]
|
||||
no-label-var: [2]
|
||||
no-labels: [0] # handled by no-restricted-syntax
|
||||
no-lone-blocks: [2]
|
||||
|
|
|
@ -5,17 +5,29 @@ set -ex
|
|||
end_to_end=$1
|
||||
end_to_end_pr=$2
|
||||
forgejo=$3
|
||||
forgejo_pr=$4
|
||||
forgejo_pr_or_ref=$4
|
||||
|
||||
cd $forgejo
|
||||
full_version=$(make show-version-full)
|
||||
minor_version=$(make show-version-minor)
|
||||
|
||||
head_url=$(jq --raw-output .head.repo.html_url < $forgejo_pr)
|
||||
test "$head_url" != null
|
||||
branch=$(jq --raw-output .head.ref < $forgejo_pr)
|
||||
test "$branch" != null
|
||||
cd $end_to_end
|
||||
echo $head_url $branch 7.0.0+0-gitea-1.22.0 > forgejo/sources/1.22
|
||||
|
||||
if ! test -f forgejo/sources/$minor_version ; then
|
||||
echo "FAIL: forgejo/sources/$minor_version does not exist in the end-to-end repository"
|
||||
false
|
||||
fi
|
||||
|
||||
date > last-upgrade
|
||||
|
||||
base_url=$(jq --raw-output .base.repo.html_url < $forgejo_pr)
|
||||
test "$base_url" != null
|
||||
test "$GITHUB_RUN_NUMBER"
|
||||
echo $base_url/actions/runs/$GITHUB_RUN_NUMBER/artifacts/forgejo > forgejo/binary-url
|
||||
if test -f "$forgejo_pr_or_ref" ; then
|
||||
forgejo_pr=$forgejo_pr_or_ref
|
||||
head_url=$(jq --raw-output .head.repo.html_url < $forgejo_pr)
|
||||
test "$head_url" != null
|
||||
branch=$(jq --raw-output .head.ref < $forgejo_pr)
|
||||
test "$branch" != null
|
||||
echo $head_url $branch $full_version > forgejo/sources/$minor_version
|
||||
else
|
||||
forgejo_ref=$forgejo_pr_or_ref
|
||||
echo $GITHUB_SERVER_URL/$GITHUB_REPOSITORY ${forgejo_ref#refs/heads/} $full_version > forgejo/sources/$minor_version
|
||||
fi
|
||||
|
|
|
@ -9,4 +9,14 @@ forgejo_ref=$4
|
|||
|
||||
cd $end_to_end
|
||||
date > last-upgrade
|
||||
echo $FORGEJO_BINARY > forgejo/binary-url
|
||||
organizations=lib/ORGANIZATIONS
|
||||
if ! test -f $organizations ; then
|
||||
echo "$organizations file not found"
|
||||
false
|
||||
fi
|
||||
#
|
||||
# do not include forgejo-experimental so that 7.0-test is found
|
||||
# in forgejo-integration where it was just built instead of
|
||||
# forgejo-experimental which was published by the previous build
|
||||
#
|
||||
echo forgejo forgejo-integration > $organizations
|
||||
|
|
83
.forgejo/workflows/backport.yml
Normal file
83
.forgejo/workflows/backport.yml
Normal file
|
@ -0,0 +1,83 @@
|
|||
# Copyright 2024 The Forgejo Authors
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# To modify this workflow:
|
||||
#
|
||||
# - change pull_request_target: to pull_request:
|
||||
# so that it runs from a pull request instead of the default branch
|
||||
#
|
||||
# - push it to the wip-ci-backport branch on the forgejo repository
|
||||
# otherwise it will not have access to the secrets required to push
|
||||
# the PR
|
||||
#
|
||||
# - open a pull request targetting wip-ci-backport that includes a change
|
||||
# that can be backported without conflict in v1.21 and set the
|
||||
# `backport/v1.21` label.
|
||||
#
|
||||
# - once it works, open a pull request for the sake of keeping track
|
||||
# of the change even if the PR won't run it because it will use
|
||||
# whatever is in the default branch instead
|
||||
#
|
||||
# - after it is merged, double check it works by setting a
|
||||
# `backport/v1.21` label on a merged pull request that can be backported
|
||||
# without conflict.
|
||||
#
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
- labeled
|
||||
|
||||
jobs:
|
||||
backporting:
|
||||
if: >
|
||||
!startsWith(vars.ROLE, 'forgejo-') && (
|
||||
github.event.pull_request.merged
|
||||
&&
|
||||
contains(toJSON(github.event.pull_request.labels), 'backport/v')
|
||||
)
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'docker.io/node:20-bookworm'
|
||||
steps:
|
||||
- name: event info
|
||||
run: |
|
||||
cat <<'EOF'
|
||||
${{ toJSON(github) }}
|
||||
EOF
|
||||
- name: Fetch labels
|
||||
id: fetch-labels
|
||||
shell: bash
|
||||
run: |
|
||||
set -x
|
||||
echo "Labels retrieved below"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
apt-get -q install -qq -y jq
|
||||
filtered_labels=$(echo "$LABELS" | jq -c 'map(select(.name | startswith("backport/v")))')
|
||||
echo "FILTERED_LABELS=${filtered_labels}" >> $GITHUB_ENV
|
||||
env:
|
||||
LABELS: ${{ toJSON(github.event.pull_request.labels) }}
|
||||
- name: Extract targets
|
||||
id: extract-targets
|
||||
shell: bash
|
||||
run: |
|
||||
set -x
|
||||
targets="$(echo $FILTERED_LABELS | jq -c '[.[] | .name | sub("backport/"; "")]')"
|
||||
echo "targets=$(echo $targets)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Printing info
|
||||
shell: bash
|
||||
run: |
|
||||
echo "targets: ${{ steps.extract-targets.outputs.targets }}"
|
||||
echo "target-branch: ${{ fromJSON(steps.extract-targets.outputs.targets)[0] }}"
|
||||
echo "pull-request: ${{ github.event.pull_request.url }}"
|
||||
|
||||
- uses: https://code.forgejo.org/forgejo/git-backporting@b2554a678d5ea2814f0df3abad2ac4fcdee2d81f
|
||||
with:
|
||||
target-branch: ${{ fromJSON(steps.extract-targets.outputs.targets)[0] }}/forgejo
|
||||
strategy: ort
|
||||
strategy-option: find-renames
|
||||
cherry-pick-options: -x
|
||||
auth: ${{ secrets.BACKPORT_TOKEN }}
|
||||
pull-request: ${{ github.event.pull_request.url }}
|
|
@ -9,6 +9,8 @@ on:
|
|||
- docker/**
|
||||
- .forgejo/workflows/build-release.yml
|
||||
- .forgejo/workflows/build-release-integration.yml
|
||||
branches-ignore:
|
||||
- renovate/**
|
||||
pull_request:
|
||||
paths:
|
||||
- Makefile
|
||||
|
@ -110,7 +112,7 @@ jobs:
|
|||
#
|
||||
# Push a commit to a branch that triggers the build of a test release
|
||||
#
|
||||
version=forgejo-test
|
||||
version=1.2-test
|
||||
(
|
||||
git clone $url/root/forgejo /tmp/forgejo
|
||||
cd /tmp/forgejo
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
# secrets.CASCADE_DESTINATION_TOKEN: <generated from code.forgejo.org/forgejo-ci> scope read:user, write:repository, write:issue
|
||||
# vars.CASCADE_DESTINATION_DOER: forgejo-ci
|
||||
#
|
||||
name: Build release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: 'v[0-9]+.[0-9]+.*'
|
||||
|
@ -30,6 +28,8 @@ jobs:
|
|||
if: vars.ROLE == 'forgejo-integration' || github.repository_owner == 'root'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Sanitize the name of the repository
|
||||
id: repository
|
||||
|
@ -43,7 +43,7 @@ jobs:
|
|||
|
||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.21"
|
||||
go-version: "1.22"
|
||||
check-latest: true
|
||||
|
||||
- name: version from ref
|
||||
|
@ -53,8 +53,12 @@ jobs:
|
|||
set -x
|
||||
ref="${{ github.ref }}"
|
||||
if [[ $ref =~ ^refs/heads/ ]] ; then
|
||||
version=${ref#refs/heads/}
|
||||
version=${version%/forgejo}-test
|
||||
if test "$ref" = "refs/heads/forgejo" ; then
|
||||
version=$(git tag -l --sort=version:refname --merged | grep -v -e '-test$' | tail -1 | sed -E -e 's/^(v[0-9]+\.[0-9]+).*/\1/')-test
|
||||
else
|
||||
version=${ref#refs/heads/}
|
||||
version=${version%/forgejo}-test
|
||||
fi
|
||||
override=true
|
||||
fi
|
||||
if [[ $ref =~ ^refs/tags/ ]] ; then
|
||||
|
@ -66,6 +70,7 @@ jobs:
|
|||
exit 1
|
||||
fi
|
||||
version=${version#v}
|
||||
git describe --exclude '*-test' --tags --always
|
||||
echo "sha=${{ github.sha }}" >> "$GITHUB_OUTPUT"
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
echo "override=$override" >> "$GITHUB_OUTPUT"
|
||||
|
@ -81,6 +86,18 @@ jobs:
|
|||
ENDVAR
|
||||
EOF
|
||||
|
||||
- name: cache node_modules
|
||||
id: node
|
||||
uses: https://code.forgejo.org/actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
key: node-${{ steps.release-info.outputs.version }}
|
||||
|
||||
- name: skip if node cache hit
|
||||
if: steps.node.outputs.cache-hit != 'true'
|
||||
run: echo no hit
|
||||
|
||||
- name: Build sources
|
||||
run: |
|
||||
set -x
|
||||
|
@ -177,7 +194,7 @@ jobs:
|
|||
|
||||
- name: end-to-end tests
|
||||
if: ${{ secrets.TOKEN != '' && vars.ROLE == 'forgejo-integration' }}
|
||||
uses: https://code.forgejo.org/actions/cascading-pr@v1
|
||||
uses: https://code.forgejo.org/actions/cascading-pr@v2
|
||||
with:
|
||||
origin-url: ${{ env.GITHUB_SERVER_URL }}
|
||||
origin-repo: ${{ github.repository }}
|
||||
|
@ -186,8 +203,28 @@ jobs:
|
|||
destination-url: https://code.forgejo.org
|
||||
destination-fork-repo: ${{ vars.CASCADE_DESTINATION_DOER }}/end-to-end
|
||||
destination-repo: forgejo/end-to-end
|
||||
destination-branch: forgejo-pr
|
||||
destination-branch: main
|
||||
destination-token: ${{ secrets.CASCADE_DESTINATION_TOKEN }}
|
||||
update: .forgejo/cascading-release-end-to-end
|
||||
env:
|
||||
FORGEJO_BINARY: "${{ env.GITHUB_SERVER_URL }}/${{ github.repository }}/releases/download/v${{ steps.release-info.outputs.version }}/forgejo-${{ steps.release-info.outputs.version }}-linux-amd64"
|
||||
|
||||
- name: copy to experimental
|
||||
if: vars.ROLE == 'forgejo-integration' && secrets.TOKEN != ''
|
||||
run: |
|
||||
if test "${{ vars.VERBOSE }}" = true ; then
|
||||
set -x
|
||||
fi
|
||||
tag=v${{ steps.release-info.outputs.version }}
|
||||
url=https://any:${{ secrets.TOKEN }}@codeberg.org
|
||||
if test "${{ steps.release-info.outputs.override }}" = "true" ; then
|
||||
curl -sS -X DELETE $url/api/v1/repos/forgejo-experimental/forgejo/releases/tags/$tag > /dev/null
|
||||
curl -sS -X DELETE $url/api/v1/repos/forgejo-experimental/forgejo/tags/$tag > /dev/null
|
||||
fi
|
||||
# actions/checkout@v3 sets http.https://codeberg.org/.extraheader with the automatic token.
|
||||
# Get rid of it so it does not prevent using the token that has write permissions
|
||||
git config --local --unset http.https://codeberg.org/.extraheader
|
||||
if test -f .git/shallow ; then
|
||||
echo "unexptected .git/shallow file is present"
|
||||
echo "it suggests a checkout --depth X was used which may prevent pushing the commit"
|
||||
echo "it happens when actions/checkout is called without depth: 0"
|
||||
fi
|
||||
git push $url/forgejo-experimental/forgejo ${{ steps.release-info.outputs.sha }}:refs/tags/$tag
|
||||
|
|
|
@ -1,5 +1,23 @@
|
|||
# Copyright 2024 The Forgejo Authors
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# To modify this workflow:
|
||||
#
|
||||
# - push it to the wip-ci-end-to-end branch on the forgejo repository
|
||||
# otherwise it will not have access to the secrets required to push
|
||||
# the cascading PR
|
||||
#
|
||||
# - once it works, open a pull request for the sake of keeping track
|
||||
# of the change even if the PR won't run it because it will use
|
||||
# whatever is in the default branch instead
|
||||
#
|
||||
# - after it is merged, double check it works by setting the
|
||||
# run-end-to-end-test on a pull request (any pull request will doe
|
||||
#
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'wip-ci-end-to-end'
|
||||
pull_request_target:
|
||||
types:
|
||||
- labeled
|
||||
|
@ -20,58 +38,37 @@ jobs:
|
|||
cat <<'EOF'
|
||||
${{ toJSON(github.event.pull_request.labels.*.name) }}
|
||||
EOF
|
||||
|
||||
build:
|
||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') && github.event.action == 'label_updated' && contains(github.event.pull_request.labels.*.name, 'run-end-to-end-tests') }}
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'docker.io/node:20-bookworm'
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: '0'
|
||||
show-progress: 'false'
|
||||
- name: adduser forgejo
|
||||
run: |
|
||||
git config --add safe.directory '*'
|
||||
git config user.email "you@example.com"
|
||||
git config user.name "Your Name"
|
||||
adduser --quiet --comment forgejo --disabled-password forgejo
|
||||
chown -R forgejo:forgejo .
|
||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.21"
|
||||
- name: make deps-backend
|
||||
run: |
|
||||
su forgejo -c 'make deps-backend'
|
||||
- name: make forgejo
|
||||
run: |
|
||||
su forgejo -c 'make generate-backend static-executable && ln gitea forgejo'
|
||||
env:
|
||||
TAGS: bindata sqlite sqlite_unlock_notify
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: forgejo
|
||||
path: forgejo
|
||||
cat <<'EOF'
|
||||
${{ toJSON(github.event) }}
|
||||
EOF
|
||||
|
||||
cascade:
|
||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') && github.event.action == 'label_updated' && contains(github.event.pull_request.labels.*.name, 'run-end-to-end-tests') }}
|
||||
needs: [build]
|
||||
if: >
|
||||
!startsWith(vars.ROLE, 'forgejo-') && (
|
||||
github.event_name == 'push' ||
|
||||
(
|
||||
github.event.action == 'label_updated' && contains(github.event.pull_request.labels.*.name, 'run-end-to-end-tests')
|
||||
)
|
||||
)
|
||||
runs-on: docker
|
||||
container:
|
||||
image: node:20-bookworm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cascading-pr@v1
|
||||
with:
|
||||
fetch-depth: '0'
|
||||
show-progress: 'false'
|
||||
- uses: actions/cascading-pr@v2
|
||||
with:
|
||||
origin-url: ${{ env.GITHUB_SERVER_URL }}
|
||||
origin-repo: ${{ github.repository }}
|
||||
origin-token: ${{ secrets.END_TO_END_CASCADING_PR_ORIGIN }}
|
||||
origin-pr: ${{ github.event.pull_request.number }}
|
||||
origin-ref: ${{ github.event_name == 'push' && github.event.ref || '' }}
|
||||
destination-url: https://code.forgejo.org
|
||||
destination-fork-repo: cascading-pr/end-to-end
|
||||
destination-repo: forgejo/end-to-end
|
||||
destination-branch: forgejo-pr
|
||||
destination-branch: main
|
||||
destination-token: ${{ secrets.END_TO_END_CASCADING_PR_DESTINATION }}
|
||||
close-merge: true
|
||||
update: .forgejo/cascading-pr-end-to-end
|
||||
|
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||
with:
|
||||
go-version: "~1.21"
|
||||
go-version: "1.22"
|
||||
check-latest: true
|
||||
- run: |
|
||||
apt-get -qq update
|
||||
|
|
|
@ -42,16 +42,19 @@ jobs:
|
|||
- uses: actions/checkout@v3
|
||||
|
||||
- name: copy & sign
|
||||
uses: https://code.forgejo.org/forgejo/forgejo-build-publish/publish@v1
|
||||
uses: https://code.forgejo.org/forgejo/forgejo-build-publish/publish@v4
|
||||
with:
|
||||
forgejo: ${{ vars.FORGEJO }}
|
||||
from-forgejo: ${{ vars.FORGEJO }}
|
||||
to-forgejo: ${{ vars.FORGEJO }}
|
||||
from-owner: ${{ vars.FROM_OWNER }}
|
||||
to-owner: ${{ vars.TO_OWNER }}
|
||||
repo: ${{ vars.REPO }}
|
||||
ref-name: ${{ github.ref_name }}
|
||||
release-notes: "See https://codeberg.org/forgejo/forgejo/src/branch/forgejo/RELEASE-NOTES.md#{ANCHOR}"
|
||||
doer: ${{ vars.DOER }}
|
||||
token: ${{ secrets.TOKEN }}
|
||||
ref-name: ${{ github.ref_name }}
|
||||
sha: ${{ github.sha }}
|
||||
from-token: ${{ secrets.TOKEN }}
|
||||
to-doer: ${{ vars.DOER }}
|
||||
to-token: ${{ secrets.TOKEN }}
|
||||
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
verbose: ${{ vars.VERBOSE }}
|
||||
|
@ -61,7 +64,7 @@ jobs:
|
|||
if: vars.ROLE == 'forgejo-experimental' && secrets.OVH_APP_KEY != ''
|
||||
uses: https://code.forgejo.org/actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.21"
|
||||
go-version: "1.22"
|
||||
check-latest: true
|
||||
- name: update the _release.experimental DNS record
|
||||
if: vars.ROLE == 'forgejo-experimental' && secrets.OVH_APP_KEY != ''
|
||||
|
|
61
.forgejo/workflows/renovate.yml
Normal file
61
.forgejo/workflows/renovate.yml
Normal file
|
@ -0,0 +1,61 @@
|
|||
#
|
||||
# The 2am run will rebase what needs rebasing & trigger the CI
|
||||
# The 4am run will merge one of them
|
||||
#
|
||||
# These times are chosen to minimize the likelyhood that another PR
|
||||
# is merged at the same time. This would not be necessary if automerge
|
||||
# worked but as of 30 March 2024 it does not.
|
||||
#
|
||||
name: renovate
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'renovate/**' # self-test updates
|
||||
schedule:
|
||||
- cron: '0 2,4 * * *'
|
||||
|
||||
env:
|
||||
RENOVATE_DRY_RUN: ${{ (github.event_name != 'schedule' && github.ref_name != github.event.repository.default_branch) && 'full' || '' }}
|
||||
RENOVATE_REPOSITORIES: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
renovate:
|
||||
if: ${{ secrets.RENOVATE_TOKEN != '' }}
|
||||
|
||||
runs-on: docker
|
||||
container:
|
||||
image: ghcr.io/visualon/renovate:37.278.0
|
||||
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
with:
|
||||
path: |
|
||||
.tmp/cache/renovate/repository
|
||||
key: repo-cache-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
repo-cache-
|
||||
|
||||
- run: renovate
|
||||
env:
|
||||
GITHUB_COM_TOKEN: ${{ secrets.RENOVATE_GITHUB_COM_TOKEN }}
|
||||
LOG_LEVEL: debug
|
||||
RENOVATE_BASE_DIR: ${{ github.workspace }}/.tmp
|
||||
RENOVATE_ENDPOINT: ${{ github.server_url }}
|
||||
RENOVATE_PLATFORM: gitea
|
||||
RENOVATE_REPOSITORY_CACHE: 'enabled'
|
||||
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
||||
RENOVATE_GIT_AUTHOR: 'Renovate Bot <forgejo-renovate-action@forgejo.org>'
|
||||
|
||||
GIT_AUTHOR_NAME: 'Renovate Bot'
|
||||
GIT_AUTHOR_EMAIL: 'forgejo-renovate-action@forgejo.org'
|
||||
GIT_COMMITTER_NAME: 'Renovate Bot'
|
||||
GIT_COMMITTER_EMAIL: 'forgejo-renovate-action@forgejo.org'
|
||||
|
||||
- name: Save renovate repo cache
|
||||
if: always() && env.RENOVATE_DRY_RUN == 'true'
|
||||
uses: https://code.forgejo.org/actions/cache/save@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
with:
|
||||
path: |
|
||||
.tmp/cache/renovate/repository
|
||||
key: repo-cache-${{ github.run_id }}
|
|
@ -14,32 +14,50 @@ jobs:
|
|||
container:
|
||||
image: 'docker.io/node:20-bookworm'
|
||||
steps:
|
||||
- name: event info
|
||||
run: |
|
||||
cat <<'EOF'
|
||||
${{ toJSON(github) }}
|
||||
EOF
|
||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.21"
|
||||
go-version: "1.22"
|
||||
check-latest: true
|
||||
- run: make deps-backend deps-tools
|
||||
- run: make --always-make -j$(nproc) lint-backend checks-backend # ensure the "go-licenses" make target runs
|
||||
env:
|
||||
TAGS: bindata sqlite sqlite_unlock_notify
|
||||
frontend-checks:
|
||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'docker.io/node:20-bookworm'
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||
- run: make deps-frontend
|
||||
- run: make lint-frontend
|
||||
- run: make checks-frontend
|
||||
- run: make test-frontend
|
||||
- run: make frontend
|
||||
test-unit:
|
||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
||||
runs-on: docker
|
||||
needs: [backend-checks]
|
||||
needs: [backend-checks, frontend-checks]
|
||||
container:
|
||||
image: 'docker.io/node:20-bookworm'
|
||||
services:
|
||||
minio:
|
||||
image: 'docker.io/bitnami/minio:2023.8.31'
|
||||
image: bitnami/minio:2024.2.26
|
||||
options: >-
|
||||
--hostname gitea.minio
|
||||
env:
|
||||
MINIO_DOMAIN: minio
|
||||
MINIO_ROOT_USER: 123456
|
||||
MINIO_ROOT_PASSWORD: 12345678
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.21"
|
||||
go-version: "1.22"
|
||||
- run: |
|
||||
git config --add safe.directory '*'
|
||||
adduser --quiet --comment forgejo --disabled-password forgejo
|
||||
|
@ -67,7 +85,7 @@ jobs:
|
|||
test-mysql:
|
||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
||||
runs-on: docker
|
||||
needs: [backend-checks]
|
||||
needs: [backend-checks, frontend-checks]
|
||||
container:
|
||||
image: 'docker.io/node:20-bookworm'
|
||||
services:
|
||||
|
@ -84,7 +102,7 @@ jobs:
|
|||
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.21"
|
||||
go-version: "1.22"
|
||||
- name: install dependencies & git >= 2.42
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
@ -113,15 +131,15 @@ jobs:
|
|||
test-pgsql:
|
||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
||||
runs-on: docker
|
||||
needs: [backend-checks]
|
||||
needs: [backend-checks, frontend-checks]
|
||||
container:
|
||||
image: 'docker.io/node:20-bookworm'
|
||||
services:
|
||||
minio:
|
||||
image: bitnami/minio:2021.3.17
|
||||
image: bitnami/minio:2024.2.26
|
||||
env:
|
||||
MINIO_ACCESS_KEY: 123456
|
||||
MINIO_SECRET_KEY: 12345678
|
||||
MINIO_ROOT_USER: 123456
|
||||
MINIO_ROOT_PASSWORD: 12345678
|
||||
pgsql:
|
||||
image: 'docker.io/postgres:15'
|
||||
env:
|
||||
|
@ -131,7 +149,7 @@ jobs:
|
|||
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.21"
|
||||
go-version: "1.22"
|
||||
- name: install dependencies & git >= 2.42
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
@ -161,14 +179,14 @@ jobs:
|
|||
test-sqlite:
|
||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
||||
runs-on: docker
|
||||
needs: [backend-checks]
|
||||
needs: [backend-checks, frontend-checks]
|
||||
container:
|
||||
image: 'docker.io/node:20-bookworm'
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.21"
|
||||
go-version: "1.22"
|
||||
- name: install dependencies & git >= 2.42
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
name: 🦋 Bug Report (web interface / frontend)
|
||||
description: Something doesn't look quite as it should? Report it here!
|
||||
title: "[BUG] "
|
||||
labels: ["bug", "forgejo/ui"]
|
||||
labels: ["bug/new-report", "forgejo/ui"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
name: 🐛 Bug Report (server / backend)
|
||||
description: Found something you weren't expecting? Report it here!
|
||||
title: "[BUG] "
|
||||
labels: bug
|
||||
labels: bug/new-report
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
---
|
||||
|
||||
name: "Pull Request Template"
|
||||
about: "Template for all Pull Requests"
|
||||
labels:
|
||||
|
||||
- test/needed
|
||||
|
||||
---
|
||||
<!--
|
||||
Before submitting a PR, please read the contributing guidelines:
|
||||
https://codeberg.org/forgejo/forgejo/src/branch/forgejo/CONTRIBUTING.md
|
||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -18,7 +18,7 @@ _test
|
|||
|
||||
# MS VSCode
|
||||
.vscode
|
||||
__debug_bin
|
||||
__debug_bin*
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
|
@ -64,7 +64,7 @@ cpu.out
|
|||
/data
|
||||
/indexers
|
||||
/log
|
||||
/public/img/avatar
|
||||
/public/assets/img/avatar
|
||||
/tests/integration/gitea-integration-*
|
||||
/tests/integration/indexers-*
|
||||
/tests/e2e/gitea-e2e-*
|
||||
|
|
|
@ -42,7 +42,7 @@ vscode:
|
|||
- DavidAnson.vscode-markdownlint
|
||||
- Vue.volar
|
||||
- ms-azuretools.vscode-docker
|
||||
- zixuanchen.vitest-explorer
|
||||
- vitest.explorer
|
||||
- qwtel.sqlite-viewer
|
||||
- GitHub.vscode-pull-request-github
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ rules:
|
|||
"@stylistic/block-opening-brace-newline-after": null
|
||||
"@stylistic/block-opening-brace-newline-before": null
|
||||
"@stylistic/block-opening-brace-space-after": null
|
||||
"@stylistic/block-opening-brace-space-before": null
|
||||
"@stylistic/block-opening-brace-space-before": always
|
||||
"@stylistic/color-hex-case": lower
|
||||
"@stylistic/declaration-bang-space-after": never
|
||||
"@stylistic/declaration-bang-space-before": null
|
||||
|
@ -64,6 +64,7 @@ rules:
|
|||
"@stylistic/media-query-list-comma-newline-before": null
|
||||
"@stylistic/media-query-list-comma-space-after": null
|
||||
"@stylistic/media-query-list-comma-space-before": null
|
||||
"@stylistic/named-grid-areas-alignment": null
|
||||
"@stylistic/no-empty-first-line": null
|
||||
"@stylistic/no-eol-whitespace": true
|
||||
"@stylistic/no-extra-semicolons": true
|
||||
|
@ -98,7 +99,7 @@ rules:
|
|||
at-rule-allowed-list: null
|
||||
at-rule-disallowed-list: null
|
||||
at-rule-empty-line-before: null
|
||||
at-rule-no-unknown: true
|
||||
at-rule-no-unknown: [true, {ignoreAtRules: [tailwind]}]
|
||||
at-rule-no-vendor-prefix: true
|
||||
at-rule-property-required-list: null
|
||||
block-no-empty: true
|
||||
|
@ -139,7 +140,7 @@ rules:
|
|||
function-disallowed-list: null
|
||||
function-linear-gradient-no-nonstandard-direction: true
|
||||
function-name-case: lower
|
||||
function-no-unknown: null
|
||||
function-no-unknown: true
|
||||
function-url-no-scheme-relative: null
|
||||
function-url-quotes: always
|
||||
function-url-scheme-allowed-list: null
|
||||
|
@ -167,7 +168,7 @@ rules:
|
|||
no-duplicate-selectors: true
|
||||
no-empty-source: true
|
||||
no-invalid-double-slash-comments: true
|
||||
no-invalid-position-at-import-rule: null
|
||||
no-invalid-position-at-import-rule: [true, ignoreAtRules: [tailwind]]
|
||||
no-irregular-whitespace: true
|
||||
no-unknown-animations: null
|
||||
no-unknown-custom-properties: null
|
||||
|
@ -180,6 +181,7 @@ rules:
|
|||
rule-empty-line-before: null
|
||||
rule-selector-property-disallowed-list: null
|
||||
scale-unlimited/declaration-strict-value: [[/color$/, font-weight], {ignoreValues: /^(inherit|transparent|unset|initial|currentcolor|none)$/, ignoreFunctions: false, disableFix: true, expandShorthand: true}]
|
||||
selector-anb-no-unmatchable: true
|
||||
selector-attribute-name-disallowed-list: null
|
||||
selector-attribute-operator-allowed-list: null
|
||||
selector-attribute-operator-disallowed-list: null
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
FROM --platform=$BUILDPLATFORM docker.io/tonistiigi/xx AS xx
|
||||
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.21-alpine3.19 as build-env
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.22-alpine3.19 as build-env
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY ${GOPROXY:-direct}
|
||||
|
@ -33,7 +33,7 @@ RUN apk --no-cache add build-base git nodejs npm
|
|||
COPY . ${GOPATH}/src/code.gitea.io/gitea
|
||||
WORKDIR ${GOPATH}/src/code.gitea.io/gitea
|
||||
|
||||
RUN make clean-all
|
||||
RUN make clean
|
||||
RUN make frontend
|
||||
RUN go build contrib/environment-to-ini/environment-to-ini.go && xx-verify environment-to-ini
|
||||
RUN make RELEASE_VERSION=$RELEASE_VERSION go-check generate-backend static-executable && xx-verify gitea
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
FROM --platform=$BUILDPLATFORM docker.io/tonistiigi/xx AS xx
|
||||
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.21-alpine3.19 as build-env
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.22-alpine3.19 as build-env
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY ${GOPROXY:-direct}
|
||||
|
@ -33,7 +33,7 @@ RUN apk --no-cache add build-base git nodejs npm
|
|||
COPY . ${GOPATH}/src/code.gitea.io/gitea
|
||||
WORKDIR ${GOPATH}/src/code.gitea.io/gitea
|
||||
|
||||
RUN make clean-all
|
||||
RUN make clean
|
||||
RUN make frontend
|
||||
RUN go build contrib/environment-to-ini/environment-to-ini.go && xx-verify environment-to-ini
|
||||
RUN make RELEASE_VERSION=$RELEASE_VERSION go-check generate-backend static-executable && xx-verify gitea
|
||||
|
|
158
Makefile
158
Makefile
|
@ -32,7 +32,7 @@ GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0
|
|||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.56.1
|
||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11
|
||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.4.1
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.5
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.6-0.20240201115257-bcc7c78b7786
|
||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0
|
||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.0.3
|
||||
|
@ -44,9 +44,6 @@ DOCKER_TAG ?= latest
|
|||
DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)
|
||||
|
||||
ifeq ($(HAS_GO), yes)
|
||||
GOPATH ?= $(shell $(GO) env GOPATH)
|
||||
export PATH := $(GOPATH)/bin:$(PATH)
|
||||
|
||||
CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766
|
||||
CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS)
|
||||
endif
|
||||
|
@ -85,29 +82,53 @@ endif
|
|||
STORED_VERSION_FILE := VERSION
|
||||
HUGO_VERSION ?= 0.111.3
|
||||
|
||||
GITEA_COMPATIBILITY ?= gitea-1.22.0
|
||||
|
||||
STORED_VERSION=$(shell cat $(STORED_VERSION_FILE) 2>/dev/null)
|
||||
ifneq ($(STORED_VERSION),)
|
||||
FORGEJO_VERSION ?= $(STORED_VERSION)
|
||||
else
|
||||
FORGEJO_VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
|
||||
ifneq ($(GITEA_VERSION),)
|
||||
FORGEJO_VERSION ?= $(GITEA_VERSION)
|
||||
FORGEJO_VERSION_API ?= $(GITEA_VERSION)+${GITEA_COMPATIBILITY}
|
||||
else
|
||||
# drop the "g" prefix prepended by git describe to the commit hash
|
||||
FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//' | sed 's/\-g/-/')+${GITEA_COMPATIBILITY}
|
||||
endif
|
||||
endif
|
||||
FORGEJO_VERSION_MAJOR=$(shell echo $(FORGEJO_VERSION) | sed -e 's/\..*//')
|
||||
FORGEJO_VERSION_MINOR=$(shell echo $(FORGEJO_VERSION) | sed -E -e 's/^([0-9]+\.[0-9]+).*/\1/')
|
||||
|
||||
show-version-full:
|
||||
@echo ${FORGEJO_VERSION}
|
||||
|
||||
show-version-major:
|
||||
@echo ${FORGEJO_VERSION_MAJOR}
|
||||
|
||||
show-version-minor:
|
||||
@echo ${FORGEJO_VERSION_MINOR}
|
||||
|
||||
RELEASE_VERSION ?= ${FORGEJO_VERSION}
|
||||
VERSION ?= ${RELEASE_VERSION}
|
||||
|
||||
GITEA_VERSION ?= 1.22.0
|
||||
FORGEJO_VERSION_API ?= ${FORGEJO_VERSION}
|
||||
|
||||
LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" -X "main.ForgejoVersion=$(FORGEJO_VERSION)"
|
||||
show-version-api:
|
||||
@echo ${FORGEJO_VERSION_API}
|
||||
|
||||
LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(FORGEJO_VERSION)" -X "main.Tags=$(TAGS)" -X "main.ForgejoVersion=$(FORGEJO_VERSION_API)"
|
||||
|
||||
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
|
||||
|
||||
GO_PACKAGES ?= $(filter-out code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
||||
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) $(shell $(GO) list code.gitea.io/gitea/models/forgejo_migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
||||
ifeq ($(HAS_GO), yes)
|
||||
GO_PACKAGES ?= $(filter-out code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
||||
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) $(shell $(GO) list code.gitea.io/gitea/models/forgejo_migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
||||
endif
|
||||
|
||||
FOMANTIC_WORK_DIR := web_src/fomantic
|
||||
|
||||
WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
|
||||
WEBPACK_CONFIGS := webpack.config.js
|
||||
WEBPACK_CONFIGS := webpack.config.js tailwind.config.js
|
||||
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
|
||||
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts public/assets/img/webpack
|
||||
|
||||
|
@ -134,6 +155,8 @@ TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMAN
|
|||
GO_DIRS := build cmd models modules routers services tests
|
||||
WEB_DIRS := web_src/js web_src/css
|
||||
|
||||
ESLINT_FILES := web_src/js tools *.config.js tests/e2e
|
||||
STYLELINT_FILES := web_src/css web_src/js/components/*.vue
|
||||
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github
|
||||
|
||||
GO_SOURCES := $(wildcard *.go)
|
||||
|
@ -141,7 +164,9 @@ GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/optio
|
|||
GO_SOURCES += $(GENERATED_GO_DEST)
|
||||
GO_SOURCES_NO_BINDATA := $(GO_SOURCES)
|
||||
|
||||
MIGRATION_PACKAGES := $(shell $(GO) list code.gitea.io/gitea/models/migrations/... code.gitea.io/gitea/models/forgejo_migrations/...)
|
||||
ifeq ($(HAS_GO), yes)
|
||||
MIGRATION_PACKAGES := $(shell $(GO) list code.gitea.io/gitea/models/migrations/... code.gitea.io/gitea/models/forgejo_migrations/...)
|
||||
endif
|
||||
|
||||
ifeq ($(filter $(TAGS_SPLIT),bindata),bindata)
|
||||
GO_SOURCES += $(BINDATA_DEST)
|
||||
|
@ -156,8 +181,8 @@ endif
|
|||
FORGEJO_API_SPEC := public/assets/forgejo/api.v1.yml
|
||||
|
||||
SWAGGER_SPEC := templates/swagger/v1_json.tmpl
|
||||
SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|g
|
||||
SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|"basePath": "/api/v1"|g
|
||||
SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape}}/api/v1"|g
|
||||
SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape}}/api/v1"|"basePath": "/api/v1"|g
|
||||
SWAGGER_EXCLUDE := code.gitea.io/sdk
|
||||
SWAGGER_NEWLINE_COMMAND := -e '$$a\'
|
||||
SWAGGER_SPEC_BRANDING := s|Gitea API|Forgejo API|g
|
||||
|
@ -220,6 +245,8 @@ help:
|
|||
@echo " - checks-frontend check frontend files"
|
||||
@echo " - checks-backend check backend files"
|
||||
@echo " - test test everything"
|
||||
@echo " - show-version-full show the same version as the API endpoint"
|
||||
@echo " - show-version-major show major release number only"
|
||||
@echo " - test-frontend test frontend files"
|
||||
@echo " - test-backend test backend files"
|
||||
@echo " - test-e2e[\#TestSpecificName] test end to end using playwright"
|
||||
|
@ -300,12 +327,8 @@ fmt:
|
|||
|
||||
.PHONY: fmt-check
|
||||
fmt-check: fmt
|
||||
@diff=$$(git diff --color=always $(GO_SOURCES) templates $(WEB_DIRS)); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo "Please run 'make fmt' and commit the result:"; \
|
||||
echo "$${diff}"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@git diff --exit-code --color=always $(GO_SOURCES) templates $(WEB_DIRS) \
|
||||
|| (code=$$?; echo "Please run 'make fmt' and commit the result"; exit $${code})
|
||||
|
||||
.PHONY: $(TAGS_EVIDENCE)
|
||||
$(TAGS_EVIDENCE):
|
||||
|
@ -326,12 +349,8 @@ generate-forgejo-api: $(FORGEJO_API_SPEC)
|
|||
|
||||
.PHONY: forgejo-api-check
|
||||
forgejo-api-check: generate-forgejo-api
|
||||
@diff=$$(git diff $(FORGEJO_API_SERVER) ; \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo "Please run 'make generate-forgejo-api' and commit the result:"; \
|
||||
echo "$${diff}"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@git diff --exit-code --color=always $(FORGEJO_API_SERVER) \
|
||||
|| (code=$$?; echo "Please run 'make generate-forgejo-api' and commit the result"; exit $${code})
|
||||
|
||||
.PHONY: forgejo-api-validate
|
||||
forgejo-api-validate:
|
||||
|
@ -348,12 +367,8 @@ $(SWAGGER_SPEC): $(GO_SOURCES_NO_BINDATA)
|
|||
|
||||
.PHONY: swagger-check
|
||||
swagger-check: generate-swagger
|
||||
@diff=$$(git diff --color=always '$(SWAGGER_SPEC)'); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo "Please run 'make generate-swagger' and commit the result:"; \
|
||||
echo "$${diff}"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@git diff --exit-code --color=always '$(SWAGGER_SPEC)' \
|
||||
|| (code=$$?; echo "Please run 'make generate-swagger' and commit the result"; exit $${code})
|
||||
|
||||
.PHONY: swagger-validate
|
||||
swagger-validate:
|
||||
|
@ -390,19 +405,19 @@ lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig
|
|||
|
||||
.PHONY: lint-js
|
||||
lint-js: node_modules
|
||||
npx eslint --color --max-warnings=0 --ext js,vue web_src/js build *.config.js tests/e2e
|
||||
npx eslint --color --max-warnings=0 --ext js,vue $(ESLINT_FILES)
|
||||
|
||||
.PHONY: lint-js-fix
|
||||
lint-js-fix: node_modules
|
||||
npx eslint --color --max-warnings=0 --ext js,vue web_src/js build *.config.js tests/e2e --fix
|
||||
npx eslint --color --max-warnings=0 --ext js,vue $(ESLINT_FILES) --fix
|
||||
|
||||
.PHONY: lint-css
|
||||
lint-css: node_modules
|
||||
npx stylelint --color --max-warnings=0 web_src/css web_src/js/components/*.vue
|
||||
npx stylelint --color --max-warnings=0 $(STYLELINT_FILES)
|
||||
|
||||
.PHONY: lint-css-fix
|
||||
lint-css-fix: node_modules
|
||||
npx stylelint --color --max-warnings=0 web_src/css web_src/js/components/*.vue --fix
|
||||
npx stylelint --color --max-warnings=0 $(STYLELINT_FILES) --fix
|
||||
|
||||
.PHONY: lint-swagger
|
||||
lint-swagger: node_modules
|
||||
|
@ -424,11 +439,8 @@ lint-spell-fix:
|
|||
lint-go:
|
||||
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS)
|
||||
$(GO) run $(DEADCODE_PACKAGE) -generated=false -test code.gitea.io/gitea > .cur-deadcode-out
|
||||
@$(DIFF) .deadcode-out .cur-deadcode-out; \
|
||||
if [ $$? -eq 1 ]; then \
|
||||
echo "Please run 'make lint-go-fix' and commit the result"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@$(DIFF) .deadcode-out .cur-deadcode-out \
|
||||
|| (code=$$?; echo "Please run 'make lint-go-fix' and commit the result"; exit $${code})
|
||||
|
||||
.PHONY: lint-go-fix
|
||||
lint-go-fix:
|
||||
|
@ -456,7 +468,8 @@ lint-actions:
|
|||
$(GO) run $(ACTIONLINT_PACKAGE)
|
||||
|
||||
.PHONY: lint-templates
|
||||
lint-templates: .venv
|
||||
lint-templates: .venv node_modules
|
||||
@node tools/lint-templates-svg.js
|
||||
@poetry run djlint $(shell find templates -type f -iname '*.tmpl')
|
||||
|
||||
.PHONY: lint-yaml
|
||||
|
@ -465,7 +478,7 @@ lint-yaml: .venv
|
|||
|
||||
.PHONY: watch
|
||||
watch:
|
||||
@bash build/watch.sh
|
||||
@bash tools/watch.sh
|
||||
|
||||
.PHONY: watch-frontend
|
||||
watch-frontend: node-check node_modules
|
||||
|
@ -528,12 +541,8 @@ vendor: go.mod go.sum
|
|||
|
||||
.PHONY: tidy-check
|
||||
tidy-check: tidy
|
||||
@diff=$$(git diff --color=always go.mod go.sum $(GO_LICENSE_FILE)); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo "Please run 'make tidy' and commit the result:"; \
|
||||
echo "$${diff}"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@git diff --exit-code --color=always go.mod go.sum $(GO_LICENSE_FILE) \
|
||||
|| (code=$$?; echo "Please run 'make tidy' and commit the result"; exit $${code})
|
||||
|
||||
.PHONY: go-licenses
|
||||
go-licenses: $(GO_LICENSE_FILE)
|
||||
|
@ -625,8 +634,7 @@ test-mssql\#%: integrations.mssql.test generate-ini-mssql
|
|||
test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test
|
||||
|
||||
.PHONY: playwright
|
||||
playwright: $(PLAYWRIGHT_DIR)
|
||||
npm install --no-save @playwright/test
|
||||
playwright: deps-frontend
|
||||
npx playwright install $(PLAYWRIGHT_FLAGS)
|
||||
|
||||
.PHONY: test-e2e%
|
||||
|
@ -639,7 +647,7 @@ test-e2e: test-e2e-sqlite
|
|||
|
||||
.PHONY: test-e2e-sqlite
|
||||
test-e2e-sqlite: playwright e2e.sqlite.test generate-ini-sqlite
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./e2e.sqlite.test
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./e2e.sqlite.test -test.run TestE2e
|
||||
|
||||
.PHONY: test-e2e-sqlite\#%
|
||||
test-e2e-sqlite\#%: playwright e2e.sqlite.test generate-ini-sqlite
|
||||
|
@ -647,7 +655,7 @@ test-e2e-sqlite\#%: playwright e2e.sqlite.test generate-ini-sqlite
|
|||
|
||||
.PHONY: test-e2e-mysql
|
||||
test-e2e-mysql: playwright e2e.mysql.test generate-ini-mysql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./e2e.mysql.test
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./e2e.mysql.test -test.run TestE2e
|
||||
|
||||
.PHONY: test-e2e-mysql\#%
|
||||
test-e2e-mysql\#%: playwright e2e.mysql.test generate-ini-mysql
|
||||
|
@ -655,7 +663,7 @@ test-e2e-mysql\#%: playwright e2e.mysql.test generate-ini-mysql
|
|||
|
||||
.PHONY: test-e2e-pgsql
|
||||
test-e2e-pgsql: playwright e2e.pgsql.test generate-ini-pgsql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./e2e.pgsql.test
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./e2e.pgsql.test -test.run TestE2e
|
||||
|
||||
.PHONY: test-e2e-pgsql\#%
|
||||
test-e2e-pgsql\#%: playwright e2e.pgsql.test generate-ini-pgsql
|
||||
|
@ -663,12 +671,17 @@ test-e2e-pgsql\#%: playwright e2e.pgsql.test generate-ini-pgsql
|
|||
|
||||
.PHONY: test-e2e-mssql
|
||||
test-e2e-mssql: playwright e2e.mssql.test generate-ini-mssql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./e2e.mssql.test
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./e2e.mssql.test -test.run TestE2e
|
||||
|
||||
.PHONY: test-e2e-mssql\#%
|
||||
test-e2e-mssql\#%: playwright e2e.mssql.test generate-ini-mssql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./e2e.mssql.test -test.run TestE2e/$*
|
||||
|
||||
.PHONY: test-e2e-debugserver
|
||||
test-e2e-debugserver: e2e.sqlite.test generate-ini-sqlite
|
||||
sed -i s/3003/3000/g tests/sqlite.ini
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./e2e.sqlite.test -test.run TestDebugserver -test.timeout 24h
|
||||
|
||||
.PHONY: bench-sqlite
|
||||
bench-sqlite: integrations.sqlite.test generate-ini-sqlite
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
||||
|
@ -812,6 +825,10 @@ generate-go: $(TAGS_PREREQ)
|
|||
@echo "Running go generate..."
|
||||
@CC= GOOS= GOARCH= $(GO) generate -tags '$(TAGS)' $(GO_PACKAGES)
|
||||
|
||||
.PHONY: merge-locales
|
||||
merge-locales:
|
||||
@echo "NOT NEEDED: THIS IS A NOOP AS OF Forgejo 7.0 BUT KEPT FOR BACKWARD COMPATIBILITY"
|
||||
|
||||
.PHONY: security-check
|
||||
security-check:
|
||||
go run $(GOVULNCHECK_PACKAGE) ./...
|
||||
|
@ -882,10 +899,6 @@ release-sources: | $(DIST_DIRS)
|
|||
release-docs: | $(DIST_DIRS) docs
|
||||
tar -czf $(DIST)/release/gitea-docs-$(VERSION).tar.gz -C ./docs .
|
||||
|
||||
.PHONY: docs
|
||||
docs:
|
||||
cd docs; bash scripts/trans-copy.sh;
|
||||
|
||||
.PHONY: deps
|
||||
deps: deps-frontend deps-backend deps-tools deps-py
|
||||
|
||||
|
@ -944,6 +957,7 @@ fomantic:
|
|||
cd $(FOMANTIC_WORK_DIR) && npm install --no-save
|
||||
cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config
|
||||
cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/
|
||||
$(SED_INPLACE) -e 's/ overrideBrowserslist\r/ overrideBrowserslist: ["defaults"]\r/g' $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/tasks/config/tasks.js
|
||||
cd $(FOMANTIC_WORK_DIR) && npx gulp -f node_modules/fomantic-ui/gulpfile.js build
|
||||
# fomantic uses "touchstart" as click event for some browsers, it's not ideal, so we force fomantic to always use "click" as click event
|
||||
$(SED_INPLACE) -e 's/clickEvent[ \t]*=/clickEvent = "click", unstableClickEvent =/g' $(FOMANTIC_WORK_DIR)/build/semantic.js
|
||||
|
@ -962,28 +976,19 @@ $(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json
|
|||
.PHONY: svg
|
||||
svg: node-check | node_modules
|
||||
rm -rf $(SVG_DEST_DIR)
|
||||
node build/generate-svg.js
|
||||
node tools/generate-svg.js
|
||||
|
||||
.PHONY: svg-check
|
||||
svg-check: svg
|
||||
@git add $(SVG_DEST_DIR)
|
||||
@diff=$$(git diff --color=always --cached $(SVG_DEST_DIR)); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo "Please run 'make svg' and 'git add $(SVG_DEST_DIR)' and commit the result:"; \
|
||||
echo "$${diff}"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@git diff --exit-code --color=always --cached $(SVG_DEST_DIR) \
|
||||
|| (code=$$?; echo "Please run 'make svg' and commit the result"; exit $${code})
|
||||
|
||||
.PHONY: lockfile-check
|
||||
lockfile-check:
|
||||
npm install --package-lock-only
|
||||
@diff=$$(git diff --color=always package-lock.json); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo "package-lock.json is inconsistent with package.json"; \
|
||||
echo "Please run 'npm install --package-lock-only' and commit the result:"; \
|
||||
echo "$${diff}"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@git diff --exit-code --color=always package-lock.json \
|
||||
|| (code=$$?; echo "Please run 'npm install --package-lock-only' and commit the result"; exit $${code})
|
||||
|
||||
.PHONY: update-translations
|
||||
update-translations:
|
||||
|
@ -1005,8 +1010,8 @@ generate-gitignore:
|
|||
|
||||
.PHONY: generate-images
|
||||
generate-images: | node_modules
|
||||
npm install --no-save --no-package-lock fabric@5 imagemin-zopfli@7
|
||||
node build/generate-images.js $(TAGS)
|
||||
npm install --no-save fabric@6.0.0-beta20 imagemin-zopfli@7
|
||||
node tools/generate-images.js $(TAGS)
|
||||
|
||||
.PHONY: generate-manpage
|
||||
generate-manpage:
|
||||
|
@ -1023,3 +1028,8 @@ docker:
|
|||
|
||||
# This endif closes the if at the top of the file
|
||||
endif
|
||||
|
||||
# Disable parallel execution because it would break some targets that don't
|
||||
# specify exact dependencies like 'backend' which does currently not depend
|
||||
# on 'frontend' to enable Node.js-less builds from source tarballs.
|
||||
.NOTPARALLEL:
|
||||
|
|
119
RELEASE-NOTES.md
119
RELEASE-NOTES.md
|
@ -4,6 +4,125 @@ A Forgejo release is published shortly after a Gitea release is published and th
|
|||
|
||||
The Forgejo admin should carefully read the required manual actions before upgrading. A point release (e.g. v1.21.1-0 or v1.21.2-0) does not require manual actions but others might (e.g. v1.20, v1.21).
|
||||
|
||||
## 1.21.8-0
|
||||
|
||||
The [complete list of commits](https://codeberg.org/forgejo/forgejo/commits/branch/v1.21/forgejo) included in the `Forgejo v1.21.8-0` release can be reviewed from the command line with:
|
||||
|
||||
```shell
|
||||
$ git clone https://codeberg.org/forgejo/forgejo/
|
||||
$ git -C forgejo log --oneline --no-merges v1.21.7-0..v1.21.8-0
|
||||
```
|
||||
|
||||
This stable release contains bug fixes.
|
||||
|
||||
* Recommended Action
|
||||
|
||||
We recommend that all Forgejo installations are [upgraded](https://forgejo.org/docs/v1.21/admin/upgrade/) to the latest version.
|
||||
|
||||
* [Forgejo Semantic Version](https://forgejo.org/docs/v1.21/user/semver/)
|
||||
|
||||
The semantic version was updated to `6.0.8+0-gitea-1.21.8`
|
||||
|
||||
* Bug fixes
|
||||
|
||||
The most prominent ones are described here, others can be found in the list of commits included in the release as described above.
|
||||
|
||||
* [Fix `/api/v1/{owner}/{repo}/issue_templates`](https://codeberg.org/forgejo/forgejo/commit/969d3f44101402afd9dd848e79dd5823d547a00d) which was always failing with a 500 error.
|
||||
* [Prevent error 500 on /user/settings/security when SignedUser has a linked account from a deactivated authentication source](https://codeberg.org/forgejo/forgejo/commit/d9418651af8c8a3276ebc40a516109c0f33139b0).
|
||||
* [Fix error 500 when pushing release to an empty repo](https://codeberg.org/forgejo/forgejo/commit/b76f370a3f8993638cad91547491b46631776f59).
|
||||
* [Fix incorrect rendering csv file when file size is larger than UI.CSV.MaxFileSize](https://codeberg.org/forgejo/forgejo/commit/e151e0467341bd25a2465ca15e81ebc0c8fa3fc4).
|
||||
* [Fix error 500 when deleting account with incorrect password or unsupported login type](https://codeberg.org/forgejo/forgejo/commit/66061d28286ee3adf69747c284d40121e2e4b280).
|
||||
* [handle user-defined `name` anchors like `[Link](#link)` linking to `<a name="link"></a>Link`](https://codeberg.org/forgejo/forgejo/commit/03caefbb02854d5c98a26d889e0e30c910651395).
|
||||
* [Use correct head commit for CODEOWNER](https://codeberg.org/forgejo/forgejo/commit/d1cebb0e884a88941e27e2280e117cc04d2665fe).
|
||||
* [Fix manual merge button](https://codeberg.org/forgejo/forgejo/commit/c70719c59c410e5378c04926c66461d7b9f72884).
|
||||
* [Make meilisearch do exact search for issues](https://codeberg.org/forgejo/forgejo/commit/a876ac2c7934fa0f8a66424e178b501e4a341e0f).
|
||||
* [Fix PR creation via api between branches of same repo with head field namespaced](https://codeberg.org/forgejo/forgejo/commit/120a173e24fd359b0b57f2bd1021168645fab5a8).
|
||||
|
||||
## 1.21.7-0
|
||||
|
||||
The [complete list of commits](https://codeberg.org/forgejo/forgejo/commits/branch/v1.21/forgejo) included in the `Forgejo v1.21.7-0` release can be reviewed from the command line with:
|
||||
|
||||
```shell
|
||||
$ git clone https://codeberg.org/forgejo/forgejo/
|
||||
$ git -C forgejo log --oneline --no-merges v1.21.6-0..v1.21.7-0
|
||||
```
|
||||
|
||||
This stable release contains bug fixes and a **security fix**.
|
||||
|
||||
* Recommended Action
|
||||
|
||||
We recommend that all Forgejo installations are [upgraded](https://forgejo.org/docs/v1.21/admin/upgrade/) to the latest version as soon as possible.
|
||||
|
||||
* [Forgejo Semantic Version](https://forgejo.org/docs/v1.21/user/semver/)
|
||||
|
||||
The semantic version was updated to `6.0.7+0-gitea-1.21.7`
|
||||
|
||||
* Built with Go 1.21.8
|
||||
|
||||
It [includes vulnerability fixes](https://groups.google.com/g/golang-announce/c/5pwGVUPoMbg).
|
||||
|
||||
* [CVE-2023-45290](https://go.dev/issue/65383) which could lead to memory exhaustion when parsing a multipart form.
|
||||
* [CVE-2023-45289](https://go.dev/issue/65065) which could allow incorrect forwarding of sensitive headers and cookies on HTTP redirect.
|
||||
|
||||
* Security fix
|
||||
|
||||
* The google.golang.org/protobuf module was bumped to version v1.33.0 to fix a bug in the google.golang.org/protobuf/encoding/protojson package which could cause the Unmarshal function to enter an infinite loop when handling some invalid inputs. [Read more in the announcement](https://groups.google.com/g/golang-announce/c/ArQ6CDgtEjY).
|
||||
|
||||
* Bug fixes
|
||||
|
||||
The most prominent ones are described here, others can be found in the list of commits included in the release as described above.
|
||||
|
||||
* [Fix tarball/zipball download bug](https://codeberg.org/forgejo/forgejo/commit/8e2c991b35de8c94899ad053e89339cea4538589).
|
||||
* [Ensure `HasIssueContentHistory` takes into account `comment_id`](https://codeberg.org/forgejo/forgejo/commit/8fb027fea5e9525293802d977fd3ee0c374ba9ba).
|
||||
|
||||
## 1.21.6-0
|
||||
|
||||
The [complete list of commits](https://codeberg.org/forgejo/forgejo/commits/branch/v1.21/forgejo) included in the `Forgejo v1.21.6-0` release can be reviewed from the command line with:
|
||||
|
||||
```shell
|
||||
$ git clone https://codeberg.org/forgejo/forgejo/
|
||||
$ git -C forgejo log --oneline --no-merges v1.21.5-0..v1.21.6-0
|
||||
```
|
||||
|
||||
This stable release contains bug fixes and a **security fix**, as explained in the [v1.21.6-0 companion blog post](https://forgejo.org/2024-02-release-v1-21-6-0/).
|
||||
|
||||
* Recommended Action
|
||||
|
||||
We **strongly recommend** that all Forgejo installations are [upgraded](https://forgejo.org/docs/v1.21/admin/upgrade/) to the latest version as soon as possible.
|
||||
|
||||
* [Forgejo Semantic Version](https://forgejo.org/docs/v1.21/user/semver/)
|
||||
|
||||
The semantic version was updated to `6.0.6+0-gitea-1.21.6`
|
||||
|
||||
* Security fix
|
||||
|
||||
* [Fix XSS vulnerabilities](https://codeberg.org/forgejo/forgejo/pulls/2434). It enabled attackers to inject client-side scripts into web pages displayed to Forgejo visitors.
|
||||
|
||||
* Bug fixes
|
||||
|
||||
The most prominent ones are described here, others can be found in the list of commits included in the release as described above.
|
||||
|
||||
* [Always write proc-receive hook for all git versions](https://codeberg.org/forgejo/forgejo/commit/a1fb6a2346193439dafaee5acf071632246e6dd7).
|
||||
* [Fix debian InRelease Acquire-By-Hash newline](https://codeberg.org/forgejo/forgejo/commit/8a2c4e9ff2743f47a8d1f081b9e35dcc16431115).
|
||||
* [Fix missing link on outgoing new release notifications](https://codeberg.org/forgejo/forgejo/commit/3a061083d65bdfc9acf0cb5839b84f6a9c17a727).
|
||||
* [Workaround to clean up old reviews on creating a new one](https://codeberg.org/forgejo/forgejo/commit/8377ecbfe1f2b72ec7d65c46cbc9022ad0ccd75f).
|
||||
* [Fix push to create with capitalize repo name](https://codeberg.org/forgejo/forgejo/commit/8782275c9c66ad6fc7c44503d7df9dae7196aa65).
|
||||
* In Markdown [don't try to make the link absolute if the link has a schema that's defined in `[markdown].CUSTOM_URL_SCHEMES`](https://codeberg.org/forgejo/forgejo/commit/6c100083c29fb0ccf0cc52e8767e540a260d9468), because they can't be made absolute.
|
||||
* [Fix Ctrl+Enter on submitting review comment](https://codeberg.org/forgejo/forgejo/commit/1c3a31d85112d10fb948d6f0b763191ed6f68e90).
|
||||
* In Git version v2.43.1, the behavior of `GIT_FLUSH` was accidentially flipped. This causes Forgejo to hang on the `check-attr` command, because no output was being flushed. [Workaround this by detecting if Git v2.43.1 is used and set `GIT_FLUSH=0` thus getting the correct behavior](https://codeberg.org/forgejo/forgejo/commit/ff468ab5e426582b068586ce13d5a5348365e783).
|
||||
* [When setting `url.host` on a URL object with no port specified (like is the case of default port), the resulting URL's port will not change. Workaround this quirk in the URL standard by explicitly setting port for the http and https protocols](https://codeberg.org/forgejo/forgejo/commit/628e1036cfbcfae442cb6494249fe11410447056).
|
||||
* [Fix elasticsearch Request Entity Too Large](https://codeberg.org/forgejo/forgejo/commit/e6f59f6e1489d63d53de0da1de406a7a71a82adb).
|
||||
* [Do not send update/delete release notifications when it is in a draft state](https://codeberg.org/forgejo/forgejo/commit/3c54a1dbf62e56d948feb1008512900140033737).
|
||||
* [Do not run Forgejo Actions workflows synchronized events on the same commit as the one used to create a pull request](https://codeberg.org/forgejo/forgejo/commit/ce96379aef6e92cff2e9982031d5248ef8b01947).
|
||||
* [Fix a MySQL performance regression introduced in v1.21.4-0](https://codeberg.org/forgejo/forgejo/commit/af98a0a7c6f4cbb5340974958ebe4389e3bf4e9a).
|
||||
* [Fix Internal Server Error when resolving comments](https://codeberg.org/forgejo/forgejo/commit/ad67d9ef1a219b21309f811c14e7353cbc4982e3).
|
||||
* Packages
|
||||
* Swift: [fix a failure to resolve from package registry](https://codeberg.org/forgejo/forgejo/commit/fab6780fda5d8ded020a98253a793e87ed94f634).
|
||||
* Alpine: [if the APKINFO contains an install if condition, write it in the APKINDEX](https://codeberg.org/forgejo/forgejo/commit/7afbc62057b876fb6711ef58743f664a2509dde4).
|
||||
* org-mode files
|
||||
* [It is possible that the description of an `Regularlink` is `Text` and not another `Regularlink`](https://codeberg.org/forgejo/forgejo/commit/781d2a68ccb276bf13caf0b378b74d9efeab3d39).
|
||||
* [Fix relative links on orgmode](https://codeberg.org/forgejo/forgejo/commit/fa700333ba2649d14f1670dd2745957704a33b40).
|
||||
|
||||
## 1.21.5-0
|
||||
|
||||
The [complete list of commits](https://codeberg.org/forgejo/forgejo/commits/branch/v1.21/forgejo) included in the `Forgejo v1.21.5-0` release can be reviewed from the command line with:
|
||||
|
|
20
assets/go-licenses.json
generated
20
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
|
@ -1,27 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Copyright 2024 The Forgejo Authors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
D=/tmp/crowdin-to-weblate
|
||||
mkdir -p $D
|
||||
|
||||
function checkout() {
|
||||
if test -d $D/gitea ; then
|
||||
git -C $D/gitea reset --hard
|
||||
return
|
||||
fi
|
||||
|
||||
git clone --depth 1 https://github.com/go-gitea/gitea $D/gitea
|
||||
}
|
||||
|
||||
function replace() {
|
||||
go run build/merge-forgejo-locales.go $D/gitea/options/locale
|
||||
cp -a $D/gitea/options/locale/* options/locale
|
||||
}
|
||||
|
||||
function run() {
|
||||
checkout
|
||||
replace
|
||||
}
|
||||
|
||||
"$@"
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2022 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
|
||||
// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
@ -7,103 +7,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
trimPrefix = "gitea_"
|
||||
sourceFolder = "options/locales/"
|
||||
)
|
||||
|
||||
// returns list of locales, still containing the file extension!
|
||||
func generate_locale_list() []string {
|
||||
localeFiles, _ := os.ReadDir(sourceFolder)
|
||||
locales := []string{}
|
||||
for _, localeFile := range localeFiles {
|
||||
if !localeFile.IsDir() && strings.HasPrefix(localeFile.Name(), trimPrefix) {
|
||||
locales = append(locales, strings.TrimPrefix(localeFile.Name(), trimPrefix))
|
||||
}
|
||||
}
|
||||
return locales
|
||||
}
|
||||
|
||||
// replace all occurrences of Gitea with Forgejo
|
||||
func renameGiteaForgejo(filename string) []byte {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
replacements := []string{
|
||||
"Gitea", "Forgejo",
|
||||
"https://docs.gitea.com/installation/install-from-binary", "https://forgejo.org/download/#installation-from-binary",
|
||||
"https://github.com/go-gitea/gitea/tree/master/docker", "https://forgejo.org/download/#container-image",
|
||||
"https://docs.gitea.com/installation/install-from-package", "https://forgejo.org/download",
|
||||
"https://code.gitea.io/gitea", "https://forgejo.org/download",
|
||||
"code.gitea.io/gitea", "Forgejo",
|
||||
`<a href="https://github.com/go-gitea/gitea/issues" target="_blank">GitHub</a>`, `<a href="https://codeberg.org/forgejo/forgejo/issues" target="_blank">Codeberg</a>`,
|
||||
"https://github.com/go-gitea/gitea", "https://codeberg.org/forgejo/forgejo",
|
||||
"https://blog.gitea.io", "https://forgejo.org/news",
|
||||
"https://docs.gitea.com/usage/protected-tags", "https://forgejo.org/docs/latest/user/protection/#protected-tags",
|
||||
"https://docs.gitea.com/usage/webhooks", "https://forgejo.org/docs/latest/user/webhooks/",
|
||||
}
|
||||
replacer := strings.NewReplacer(replacements...)
|
||||
replaced := make(map[string]bool, len(replacements)/2)
|
||||
count_replaced := func(original string) {
|
||||
for i := 0; i < len(replacements); i += 2 {
|
||||
if strings.Contains(original, replacements[i]) {
|
||||
replaced[replacements[i]] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out := make([]byte, 0, 1024)
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
if strings.HasPrefix(line, "license_desc=") {
|
||||
line = strings.Replace(line, "GitHub", "Forgejo", 1)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
|
||||
out = append(out, []byte(line+"\n")...)
|
||||
} else if strings.HasPrefix(line, "settings.web_hook_name_gitea") {
|
||||
out = append(out, []byte(line+"\n")...)
|
||||
out = append(out, []byte("settings.web_hook_name_forgejo = Forgejo\n")...)
|
||||
} else if strings.HasPrefix(line, "migrate.gitea.description") {
|
||||
re := regexp.MustCompile(`(.*Gitea)`)
|
||||
out = append(out, []byte(re.ReplaceAllString(line, "${1}/Forgejo")+"\n")...)
|
||||
} else {
|
||||
count_replaced(line)
|
||||
out = append(out, []byte(replacer.Replace(line)+"\n")...)
|
||||
}
|
||||
}
|
||||
file.Close()
|
||||
if strings.HasSuffix(filename, "gitea_en-US.ini") {
|
||||
for i := 0; i < len(replacements); i += 2 {
|
||||
if replaced[replacements[i]] == false {
|
||||
log.Fatalf("%s was never used to replace something in %s, it is obsolete and must be updated", replacements[i], filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func main() {
|
||||
d := os.Args[1]
|
||||
files, err := os.ReadDir(d)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
p := d + "/" + f.Name()
|
||||
os.WriteFile(p, renameGiteaForgejo(p), 0o644)
|
||||
}
|
||||
fmt.Println("NOT NEEDED: THIS IS A NOOP AS OF Forgejo 7.0 BUT KEPT FOR BACKWARD COMPATIBILITY")
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@ import (
|
|||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
pwd "code.gitea.io/gitea/modules/auth/password"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
@ -47,7 +47,8 @@ var microcmdUserCreate = &cli.Command{
|
|||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "must-change-password",
|
||||
Usage: "Set this option to false to prevent forcing the user to change their password after initial login, (Default: true)",
|
||||
Usage: "Set this option to false to prevent forcing the user to change their password after initial login",
|
||||
Value: true,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "random-password-length",
|
||||
|
@ -110,8 +111,7 @@ func runCreateUser(c *cli.Context) error {
|
|||
return errors.New("must set either password or random-password flag")
|
||||
}
|
||||
|
||||
// always default to true
|
||||
changePassword := true
|
||||
changePassword := c.Bool("must-change-password")
|
||||
|
||||
// If this is the first user being created.
|
||||
// Take it as the admin and don't force a password update.
|
||||
|
@ -119,14 +119,10 @@ func runCreateUser(c *cli.Context) error {
|
|||
changePassword = false
|
||||
}
|
||||
|
||||
if c.IsSet("must-change-password") {
|
||||
changePassword = c.Bool("must-change-password")
|
||||
}
|
||||
|
||||
restricted := util.OptionalBoolNone
|
||||
restricted := optional.None[bool]()
|
||||
|
||||
if c.IsSet("restricted") {
|
||||
restricted = util.OptionalBoolOf(c.Bool("restricted"))
|
||||
restricted = optional.Some(c.Bool("restricted"))
|
||||
}
|
||||
|
||||
// default user visibility in app.ini
|
||||
|
@ -142,7 +138,7 @@ func runCreateUser(c *cli.Context) error {
|
|||
}
|
||||
|
||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||
IsActive: util.OptionalBoolTrue,
|
||||
IsActive: optional.Some(true),
|
||||
IsRestricted: restricted,
|
||||
}
|
||||
|
||||
|
|
18
cmd/dump.go
18
cmd/dump.go
|
@ -454,14 +454,18 @@ func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeA
|
|||
for _, file := range files {
|
||||
currentAbsPath := filepath.Join(absPath, file.Name())
|
||||
currentInsidePath := path.Join(insidePath, file.Name())
|
||||
|
||||
if util.SliceContainsString(excludeAbsPath, currentAbsPath) {
|
||||
log.Debug("Skipping %q because matched an excluded path.", currentAbsPath)
|
||||
continue
|
||||
}
|
||||
|
||||
if file.IsDir() {
|
||||
if !util.SliceContainsString(excludeAbsPath, currentAbsPath) {
|
||||
if err := addFile(w, currentInsidePath, currentAbsPath, false); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = addRecursiveExclude(w, currentInsidePath, currentAbsPath, excludeAbsPath, verbose); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := addFile(w, currentInsidePath, currentAbsPath, false); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = addRecursiveExclude(w, currentInsidePath, currentAbsPath, excludeAbsPath, verbose); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// only copy regular files and symlink regular files, skip non-regular files like socket/pipe/...
|
||||
|
|
117
cmd/dump_test.go
Normal file
117
cmd/dump_test.go
Normal file
|
@ -0,0 +1,117 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mholt/archiver/v3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type mockArchiver struct {
|
||||
addedFiles []string
|
||||
}
|
||||
|
||||
func (mockArchiver) Create(out io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockArchiver) Write(f archiver.File) error {
|
||||
m.addedFiles = append(m.addedFiles, f.Name())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mockArchiver) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestAddRecursiveExclude(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
archiver := &mockArchiver{}
|
||||
|
||||
err := addRecursiveExclude(archiver, "", dir, []string{}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, archiver.addedFiles)
|
||||
})
|
||||
|
||||
t.Run("Single file", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
err := os.WriteFile(dir+"/example", nil, 0o666)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("No exclude", func(t *testing.T) {
|
||||
archiver := &mockArchiver{}
|
||||
|
||||
err = addRecursiveExclude(archiver, "", dir, nil, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, archiver.addedFiles, 1)
|
||||
assert.EqualValues(t, "example", archiver.addedFiles[0])
|
||||
})
|
||||
|
||||
t.Run("With exclude", func(t *testing.T) {
|
||||
archiver := &mockArchiver{}
|
||||
|
||||
err = addRecursiveExclude(archiver, "", dir, []string{dir + "/example"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, archiver.addedFiles)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("File inside directory", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
err := os.MkdirAll(dir+"/deep/nested/folder", 0o750)
|
||||
assert.NoError(t, err)
|
||||
err = os.WriteFile(dir+"/deep/nested/folder/example", nil, 0o666)
|
||||
assert.NoError(t, err)
|
||||
err = os.WriteFile(dir+"/deep/nested/folder/another-file", nil, 0o666)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("No exclude", func(t *testing.T) {
|
||||
archiver := &mockArchiver{}
|
||||
|
||||
err = addRecursiveExclude(archiver, "", dir, nil, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, archiver.addedFiles, 5)
|
||||
assert.EqualValues(t, "deep", archiver.addedFiles[0])
|
||||
assert.EqualValues(t, "deep/nested", archiver.addedFiles[1])
|
||||
assert.EqualValues(t, "deep/nested/folder", archiver.addedFiles[2])
|
||||
assert.EqualValues(t, "deep/nested/folder/example", archiver.addedFiles[3])
|
||||
assert.EqualValues(t, "deep/nested/folder/another-file", archiver.addedFiles[4])
|
||||
})
|
||||
|
||||
t.Run("Exclude first directory", func(t *testing.T) {
|
||||
archiver := &mockArchiver{}
|
||||
|
||||
err = addRecursiveExclude(archiver, "", dir, []string{dir + "/deep"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, archiver.addedFiles)
|
||||
})
|
||||
|
||||
t.Run("Exclude nested directory", func(t *testing.T) {
|
||||
archiver := &mockArchiver{}
|
||||
|
||||
err = addRecursiveExclude(archiver, "", dir, []string{dir + "/deep/nested/folder"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, archiver.addedFiles, 2)
|
||||
assert.EqualValues(t, "deep", archiver.addedFiles[0])
|
||||
assert.EqualValues(t, "deep/nested", archiver.addedFiles[1])
|
||||
})
|
||||
|
||||
t.Run("Exclude file", func(t *testing.T) {
|
||||
archiver := &mockArchiver{}
|
||||
|
||||
err = addRecursiveExclude(archiver, "", dir, []string{dir + "/deep/nested/folder/example"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, archiver.addedFiles, 4)
|
||||
assert.EqualValues(t, "deep", archiver.addedFiles[0])
|
||||
assert.EqualValues(t, "deep/nested", archiver.addedFiles[1])
|
||||
assert.EqualValues(t, "deep/nested/folder", archiver.addedFiles[2])
|
||||
assert.EqualValues(t, "deep/nested/folder/another-file", archiver.addedFiles[3])
|
||||
})
|
||||
})
|
||||
}
|
|
@ -35,7 +35,8 @@ func SubcmdActionsGenerateRunnerToken(ctx context.Context) *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "generate-runner-token",
|
||||
Usage: "Generate a new token for a runner to use to register with the server",
|
||||
Action: prepareWorkPathAndCustomConf(ctx, func(cliCtx *cli.Context) error { return RunGenerateActionsRunnerToken(ctx, cliCtx) }),
|
||||
Before: prepareWorkPathAndCustomConf(ctx),
|
||||
Action: func(cliCtx *cli.Context) error { return RunGenerateActionsRunnerToken(ctx, cliCtx) },
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "scope",
|
||||
|
@ -59,7 +60,8 @@ func SubcmdActionsRegister(ctx context.Context) *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "register",
|
||||
Usage: "Idempotent registration of a runner using a shared secret",
|
||||
Action: prepareWorkPathAndCustomConf(ctx, func(cliCtx *cli.Context) error { return RunRegister(ctx, cliCtx) }),
|
||||
Before: prepareWorkPathAndCustomConf(ctx),
|
||||
Action: func(cliCtx *cli.Context) error { return RunRegister(ctx, cliCtx) },
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "secret",
|
||||
|
@ -219,25 +221,3 @@ func RunGenerateActionsRunnerToken(ctx context.Context, cliCtx *cli.Context) err
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepareWorkPathAndCustomConf(ctx context.Context, action cli.ActionFunc) func(cliCtx *cli.Context) error {
|
||||
return func(cliCtx *cli.Context) error {
|
||||
if !ContextGetNoInit(ctx) {
|
||||
var args setting.ArgWorkPathAndCustomConf
|
||||
// from children to parent, check the global flags
|
||||
for _, curCtx := range cliCtx.Lineage() {
|
||||
if curCtx.IsSet("work-path") && args.WorkPath == "" {
|
||||
args.WorkPath = curCtx.String("work-path")
|
||||
}
|
||||
if curCtx.IsSet("custom-path") && args.CustomPath == "" {
|
||||
args.CustomPath = curCtx.String("custom-path")
|
||||
}
|
||||
if curCtx.IsSet("config") && args.CustomConf == "" {
|
||||
args.CustomConf = curCtx.String("config")
|
||||
}
|
||||
}
|
||||
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
|
||||
}
|
||||
return action(cliCtx)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -145,3 +145,25 @@ func handleCliResponseExtra(ctx context.Context, extra private.ResponseExtra) er
|
|||
}
|
||||
return cli.Exit(extra.Error, 1)
|
||||
}
|
||||
|
||||
func prepareWorkPathAndCustomConf(ctx context.Context) func(c *cli.Context) error {
|
||||
return func(c *cli.Context) error {
|
||||
if !ContextGetNoInit(ctx) {
|
||||
var args setting.ArgWorkPathAndCustomConf
|
||||
// from children to parent, check the global flags
|
||||
for _, curCtx := range c.Lineage() {
|
||||
if curCtx.IsSet("work-path") && args.WorkPath == "" {
|
||||
args.WorkPath = curCtx.String("work-path")
|
||||
}
|
||||
if curCtx.IsSet("custom-path") && args.CustomPath == "" {
|
||||
args.CustomPath = curCtx.String("custom-path")
|
||||
}
|
||||
if curCtx.IsSet("config") && args.CustomConf == "" {
|
||||
args.CustomConf = curCtx.String("config")
|
||||
}
|
||||
}
|
||||
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
90
cmd/hook.go
90
cmd/hook.go
|
@ -293,9 +293,32 @@ Forgejo or set your environment appropriately.`, "")
|
|||
return nil
|
||||
}
|
||||
|
||||
// runHookUpdate process the update hook: https://git-scm.com/docs/githooks#update
|
||||
func runHookUpdate(c *cli.Context) error {
|
||||
// Update is empty and is kept only for backwards compatibility
|
||||
return nil
|
||||
// Now if we're an internal don't do anything else
|
||||
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
// The last three arguments given to the hook are in order: reference name, old commit ID and new commit ID.
|
||||
args := os.Args[len(os.Args)-3:]
|
||||
refFullName := git.RefName(args[0])
|
||||
newCommitID := args[2]
|
||||
|
||||
// Only process pull references.
|
||||
if !refFullName.IsPull() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deletion of the ref means that the new commit ID is only composed of '0'.
|
||||
if strings.ContainsFunc(newCommitID, func(e rune) bool { return e != '0' }) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fail(ctx, fmt.Sprintf("The deletion of %s is skipped as it's an internal reference.", refFullName), "")
|
||||
}
|
||||
|
||||
func runHookPostReceive(c *cli.Context) error {
|
||||
|
@ -324,11 +347,10 @@ Forgejo or set your environment appropriately.`, "")
|
|||
}
|
||||
|
||||
var out io.Writer
|
||||
var dWriter *delayWriter
|
||||
out = &nilWriter{}
|
||||
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 {
|
||||
|
@ -391,7 +413,6 @@ Forgejo or set your environment appropriately.`, "")
|
|||
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)
|
||||
}
|
||||
|
@ -411,7 +432,6 @@ Forgejo or set your environment appropriately.`, "")
|
|||
}
|
||||
fmt.Fprintf(out, "Processed %d references in total\n", total)
|
||||
|
||||
_ = dWriter.Close()
|
||||
hookPrintResults(results)
|
||||
return nil
|
||||
}
|
||||
|
@ -424,7 +444,6 @@ Forgejo or set your environment appropriately.`, "")
|
|||
|
||||
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)
|
||||
}
|
||||
|
@ -440,9 +459,8 @@ Forgejo or set your environment appropriately.`, "")
|
|||
return fail(ctx, extra.UserMsg, "SetDefaultBranch failed: %v", extra.Error)
|
||||
}
|
||||
}
|
||||
_ = dWriter.Close()
|
||||
hookPrintResults(results)
|
||||
|
||||
hookPrintResults(results)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -583,7 +601,7 @@ Forgejo or set your environment appropriately.`, "")
|
|||
|
||||
for {
|
||||
// note: pktLineTypeUnknow means pktLineTypeFlush and pktLineTypeData all allowed
|
||||
rs, err = readPktLine(ctx, reader, pktLineTypeUnknow)
|
||||
rs, err = readPktLine(ctx, reader, pktLineTypeUnknown)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -604,7 +622,7 @@ Forgejo or set your environment appropriately.`, "")
|
|||
|
||||
if hasPushOptions {
|
||||
for {
|
||||
rs, err = readPktLine(ctx, reader, pktLineTypeUnknow)
|
||||
rs, err = readPktLine(ctx, reader, pktLineTypeUnknown)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -699,8 +717,8 @@ Forgejo or set your environment appropriately.`, "")
|
|||
type pktLineType int64
|
||||
|
||||
const (
|
||||
// UnKnow type
|
||||
pktLineTypeUnknow pktLineType = 0
|
||||
// Unknown type
|
||||
pktLineTypeUnknown pktLineType = 0
|
||||
// flush-pkt "0000"
|
||||
pktLineTypeFlush pktLineType = iota
|
||||
// data line
|
||||
|
@ -714,22 +732,16 @@ type gitPktLine struct {
|
|||
Data []byte
|
||||
}
|
||||
|
||||
// Reads an Pkt-Line from `in`. If requestType is not unknown, it will a
|
||||
func readPktLine(ctx context.Context, in *bufio.Reader, requestType pktLineType) (*gitPktLine, error) {
|
||||
var (
|
||||
err error
|
||||
r *gitPktLine
|
||||
)
|
||||
|
||||
// read prefix
|
||||
// Read length prefix
|
||||
lengthBytes := make([]byte, 4)
|
||||
for i := 0; i < 4; i++ {
|
||||
lengthBytes[i], err = in.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fail(ctx, "Protocol: stdin error", "Pkt-Line: read stdin failed : %v", err)
|
||||
}
|
||||
if n, err := in.Read(lengthBytes); n != 4 || err != nil {
|
||||
return nil, fail(ctx, "Protocol: stdin error", "Pkt-Line: read stdin failed : %v", err)
|
||||
}
|
||||
|
||||
r = new(gitPktLine)
|
||||
var err error
|
||||
r := &gitPktLine{}
|
||||
r.Length, err = strconv.ParseUint(string(lengthBytes), 16, 32)
|
||||
if err != nil {
|
||||
return nil, fail(ctx, "Protocol: format parse error", "Pkt-Line format is wrong :%v", err)
|
||||
|
@ -748,11 +760,8 @@ func readPktLine(ctx context.Context, in *bufio.Reader, requestType pktLineType)
|
|||
}
|
||||
|
||||
r.Data = make([]byte, r.Length-4)
|
||||
for i := range r.Data {
|
||||
r.Data[i], err = in.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fail(ctx, "Protocol: data error", "Pkt-Line: read stdin failed : %v", err)
|
||||
}
|
||||
if n, err := io.ReadFull(in, r.Data); uint64(n) != r.Length-4 || err != nil {
|
||||
return nil, fail(ctx, "Protocol: stdin error", "Pkt-Line: read stdin failed : %v", err)
|
||||
}
|
||||
|
||||
r.Type = pktLineTypeData
|
||||
|
@ -768,20 +777,23 @@ func writeFlushPktLine(ctx context.Context, out io.Writer) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Write an Pkt-Line based on `data` to `out` according to the specifcation.
|
||||
// https://git-scm.com/docs/protocol-common
|
||||
func writeDataPktLine(ctx context.Context, out io.Writer, data []byte) error {
|
||||
hexchar := []byte("0123456789abcdef")
|
||||
hex := func(n uint64) byte {
|
||||
return hexchar[(n)&15]
|
||||
// Implementations SHOULD NOT send an empty pkt-line ("0004").
|
||||
if len(data) == 0 {
|
||||
return fail(ctx, "Protocol: write error", "Not allowed to write empty Pkt-Line")
|
||||
}
|
||||
|
||||
length := uint64(len(data) + 4)
|
||||
tmp := make([]byte, 4)
|
||||
tmp[0] = hex(length >> 12)
|
||||
tmp[1] = hex(length >> 8)
|
||||
tmp[2] = hex(length >> 4)
|
||||
tmp[3] = hex(length)
|
||||
|
||||
lr, err := out.Write(tmp)
|
||||
// The maximum length of a pkt-line’s data component is 65516 bytes.
|
||||
// Implementations MUST NOT send pkt-line whose length exceeds 65520 (65516 bytes of payload + 4 bytes of length data).
|
||||
if length > 65520 {
|
||||
return fail(ctx, "Protocol: write error", "Pkt-Line exceeds maximum of 65520 bytes")
|
||||
}
|
||||
|
||||
lr, err := fmt.Fprintf(out, "%04x", length)
|
||||
if err != nil || lr != 4 {
|
||||
return fail(ctx, "Protocol: write error", "Pkt-Line response failed: %v", err)
|
||||
}
|
||||
|
|
164
cmd/hook_test.go
164
cmd/hook_test.go
|
@ -7,36 +7,158 @@ import (
|
|||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func TestPktLine(t *testing.T) {
|
||||
// test read
|
||||
ctx := context.Background()
|
||||
s := strings.NewReader("0000")
|
||||
r := bufio.NewReader(s)
|
||||
result, err := readPktLine(ctx, r, pktLineTypeFlush)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, pktLineTypeFlush, result.Type)
|
||||
|
||||
s = strings.NewReader("0006a\n")
|
||||
r = bufio.NewReader(s)
|
||||
result, err = readPktLine(ctx, r, pktLineTypeData)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, pktLineTypeData, result.Type)
|
||||
assert.Equal(t, []byte("a\n"), result.Data)
|
||||
t.Run("Read", func(t *testing.T) {
|
||||
s := strings.NewReader("0000")
|
||||
r := bufio.NewReader(s)
|
||||
result, err := readPktLine(ctx, r, pktLineTypeFlush)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, pktLineTypeFlush, result.Type)
|
||||
|
||||
// test write
|
||||
w := bytes.NewBuffer([]byte{})
|
||||
err = writeFlushPktLine(ctx, w)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("0000"), w.Bytes())
|
||||
s = strings.NewReader("0006a\n")
|
||||
r = bufio.NewReader(s)
|
||||
result, err = readPktLine(ctx, r, pktLineTypeData)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, pktLineTypeData, result.Type)
|
||||
assert.Equal(t, []byte("a\n"), result.Data)
|
||||
|
||||
w.Reset()
|
||||
err = writeDataPktLine(ctx, w, []byte("a\nb"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("0007a\nb"), w.Bytes())
|
||||
s = strings.NewReader("0004")
|
||||
r = bufio.NewReader(s)
|
||||
result, err = readPktLine(ctx, r, pktLineTypeData)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, result)
|
||||
|
||||
data := strings.Repeat("x", 65516)
|
||||
r = bufio.NewReader(strings.NewReader("fff0" + data))
|
||||
result, err = readPktLine(ctx, r, pktLineTypeData)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, pktLineTypeData, result.Type)
|
||||
assert.Equal(t, []byte(data), result.Data)
|
||||
|
||||
r = bufio.NewReader(strings.NewReader("fff1a"))
|
||||
result, err = readPktLine(ctx, r, pktLineTypeData)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
|
||||
t.Run("Write", func(t *testing.T) {
|
||||
w := bytes.NewBuffer([]byte{})
|
||||
err := writeFlushPktLine(ctx, w)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("0000"), w.Bytes())
|
||||
|
||||
w.Reset()
|
||||
err = writeDataPktLine(ctx, w, []byte("a\nb"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("0007a\nb"), w.Bytes())
|
||||
|
||||
w.Reset()
|
||||
data := bytes.Repeat([]byte{0x05}, 288)
|
||||
err = writeDataPktLine(ctx, w, data)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, append([]byte("0124"), data...), w.Bytes())
|
||||
|
||||
w.Reset()
|
||||
err = writeDataPktLine(ctx, w, nil)
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, w.Bytes())
|
||||
|
||||
w.Reset()
|
||||
data = bytes.Repeat([]byte{0x64}, 65516)
|
||||
err = writeDataPktLine(ctx, w, data)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, append([]byte("fff0"), data...), w.Bytes())
|
||||
|
||||
w.Reset()
|
||||
err = writeDataPktLine(ctx, w, bytes.Repeat([]byte{0x64}, 65516+1))
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, w.Bytes())
|
||||
})
|
||||
}
|
||||
|
||||
func TestDelayWriter(t *testing.T) {
|
||||
// Setup the environment.
|
||||
defer test.MockVariableValue(&setting.InternalToken, "Random")()
|
||||
defer test.MockVariableValue(&setting.InstallLock, true)()
|
||||
defer test.MockVariableValue(&setting.Git.VerbosePush, true)()
|
||||
require.NoError(t, os.Setenv("SSH_ORIGINAL_COMMAND", "true"))
|
||||
|
||||
// Setup the Stdin.
|
||||
f, err := os.OpenFile(t.TempDir()+"/stdin", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o666)
|
||||
require.NoError(t, err)
|
||||
_, err = f.Write([]byte("00000000000000000000 00000000000000000001 refs/head/main\n"))
|
||||
require.NoError(t, err)
|
||||
_, err = f.Seek(0, 0)
|
||||
require.NoError(t, err)
|
||||
defer test.MockVariableValue(os.Stdin, *f)()
|
||||
|
||||
// Setup the server that processes the hooks.
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
time.Sleep(time.Millisecond * 600)
|
||||
}))
|
||||
defer ts.Close()
|
||||
defer test.MockVariableValue(&setting.LocalURL, ts.URL+"/")()
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Commands = []*cli.Command{subcmdHookPreReceive}
|
||||
|
||||
// Capture what's being written into stdout
|
||||
captureStdout := func(t *testing.T) (finish func() (output string)) {
|
||||
t.Helper()
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
require.NoError(t, err)
|
||||
resetStdout := test.MockVariableValue(os.Stdout, *w)
|
||||
|
||||
return func() (output string) {
|
||||
w.Close()
|
||||
resetStdout()
|
||||
|
||||
out, err := io.ReadAll(r)
|
||||
require.NoError(t, err)
|
||||
return string(out)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("Should delay", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Millisecond*500)()
|
||||
finish := captureStdout(t)
|
||||
|
||||
err = app.Run([]string{"./forgejo", "pre-receive"})
|
||||
require.NoError(t, err)
|
||||
out := finish()
|
||||
|
||||
require.Contains(t, out, "* Checking 1 references")
|
||||
require.Contains(t, out, "Checked 1 references in total")
|
||||
})
|
||||
|
||||
t.Run("Shouldn't delay", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Second*5)()
|
||||
finish := captureStdout(t)
|
||||
|
||||
err = app.Run([]string{"./forgejo", "pre-receive"})
|
||||
require.NoError(t, err)
|
||||
out := finish()
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, out)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ func runKeys(c *cli.Context) error {
|
|||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
setup(ctx, false)
|
||||
setup(ctx, c.Bool("debug"))
|
||||
|
||||
authorizedString, extra := private.AuthorizedPublicKeyByContent(ctx, content)
|
||||
// do not use handleCliResponseExtra or cli.NewExitError, if it exists immediately, it breaks some tests like Test_CmdKeys
|
||||
|
|
19
cmd/serv.go
19
cmd/serv.go
|
@ -14,6 +14,7 @@ import (
|
|||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
|
@ -63,21 +64,10 @@ func setup(ctx context.Context, debug bool) {
|
|||
setupConsoleLogger(log.FATAL, false, os.Stderr)
|
||||
}
|
||||
setting.MustInstalled()
|
||||
if debug {
|
||||
setting.RunMode = "dev"
|
||||
}
|
||||
|
||||
// Check if setting.RepoRootPath exists. It could be the case that it doesn't exist, this can happen when
|
||||
// `[repository]` `ROOT` is a relative path and $GITEA_WORK_DIR isn't passed to the SSH connection.
|
||||
if _, err := os.Stat(setting.RepoRootPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
_ = fail(ctx, "Incorrect configuration, no repository directory.", "Directory `[repository].ROOT` %q was not found, please check if $GITEA_WORK_DIR is passed to the SSH connection or make `[repository].ROOT` an absolute value.", setting.RepoRootPath)
|
||||
} else {
|
||||
_ = fail(ctx, "Incorrect configuration, repository directory is inaccessible", "Directory `[repository].ROOT` %q is inaccessible. err: %v", setting.RepoRootPath, err)
|
||||
}
|
||||
_ = fail(ctx, "Unable to access repository path", "Unable to access repository path %q, err: %v", setting.RepoRootPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := git.InitSimple(context.Background()); err != nil {
|
||||
_ = fail(ctx, "Failed to init git", "Failed to init git, err: %v", err)
|
||||
}
|
||||
|
@ -117,7 +107,10 @@ func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error
|
|||
logMsg = userMessage + ". " + logMsg
|
||||
}
|
||||
}
|
||||
_ = private.SSHLog(ctx, true, logMsg)
|
||||
// Don't send an log if this is done in a test and no InternalToken is set.
|
||||
if !testing.Testing() || setting.InternalToken != "" {
|
||||
_ = private.SSHLog(ctx, true, logMsg)
|
||||
}
|
||||
}
|
||||
return cli.Exit("", 1)
|
||||
}
|
||||
|
|
|
@ -441,7 +441,7 @@ INTERNAL_TOKEN =
|
|||
;INTERNAL_TOKEN_URI = file:/etc/gitea/internal_token
|
||||
;;
|
||||
;; How long to remember that a user is logged in before requiring relogin (in days)
|
||||
;LOGIN_REMEMBER_DAYS = 7
|
||||
;LOGIN_REMEMBER_DAYS = 31
|
||||
;;
|
||||
;; Name of the cookie used to store the current username.
|
||||
;COOKIE_USERNAME = gitea_awesome
|
||||
|
@ -969,6 +969,12 @@ LEVEL = Info
|
|||
;GO_GET_CLONE_URL_PROTOCOL = https
|
||||
;;
|
||||
;; Close issues as long as a commit on any branch marks it as fixed
|
||||
;DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false
|
||||
;;
|
||||
;; Allow users to push local repositories to Gitea and have them automatically created for a user or an org
|
||||
;ENABLE_PUSH_CREATE_USER = false
|
||||
;ENABLE_PUSH_CREATE_ORG = false
|
||||
;;
|
||||
;; Comma separated list of globally disabled repo units. Allowed values: repo.issues, repo.ext_issues, repo.pulls, repo.wiki, repo.ext_wiki, repo.projects, repo.packages, repo.actions.
|
||||
;DISABLED_REPO_UNITS =
|
||||
;;
|
||||
|
@ -991,6 +997,9 @@ LEVEL = Info
|
|||
;; Disable stars feature.
|
||||
;DISABLE_STARS = false
|
||||
;;
|
||||
;; Disable repository forking.
|
||||
;DISABLE_FORKS = false
|
||||
;;
|
||||
;; The default branch name of new repositories
|
||||
;DEFAULT_BRANCH = main
|
||||
;;
|
||||
|
@ -1489,6 +1498,11 @@ LEVEL = Info
|
|||
;DEFAULT_EMAIL_NOTIFICATIONS = enabled
|
||||
;; Send an email to all admins when a new user signs up to inform the admins about this act. Options: true, false
|
||||
;SEND_NOTIFICATION_EMAIL_ON_NEW_USER = false
|
||||
;; Disabled features for users, could be "deletion", "manage_ssh_keys","manage_gpg_keys" more features can be disabled in future
|
||||
;; - deletion: a user cannot delete their own account
|
||||
;; - manage_ssh_keys: a user cannot configure ssh keys
|
||||
;; - manage_gpg_keys: a user cannot configure gpg keys
|
||||
;USER_DISABLED_FEATURES =
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -2324,6 +2338,8 @@ LEVEL = Info
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Set the maximum number of characters in a mermaid source. (Set to -1 to disable limits)
|
||||
;MERMAID_MAX_SOURCE_CHARACTERS = 5000
|
||||
;; Set the maximum number of lines allowed for a filepreview. (Set to -1 to disable limits; set to 0 to disable the feature)
|
||||
;FILEPREVIEW_MAX_LINES = 50
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -2609,7 +2625,7 @@ LEVEL = Info
|
|||
;ENDLESS_TASK_TIMEOUT = 3h
|
||||
;; Timeout to cancel the jobs which have waiting status, but haven't been picked by a runner for a long time
|
||||
;ABANDONED_JOB_TIMEOUT = 24h
|
||||
;; Strings committers can place inside a commit message to skip executing the corresponding actions workflow
|
||||
;; Strings committers can place inside a commit message or PR title to skip executing the corresponding actions workflow
|
||||
;SKIP_WORKFLOW_STRINGS = [skip ci],[ci skip],[no ci],[skip actions],[actions skip]
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -37,7 +37,7 @@ gitea embedded list [--include-vendored] [patterns...]
|
|||
|
||||
- 列出所有模板文件,无论在哪个虚拟目录下:`**.tmpl`
|
||||
- 列出所有邮件模板文件:`templates/mail/**.tmpl`
|
||||
- 列出 `public/img` 目录下的所有文件:`public/img/**`
|
||||
列出 `public/assets/img` 目录下的所有文件:`public/assets/img/**`
|
||||
|
||||
不要忘记为模式使用引号,因为空格、`*` 和其他字符可能对命令行解释器有特殊含义。
|
||||
|
||||
|
@ -49,8 +49,8 @@ gitea embedded list [--include-vendored] [patterns...]
|
|||
|
||||
```sh
|
||||
$ gitea embedded list '**openid**'
|
||||
public/img/auth/openid_connect.svg
|
||||
public/img/openid-16x16.png
|
||||
public/assets/img/auth/openid_connect.svg
|
||||
public/assets/img/openid-16x16.png
|
||||
templates/user/auth/finalize_openid.tmpl
|
||||
templates/user/auth/signin_openid.tmpl
|
||||
templates/user/auth/signup_openid_connect.tmpl
|
||||
|
|
|
@ -518,14 +518,17 @@ And the following unique queues:
|
|||
|
||||
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
|
||||
- `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations.
|
||||
- `SEND_NOTIFICATION_EMAIL_ON_NEW_USER`: **false**: Send an email to all admins when a new user signs up to inform the admins about this act.
|
||||
- `USER_DISABLED_FEATURES`: **_empty_** Disabled features for users, could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys` and more features can be added in future.
|
||||
- `deletion`: User cannot delete their own account.
|
||||
- `manage_ssh_keys`: User cannot configure ssh keys.
|
||||
- `manage_gpg_keys`: User cannot configure gpg keys.
|
||||
|
||||
## Security (`security`)
|
||||
|
||||
- `INSTALL_LOCK`: **false**: Controls access to the installation page. When set to "true", the installation page is not accessible.
|
||||
- `SECRET_KEY`: **\<random at every install\>**: Global secret key. This key is VERY IMPORTANT, if you lost it, the data encrypted by it (like 2FA secret) can't be decrypted anymore.
|
||||
- `SECRET_KEY_URI`: **_empty_**: Instead of defining SECRET_KEY, this option can be used to use the key stored in a file (example value: `file:/etc/gitea/secret_key`). It shouldn't be lost like SECRET_KEY.
|
||||
- `LOGIN_REMEMBER_DAYS`: **7**: Cookie lifetime, in days.
|
||||
- `LOGIN_REMEMBER_DAYS`: **31**: How long to remember that a user is logged in before requiring relogin (in days).
|
||||
- `COOKIE_REMEMBER_NAME`: **gitea\_incredible**: Name of cookie used to store authentication
|
||||
information.
|
||||
- `REVERSE_PROXY_AUTHENTICATION_USER`: **X-WEBAUTH-USER**: Header name for reverse proxy
|
||||
|
@ -829,7 +832,7 @@ Default templates for project boards:
|
|||
## Issue and pull request attachments (`attachment`)
|
||||
|
||||
- `ENABLED`: **true**: Whether issue and pull request attachments are enabled.
|
||||
- `ALLOWED_TYPES`: **.csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
||||
- `ALLOWED_TYPES`: **.cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
||||
- `MAX_SIZE`: **2048**: Maximum size (MB).
|
||||
- `MAX_FILES`: **5**: Maximum number of attachments that can be uploaded at once.
|
||||
- `STORAGE_TYPE`: **local**: Storage type for attachments, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]`
|
||||
|
@ -839,6 +842,10 @@ Default templates for project boards:
|
|||
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when STORAGE_TYPE is `minio`
|
||||
- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio`
|
||||
- `MINIO_BUCKET`: **gitea**: Minio bucket to store the attachments only available when STORAGE_TYPE is `minio`
|
||||
- `MINIO_BUCKET_LOOKUP`: **auto**: Minio bucket lookup type only available when `STORAGE_TYPE` is `minio`
|
||||
- `auto` Auto detect
|
||||
- `dns` Virtual Host Style bucket lookup
|
||||
- `path` Path style bucket lookup
|
||||
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when STORAGE_TYPE is `minio`
|
||||
- `MINIO_BASE_PATH`: **attachments/**: Minio base path on the bucket only available when STORAGE_TYPE is `minio`
|
||||
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when STORAGE_TYPE is `minio`
|
||||
|
@ -1269,6 +1276,10 @@ is `data/lfs` and the default of `MINIO_BASE_PATH` is `lfs/`.
|
|||
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when `STORAGE_TYPE is` `minio`
|
||||
- `MINIO_BUCKET`: **gitea**: Minio bucket to store the lfs only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_BUCKET_LOOKUP`: **auto**: Minio bucket lookup type only available when `STORAGE_TYPE` is `minio`
|
||||
- `auto` Auto detect
|
||||
- `dns` Virtual Host Style bucket lookup
|
||||
- `path` Path style bucket lookup
|
||||
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_BASE_PATH`: **lfs/**: Minio base path on the bucket only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio`
|
||||
|
@ -1284,6 +1295,10 @@ Default storage configuration for attachments, lfs, avatars, repo-avatars, repo-
|
|||
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when `STORAGE_TYPE is` `minio`
|
||||
- `MINIO_BUCKET`: **gitea**: Minio bucket to store the data only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_BUCKET_LOOKUP`: **auto**: Minio bucket lookup type only available when `STORAGE_TYPE` is `minio`
|
||||
- `auto` Auto detect
|
||||
- `dns` Virtual Host Style bucket lookup
|
||||
- `path` Path style bucket lookup
|
||||
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio`
|
||||
|
@ -1369,6 +1384,10 @@ is `data/repo-archive` and the default of `MINIO_BASE_PATH` is `repo-archive/`.
|
|||
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when `STORAGE_TYPE is` `minio`
|
||||
- `MINIO_BUCKET`: **gitea**: Minio bucket to store the lfs only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_BUCKET_LOOKUP`: **auto**: Minio bucket lookup type only available when `STORAGE_TYPE` is `minio`
|
||||
- `auto` Auto detect
|
||||
- `dns` Virtual Host Style bucket lookup
|
||||
- `path` Path style bucket lookup
|
||||
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_BASE_PATH`: **repo-archive/**: Minio base path on the bucket only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio`
|
||||
|
@ -1403,7 +1422,7 @@ PROXY_HOSTS = *.github.com
|
|||
- `ZOMBIE_TASK_TIMEOUT`: **10m**: Timeout to stop the task which have running status, but haven't been updated for a long time
|
||||
- `ENDLESS_TASK_TIMEOUT`: **3h**: Timeout to stop the tasks which have running status and continuous updates, but don't end for a long time
|
||||
- `ABANDONED_JOB_TIMEOUT`: **24h**: Timeout to cancel the jobs which have waiting status, but haven't been picked by a runner for a long time
|
||||
- `SKIP_WORKFLOW_STRINGS`: **[skip ci],[ci skip],[no ci],[skip actions],[actions skip]**: Strings committers can place inside a commit message to skip executing the corresponding actions workflow
|
||||
- `SKIP_WORKFLOW_STRINGS`: **[skip ci],[ci skip],[no ci],[skip actions],[actions skip]**: Strings committers can place inside a commit message or PR title to skip executing the corresponding actions workflow
|
||||
|
||||
`DEFAULT_ACTIONS_URL` indicates where the Gitea Actions runners should find the actions with relative path.
|
||||
For example, `uses: actions/checkout@v4` means `https://github.com/actions/checkout@v4` since the value of `DEFAULT_ACTIONS_URL` is `github`.
|
||||
|
|
|
@ -497,13 +497,17 @@ Gitea 创建以下非唯一队列:
|
|||
|
||||
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**:用户电子邮件通知的默认配置(用户可配置)。选项:enabled、onmention、disabled
|
||||
- `DISABLE_REGULAR_ORG_CREATION`: **false**:禁止普通(非管理员)用户创建组织。
|
||||
- `USER_DISABLED_FEATURES`:**_empty_** 禁用的用户特性,当前允许为空或者 `deletion`,`manage_ssh_keys`, `manage_gpg_keys` 未来可以增加更多设置。
|
||||
- `deletion`: 用户不能通过界面或者API删除他自己。
|
||||
- `manage_ssh_keys`: 用户不能通过界面或者API配置SSH Keys。
|
||||
- `manage_gpg_keys`: 用户不能配置 GPG 密钥。
|
||||
|
||||
## 安全性 (`security`)
|
||||
|
||||
- `INSTALL_LOCK`: **false**:控制是否能够访问安装向导页面,设置为 `true` 则禁止访问安装向导页面。
|
||||
- `SECRET_KEY`: **\<每次安装时随机生成\>**:全局服务器安全密钥。这个密钥非常重要,如果丢失将无法解密加密的数据(例如 2FA)。
|
||||
- `SECRET_KEY_URI`: **_empty_**:与定义 `SECRET_KEY` 不同,此选项可用于使用存储在文件中的密钥(示例值:`file:/etc/gitea/secret_key`)。它不应该像 `SECRET_KEY` 一样容易丢失。
|
||||
- `LOGIN_REMEMBER_DAYS`: **7**:Cookie 保存时间,单位为天。
|
||||
- `LOGIN_REMEMBER_DAYS`: **31**:在要求重新登录之前,记住用户的登录状态多长时间(以天为单位)。
|
||||
- `COOKIE_REMEMBER_NAME`: **gitea\_incredible**:保存自动登录信息的 Cookie 名称。
|
||||
- `REVERSE_PROXY_AUTHENTICATION_USER`: **X-WEBAUTH-USER**:反向代理认证的 HTTP 头部名称,用于提供用户信息。
|
||||
- `REVERSE_PROXY_AUTHENTICATION_EMAIL`: **X-WEBAUTH-EMAIL**:反向代理认证的 HTTP 头部名称,用于提供邮箱信息。
|
||||
|
@ -778,7 +782,7 @@ Gitea 创建以下非唯一队列:
|
|||
## 工单和合并请求的附件 (`attachment`)
|
||||
|
||||
- `ENABLED`: **true**: 是否允许用户上传附件。
|
||||
- `ALLOWED_TYPES`: **.csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip**: 允许的文件扩展名(`.zip`)、mime 类型(`text/plain`)或通配符类型(`image/*`、`audio/*`、`video/*`)的逗号分隔列表。空值或 `*/*` 允许所有类型。
|
||||
- `ALLOWED_TYPES`: **.cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip**: 允许的文件扩展名(`.zip`)、mime 类型(`text/plain`)或通配符类型(`image/*`、`audio/*`、`video/*`)的逗号分隔列表。空值或 `*/*` 允许所有类型。
|
||||
- `MAX_SIZE`: **2048**: 附件的最大限制(MB)。
|
||||
- `MAX_FILES`: **5**: 一次最多上传的附件数量。
|
||||
- `STORAGE_TYPE`: **local**: 附件的存储类型,`local` 表示本地磁盘,`minio` 表示兼容 S3 的对象存储服务,如果未设置将使用默认值 `local` 或其他在 `[storage.xxx]` 中定义的名称。
|
||||
|
@ -788,6 +792,10 @@ Gitea 创建以下非唯一队列:
|
|||
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID 以连接,仅当 STORAGE_TYPE 为 `minio` 时可用。
|
||||
- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey 以连接,仅当 STORAGE_TYPE 为 `minio` 时可用。
|
||||
- `MINIO_BUCKET`: **gitea**: Minio 存储附件的存储桶,仅当 STORAGE_TYPE 为 `minio` 时可用。
|
||||
- `MINIO_BUCKET_LOOKUP`: **auto**: Minio 存储桶寻址方式, 仅当 `STORAGE_TYPE` 为 `minio` 时可用。
|
||||
- `auto` 自动检测
|
||||
- `dns` 子域名寻址
|
||||
- `path` 路径寻址
|
||||
- `MINIO_LOCATION`: **us-east-1**: Minio 存储桶的位置以创建,仅当 STORAGE_TYPE 为 `minio` 时可用。
|
||||
- `MINIO_BASE_PATH`: **attachments/**: Minio 存储桶上的基本路径,仅当 STORAGE_TYPE 为 `minio` 时可用。
|
||||
- `MINIO_USE_SSL`: **false**: Minio 启用 SSL,仅当 STORAGE_TYPE 为 `minio` 时可用。
|
||||
|
@ -1203,6 +1211,10 @@ ALLOW_DATA_URI_IMAGES = true
|
|||
- `MINIO_ACCESS_KEY_ID`:Minio 的 accessKeyID,仅在 `STORAGE_TYPE` 为 `minio` 时可用。
|
||||
- `MINIO_SECRET_ACCESS_KEY`:Minio 的 secretAccessKey,仅在 `STORAGE_TYPE` 为 `minio` 时可用。
|
||||
- `MINIO_BUCKET`:**gitea**:用于存储 lfs 的 Minio 桶,仅在 `STORAGE_TYPE` 为 `minio` 时可用。
|
||||
- `MINIO_BUCKET_LOOKUP`: **auto**: Minio 存储桶寻址方式,可选值为 `auto`, `dns`, `path` 仅当 `STORAGE_TYPE` 为 `minio` 时可用。
|
||||
- `auto` 自动检测
|
||||
- `dns` 子域名寻址
|
||||
- `path` 路径寻址
|
||||
- `MINIO_LOCATION`:**us-east-1**:创建桶的 Minio 位置,仅在 `STORAGE_TYPE` 为 `minio` 时可用。
|
||||
- `MINIO_BASE_PATH`:**lfs/**:桶上的 Minio 基本路径,仅在 `STORAGE_TYPE` 为 `minio` 时可用。
|
||||
- `MINIO_USE_SSL`:**false**:Minio 启用 ssl,仅在 `STORAGE_TYPE` 为 `minio` 时可用。
|
||||
|
@ -1218,6 +1230,10 @@ ALLOW_DATA_URI_IMAGES = true
|
|||
- `MINIO_ACCESS_KEY_ID`:Minio 的 accessKeyID,仅在 `STORAGE_TYPE` 为 `minio` 时可用。
|
||||
- `MINIO_SECRET_ACCESS_KEY`:Minio 的 secretAccessKey,仅在 `STORAGE_TYPE` 为 `minio` 时可用。
|
||||
- `MINIO_BUCKET`:**gitea**:用于存储数据的 Minio 桶,仅在 `STORAGE_TYPE` 为 `minio` 时可用。
|
||||
- `MINIO_BUCKET_LOOKUP`: **auto**: Minio 存储桶寻址方式,可选值为 `auto`, `dns`, `path` 仅当 `STORAGE_TYPE` 为 `minio` 时可用。
|
||||
- `auto` 自动检测
|
||||
- `dns` 子域名寻址
|
||||
- `path` 路径寻址
|
||||
- `MINIO_LOCATION`:**us-east-1**:创建桶的 Minio 位置,仅在 `STORAGE_TYPE` 为 `minio` 时可用。
|
||||
- `MINIO_USE_SSL`:**false**:Minio 启用 ssl,仅在 `STORAGE_TYPE` 为 `minio` 时可用。
|
||||
- `MINIO_INSECURE_SKIP_VERIFY`:**false**:Minio 跳过 SSL 验证,仅在 `STORAGE_TYPE` 为 `minio` 时可用。
|
||||
|
@ -1301,6 +1317,10 @@ MINIO_INSECURE_SKIP_VERIFY = false
|
|||
- `MINIO_ACCESS_KEY_ID`: Minio的accessKeyID,仅在`STORAGE_TYPE`为`minio`时可用。
|
||||
- `MINIO_SECRET_ACCESS_KEY`: Minio的secretAccessKey,仅在`STORAGE_TYPE`为`minio`时可用。
|
||||
- `MINIO_BUCKET`: **gitea**:用于存储归档的Minio存储桶,仅在`STORAGE_TYPE`为`minio`时可用。
|
||||
- `MINIO_BUCKET_LOOKUP`: **auto**: Minio 存储桶寻址方式,可选值为 `auto`, `dns`, `path` 仅当 `STORAGE_TYPE` 为 `minio` 时可用。
|
||||
- `auto` 自动检测
|
||||
- `dns` 子域名寻址
|
||||
- `path` 路径寻址
|
||||
- `MINIO_LOCATION`: **us-east-1**:用于创建存储桶的Minio位置,仅在`STORAGE_TYPE`为`minio`时可用。
|
||||
- `MINIO_BASE_PATH`: **repo-archive/**:存储桶上的Minio基本路径,仅在`STORAGE_TYPE`为`minio`时可用。
|
||||
- `MINIO_USE_SSL`: **false**:启用Minio的SSL,仅在`STORAGE_TYPE`为`minio`时可用。
|
||||
|
|
|
@ -163,7 +163,7 @@ clients don't even support HTML, so they show the text version included in the g
|
|||
|
||||
If the template fails to render, it will be noticed only at the moment the mail is sent.
|
||||
A default subject is used if the subject template fails, and whatever was rendered successfully
|
||||
from the the _mail body_ is used, disregarding the rest.
|
||||
from the _mail body_ is used, disregarding the rest.
|
||||
|
||||
Please check [Gitea's logs](administration/logging-config.md) for error messages in case of trouble.
|
||||
|
||||
|
@ -222,9 +222,9 @@ Please check [Gitea's logs](administration/logging-config.md) for error messages
|
|||
<a href="{{.Link}}">{{.Repo}}#{{.Issue.Index}}</a>.
|
||||
</p>
|
||||
{{if not (eq .Body "")}}
|
||||
<h3>Message content:</h3>
|
||||
<h3>Message content</h3>
|
||||
<hr>
|
||||
{{.Body | Str2html}}
|
||||
{{.Body}}
|
||||
{{end}}
|
||||
</p>
|
||||
<hr>
|
||||
|
@ -245,7 +245,7 @@ This template produces something along these lines:
|
|||
|
||||
> [@rhonda](#) (Rhonda Myers) updated [mike/stuff#38](#).
|
||||
>
|
||||
> #### Message content:
|
||||
> #### Message content
|
||||
>
|
||||
> \_********************************\_********************************
|
||||
>
|
||||
|
@ -259,20 +259,20 @@ This template produces something along these lines:
|
|||
The template system contains several functions that can be used to further process and format
|
||||
the messages. Here's a list of some of them:
|
||||
|
||||
| Name | Parameters | Available | Usage |
|
||||
| ---------------- | ----------- | --------- | --------------------------------------------------------------------------- |
|
||||
| `AppUrl` | - | Any | Gitea's URL |
|
||||
| `AppName` | - | Any | Set from `app.ini`, usually "Gitea" |
|
||||
| `AppDomain` | - | Any | Gitea's host name |
|
||||
| `EllipsisString` | string, int | Any | Truncates a string to the specified length; adds ellipsis as needed |
|
||||
| `Str2html` | string | Body only | Sanitizes text by removing any HTML tags from it. |
|
||||
| `Safe` | string | Body only | Takes the input as HTML; can be used for `.ReviewComments.RenderedContent`. |
|
||||
| Name | Parameters | Available | Usage |
|
||||
| ---------------- | ----------- | --------- | ------------------------------------------------------------------- |
|
||||
| `AppUrl` | - | Any | Gitea's URL |
|
||||
| `AppName` | - | Any | Set from `app.ini`, usually "Gitea" |
|
||||
| `AppDomain` | - | Any | Gitea's host name |
|
||||
| `EllipsisString` | string, int | Any | Truncates a string to the specified length; adds ellipsis as needed |
|
||||
| `SanitizeHTML` | string | Body only | Sanitizes text by removing any dangerous HTML tags from it |
|
||||
| `SafeHTML` | string | Body only | Takes the input as HTML, can be used for outputing raw HTML content |
|
||||
|
||||
These are _functions_, not metadata, so they have to be used:
|
||||
|
||||
```html
|
||||
Like this: {{Str2html "Escape<my>text"}}
|
||||
Or this: {{"Escape<my>text" | Str2html}}
|
||||
Like this: {{SanitizeHTML "Escape<my>text"}}
|
||||
Or this: {{"Escape<my>text" | SanitizeHTML}}
|
||||
Or this: {{AppUrl}}
|
||||
But not like this: {{.AppUrl}}
|
||||
```
|
||||
|
|
|
@ -207,7 +207,7 @@ _主题_ 和 _邮件正文_ 由 [Golang的模板引擎](https://go.dev/pkg/text/
|
|||
{{if not (eq .Body "")}}
|
||||
<h3>消息内容:</h3>
|
||||
<hr>
|
||||
{{.Body | Str2html}}
|
||||
{{.Body}}
|
||||
{{end}}
|
||||
</p>
|
||||
<hr>
|
||||
|
@ -228,7 +228,7 @@ _主题_ 和 _邮件正文_ 由 [Golang的模板引擎](https://go.dev/pkg/text/
|
|||
|
||||
> [@rhonda](#)(Rhonda Myers)更新了 [mike/stuff#38](#)。
|
||||
>
|
||||
> #### 消息内容:
|
||||
> #### 消息内容
|
||||
>
|
||||
> \_********************************\_********************************
|
||||
>
|
||||
|
@ -242,20 +242,20 @@ _主题_ 和 _邮件正文_ 由 [Golang的模板引擎](https://go.dev/pkg/text/
|
|||
|
||||
模板系统包含一些函数,可用于进一步处理和格式化消息。以下是其中一些函数的列表:
|
||||
|
||||
| 函数名 | 参数 | 可用于 | 用法 |
|
||||
| ----------------- | ----------- | ------------ | --------------------------------------------------------------------------------- |
|
||||
| `AppUrl` | - | 任何地方 | Gitea 的 URL |
|
||||
| `AppName` | - | 任何地方 | 从 `app.ini` 中设置,通常为 "Gitea" |
|
||||
| `AppDomain` | - | 任何地方 | Gitea 的主机名 |
|
||||
| `EllipsisString` | string, int | 任何地方 | 将字符串截断为指定长度;根据需要添加省略号 |
|
||||
| `Str2html` | string | 仅正文部分 | 通过删除其中的 HTML 标签对文本进行清理 |
|
||||
| `Safe` | string | 仅正文部分 | 将输入作为 HTML 处理;可用于 `.ReviewComments.RenderedContent` 等字段 |
|
||||
| 函数名 | 参数 | 可用于 | 用法 |
|
||||
|------------------| ----------- | ------------ | ------------------------------ |
|
||||
| `AppUrl` | - | 任何地方 | Gitea 的 URL |
|
||||
| `AppName` | - | 任何地方 | 从 `app.ini` 中设置,通常为 "Gitea" |
|
||||
| `AppDomain` | - | 任何地方 | Gitea 的主机名 |
|
||||
| `EllipsisString` | string, int | 任何地方 | 将字符串截断为指定长度;根据需要添加省略号 |
|
||||
| `SanitizeHTML` | string | 仅正文部分 | 通过删除其中的危险 HTML 标签对文本进行清理 |
|
||||
| `SafeHTML` | string | 仅正文部分 | 将输入作为 HTML 处理;可用于输出原始的 HTML 内容 |
|
||||
|
||||
这些都是 _函数_,而不是元数据,因此必须按以下方式使用:
|
||||
|
||||
```html
|
||||
像这样使用: {{Str2html "Escape<my>text"}}
|
||||
或者这样使用: {{"Escape<my>text" | Str2html}}
|
||||
像这样使用: {{SanitizeHTML "Escape<my>text"}}
|
||||
或者这样使用: {{"Escape<my>text" | SanitizeHTML}}
|
||||
或者这样使用: {{AppUrl}}
|
||||
但不要像这样使用: {{.AppUrl}}
|
||||
```
|
||||
|
|
|
@ -17,6 +17,12 @@ menu:
|
|||
|
||||
# Repository indexer
|
||||
|
||||
## Builtin repository code search without indexer
|
||||
|
||||
Users could do repository-level code search without setting up a repository indexer.
|
||||
The builtin code search is based on the `git grep` command, which is fast and efficient for small repositories.
|
||||
Better code search support could be achieved by setting up the repository indexer.
|
||||
|
||||
## Setting up the repository indexer
|
||||
|
||||
Gitea can search through the files of the repositories by enabling this function in your [`app.ini`](administration/config-cheat-sheet.md):
|
||||
|
|
|
@ -101,6 +101,10 @@ i.e. `services/user`, `models/repository`.
|
|||
Since there are some packages which use the same package name, it is possible that you find packages like `modules/user`, `models/user`, and `services/user`. When these packages are imported in one Go file, it's difficult to know which package we are using and if it's a variable name or an import name. So, we always recommend to use import aliases. To differ from package variables which are commonly in camelCase, just use **snake_case** for import aliases.
|
||||
i.e. `import user_service "code.gitea.io/gitea/services/user"`
|
||||
|
||||
### Implementing `io.Closer`
|
||||
|
||||
If a type implements `io.Closer`, calling `Close` multiple times must not fail or `panic` but return an error or `nil`.
|
||||
|
||||
### Important Gotchas
|
||||
|
||||
- Never write `x.Update(exemplar)` without an explicit `WHERE` clause:
|
||||
|
|
|
@ -47,7 +47,7 @@ We recommend [Google HTML/CSS Style Guide](https://google.github.io/styleguide/h
|
|||
9. Avoid unnecessary `!important` in CSS, add comments to explain why it's necessary if it can't be avoided.
|
||||
10. Avoid mixing different events in one event listener, prefer to use individual event listeners for every event.
|
||||
11. Custom event names are recommended to use `ce-` prefix.
|
||||
12. Gitea's tailwind-style CSS classes use `gt-` prefix (`gt-relative`), while Gitea's own private framework-level CSS classes use `g-` prefix (`g-modal-confirm`).
|
||||
12. Prefer using Tailwind CSS which is available via `tw-` prefix, e.g. `tw-relative`. Gitea's helper CSS classes use `gt-` prefix (`gt-word-break`), while Gitea's own private framework-level CSS classes use `g-` prefix (`g-modal-confirm`).
|
||||
13. Avoid inline scripts & styles as much as possible, it's recommended to put JS code into JS files and use CSS classes. If inline scripts & styles are unavoidable, explain the reason why it can't be avoided.
|
||||
|
||||
### Accessibility / ARIA
|
||||
|
@ -118,7 +118,7 @@ However, there are still some special cases, so the current guideline is:
|
|||
### Show/Hide Elements
|
||||
|
||||
* Vue components are recommended to use `v-if` and `v-show` to show/hide elements.
|
||||
* Go template code should use Gitea's `.gt-hidden` and `showElem()/hideElem()/toggleElem()`, see more details in `.gt-hidden`'s comment.
|
||||
* Go template code should use `.tw-hidden` and `showElem()/hideElem()/toggleElem()`, see more details in `.tw-hidden`'s comment.
|
||||
|
||||
### Styles and Attributes in Go HTML Template
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ HTML 页面由[Go HTML Template](https://pkg.go.dev/html/template)渲染。
|
|||
|
||||
我们推荐使用[Google HTML/CSS Style Guide](https://google.github.io/styleguide/htmlcssguide.html)和[Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html)。
|
||||
|
||||
## Gitea 特定准则:
|
||||
## Gitea 特定准则
|
||||
|
||||
1. 每个功能(Fomantic-UI/jQuery 模块)应放在单独的文件/目录中。
|
||||
2. HTML 的 id 和 class 应使用 kebab-case,最好包含2-3个与功能相关的关键词。
|
||||
|
@ -47,7 +47,8 @@ HTML 页面由[Go HTML Template](https://pkg.go.dev/html/template)渲染。
|
|||
9. 避免在 CSS 中使用不必要的`!important`,如果无法避免,添加注释解释为什么需要它。
|
||||
10. 避免在一个事件监听器中混合不同的事件,优先为每个事件使用独立的事件监听器。
|
||||
11. 推荐使用自定义事件名称前缀`ce-`。
|
||||
12. Gitea 的 tailwind-style CSS 类使用`gt-`前缀(`gt-relative`),而 Gitea 自身的私有框架级 CSS 类使用`g-`前缀(`g-modal-confirm`)。
|
||||
12. 建议使用 Tailwind CSS,它可以通过 `tw-` 前缀获得,例如 `tw-relative`. Gitea 自身的助手类 CSS 使用 `gt-` 前缀(`gt-word-break`),Gitea 自身的私有框架级 CSS 类使用 `g-` 前缀(`g-modal-confirm`)。
|
||||
13. 尽量避免内联脚本和样式,建议将JS代码放入JS文件中并使用CSS类。如果内联脚本和样式不可避免,请解释无法避免的原因。
|
||||
|
||||
### 可访问性 / ARIA
|
||||
|
||||
|
@ -64,18 +65,21 @@ Gitea使用一些补丁使Fomantic UI更具可访问性(参见`aria.js`和`ari
|
|||
|
||||
* Vue + Vanilla JS
|
||||
* Fomantic-UI(jQuery)
|
||||
* htmx (部分页面重新加载其他静态组件)
|
||||
* Vanilla JS
|
||||
|
||||
不推荐的实现方式:
|
||||
|
||||
* Vue + Fomantic-UI(jQuery)
|
||||
* jQuery + Vanilla JS
|
||||
* htmx + 任何其他需要大量 JavaScript 代码或不必要的功能,如 htmx 脚本 (`hx-on`)
|
||||
|
||||
为了保持界面一致,Vue 组件可以使用 Fomantic-UI 的 CSS 类。
|
||||
尽管不建议混合使用不同的框架,
|
||||
我们使用 htmx 进行简单的交互。您可以在此 [PR](https://github.com/go-gitea/gitea/pull/28908) 中查看一个简单交互的示例,其中应使用 htmx。如果您需要更高级的反应性,请不要使用 htmx,请使用其他框架(Vue/Vanilla JS)。
|
||||
但如果混合使用是必要的,并且代码设计良好且易于维护,也可以工作。
|
||||
|
||||
### async 函数
|
||||
### `async` 函数
|
||||
|
||||
只有当函数内部存在`await`调用或返回`Promise`时,才将函数标记为`async`。
|
||||
|
||||
|
@ -91,6 +95,12 @@ Gitea使用一些补丁使Fomantic UI更具可访问性(参见`aria.js`和`ari
|
|||
这是有意为之的,我们想调用异步函数并忽略Promise。
|
||||
一些 lint 规则和 IDE 也会在未处理返回的 Promise 时发出警告。
|
||||
|
||||
### 获取数据
|
||||
|
||||
要获取数据,请使用`modules/fetch.js`中的包装函数`GET`、`POST`等。他们
|
||||
接受内容的`data`选项,将自动设置 CSRF 令牌并返回
|
||||
[Response](https://developer.mozilla.org/en-US/docs/Web/API/Response)。
|
||||
|
||||
### HTML 属性和 dataset
|
||||
|
||||
禁止使用`dataset`,它的驼峰命名行为使得搜索属性变得困难。
|
||||
|
@ -107,7 +117,7 @@ Gitea使用一些补丁使Fomantic UI更具可访问性(参见`aria.js`和`ari
|
|||
### 显示/隐藏元素
|
||||
|
||||
* 推荐在Vue组件中使用`v-if`和`v-show`来显示/隐藏元素。
|
||||
* Go 模板代码应使用 Gitea 的 `.gt-hidden` 和 `showElem()/hideElem()/toggleElem()` 来显示/隐藏元素,请参阅`.gt-hidden`的注释以获取更多详细信息。
|
||||
* Go 模板代码应使用 `.tw-hidden` 和 `showElem()/hideElem()/toggleElem()` 来显示/隐藏元素,请参阅`.tw-hidden`的注释以获取更多详细信息。
|
||||
|
||||
### Go HTML 模板中的样式和属性
|
||||
|
||||
|
@ -132,3 +142,7 @@ Gitea使用一些补丁使Fomantic UI更具可访问性(参见`aria.js`和`ari
|
|||
### Vue3 和 JSX
|
||||
|
||||
Gitea 现在正在使用 Vue3。我们决定不引入 JSX,以保持 HTML 代码和 JavaScript 代码分离。
|
||||
|
||||
### UI示例
|
||||
|
||||
Gitea 使用一些自制的 UI 元素并自定义其他元素,以将它们更好地集成到通用 UI 方法中。当在开发模式(`RUN_MODE=dev`)下运行 Gitea 时,在 `http(s)://your-gitea-url:port/devtest` 下会提供一个包含一些标准化 UI 示例的页面。
|
||||
|
|
|
@ -214,7 +214,7 @@ REPO_INDEXER_CONN_STR = http://elastic:changeme@localhost:9200
|
|||
|
||||
### Building and adding SVGs
|
||||
|
||||
SVG icons are built using the `make svg` target which compiles the icon sources defined in `build/generate-svg.js` into the output directory `public/assets/img/svg`. Custom icons can be added in the `web_src/svg` directory.
|
||||
SVG icons are built using the `make svg` target which compiles the icon sources into the output directory `public/assets/img/svg`. Custom icons can be added in the `web_src/svg` directory.
|
||||
|
||||
### Building the Logo
|
||||
|
||||
|
@ -333,14 +333,9 @@ Documentation for the website is found in `docs/`. If you change this you
|
|||
can test your changes to ensure that they pass continuous integration using:
|
||||
|
||||
```bash
|
||||
# from the docs directory within Gitea
|
||||
make trans-copy clean build
|
||||
make lint-md
|
||||
```
|
||||
|
||||
You will require a copy of [Hugo](https://gohugo.io/) to run this task. Please
|
||||
note: this may generate a number of untracked Git objects, which will need to
|
||||
be cleaned up.
|
||||
|
||||
## Visual Studio Code
|
||||
|
||||
A `launch.json` and `tasks.json` are provided within `contrib/ide/vscode` for
|
||||
|
|
|
@ -201,7 +201,7 @@ REPO_INDEXER_CONN_STR = http://elastic:changeme@localhost:9200
|
|||
|
||||
### 构建和添加 SVGs
|
||||
|
||||
SVG 图标是使用 `make svg` 目标构建的,该目标将 `build/generate-svg.js` 中定义的图标源编译到输出目录 `public/img/svg` 中。可以在 `web_src/svg` 目录中添加自定义图标。
|
||||
SVG 图标是使用 `make svg` 命令构建的,该命令将图标资源编译到输出目录 `public/assets/img/svg` 中。可以在 `web_src/svg` 目录中添加自定义图标。
|
||||
|
||||
### 构建 Logo
|
||||
|
||||
|
@ -307,13 +307,9 @@ TAGS="bindata sqlite sqlite_unlock_notify" make build test-sqlite
|
|||
该网站的文档位于 `docs/` 中。如果你改变了文档内容,你可以使用以下测试方法进行持续集成:
|
||||
|
||||
```bash
|
||||
# 来自 Gitea 中的 docs 目录
|
||||
make trans-copy clean build
|
||||
make lint-md
|
||||
```
|
||||
|
||||
运行此任务依赖于 [Hugo](https://gohugo.io/)。请注意:这可能会生成一些未跟踪的 Git 对象,
|
||||
需要被清理干净。
|
||||
|
||||
## Visual Studio Code
|
||||
|
||||
`contrib/ide/vscode` 中为 Visual Studio Code 提供了 `launch.json` 和 `tasks.json`。查看
|
||||
|
|
|
@ -87,6 +87,9 @@ _Symbols used in table:_
|
|||
| Git Blame | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Visual comparison of image changes | ✓ | ✘ | ✓ | ? | ? | ? | ✘ | ✘ |
|
||||
|
||||
- Gitea has builtin repository-level code search
|
||||
- Better code search support could be achieved by [using a repository indexer](administration/repo-indexer.md)
|
||||
|
||||
## Issue Tracker
|
||||
|
||||
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | RhodeCode EE |
|
||||
|
|
|
@ -135,6 +135,12 @@ body:
|
|||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
# some markdown that will only be visible once the issue has been created
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
This issue was created by an issue **template** :)
|
||||
visible: [content]
|
||||
- type: input
|
||||
id: contact
|
||||
attributes:
|
||||
|
@ -186,11 +192,16 @@ body:
|
|||
options:
|
||||
- label: I agree to follow this project's Code of Conduct
|
||||
required: true
|
||||
- label: I have also read the CONTRIBUTION.MD
|
||||
required: true
|
||||
visible: [form]
|
||||
- label: This is a TODO only visible after issue creation
|
||||
visible: [content]
|
||||
```
|
||||
|
||||
### Markdown
|
||||
|
||||
You can use a `markdown` element to display Markdown in your form that provides extra context to the user, but is not submitted.
|
||||
You can use a `markdown` element to display Markdown in your form that provides extra context to the user, but is not submitted by default.
|
||||
|
||||
Attributes:
|
||||
|
||||
|
@ -198,6 +209,8 @@ Attributes:
|
|||
|-------|--------------------------------------------------------------|----------|--------|---------|--------------|
|
||||
| value | The text that is rendered. Markdown formatting is supported. | Required | String | - | - |
|
||||
|
||||
visible: Default is **[form]**
|
||||
|
||||
### Textarea
|
||||
|
||||
You can use a `textarea` element to add a multi-line text field to your form. Contributors can also attach files in `textarea` fields.
|
||||
|
@ -218,6 +231,8 @@ Validations:
|
|||
|----------|------------------------------------------------------|----------|---------|---------|--------------|
|
||||
| required | Prevents form submission until element is completed. | Optional | Boolean | false | - |
|
||||
|
||||
visible: Default is **[form, content]**
|
||||
|
||||
### Input
|
||||
|
||||
You can use an `input` element to add a single-line text field to your form.
|
||||
|
@ -239,6 +254,8 @@ Validations:
|
|||
| is_number | Prevents form submission until element is filled with a number. | Optional | Boolean | false | - |
|
||||
| regex | Prevents form submission until element is filled with a value that match the regular expression. | Optional | String | - | a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) |
|
||||
|
||||
visible: Default is **[form, content]**
|
||||
|
||||
### Dropdown
|
||||
|
||||
You can use a `dropdown` element to add a dropdown menu in your form.
|
||||
|
@ -258,6 +275,8 @@ Validations:
|
|||
|----------|------------------------------------------------------|----------|---------|---------|--------------|
|
||||
| required | Prevents form submission until element is completed. | Optional | Boolean | false | - |
|
||||
|
||||
visible: Default is **[form, content]**
|
||||
|
||||
### Checkboxes
|
||||
|
||||
You can use the `checkboxes` element to add a set of checkboxes to your form.
|
||||
|
@ -265,17 +284,20 @@ You can use the `checkboxes` element to add a set of checkboxes to your form.
|
|||
Attributes:
|
||||
|
||||
| Key | Description | Required | Type | Default | Valid values |
|
||||
|-------------|-------------------------------------------------------------------------------------------------------|----------|--------|--------------|--------------|
|
||||
| ----------- | ----------------------------------------------------------------------------------------------------- | -------- | ------ | ------------ | ------------ |
|
||||
| label | A brief description of the expected user input, which is displayed in the form. | Required | String | - | - |
|
||||
| description | A description of the set of checkboxes, which is displayed in the form. Supports Markdown formatting. | Optional | String | Empty String | - |
|
||||
| options | An array of checkboxes that the user can select. For syntax, see below. | Required | Array | - | - |
|
||||
|
||||
For each value in the options array, you can set the following keys.
|
||||
|
||||
| Key | Description | Required | Type | Default | Options |
|
||||
|----------|------------------------------------------------------------------------------------------------------------------------------------------|----------|---------|---------|---------|
|
||||
| label | The identifier for the option, which is displayed in the form. Markdown is supported for bold or italic text formatting, and hyperlinks. | Required | String | - | - |
|
||||
| required | Prevents form submission until element is completed. | Optional | Boolean | false | - |
|
||||
| Key | Description | Required | Type | Default | Options |
|
||||
|--------------|------------------------------------------------------------------------------------------------------------------------------------------|----------|--------------|---------|---------|
|
||||
| label | The identifier for the option, which is displayed in the form. Markdown is supported for bold or italic text formatting, and hyperlinks. | Required | String | - | - |
|
||||
| required | Prevents form submission until element is completed. | Optional | Boolean | false | - |
|
||||
| visible | Whether a specific checkbox appears in the form only, in the created issue only, or both. Valid options are "form" and "content". | Optional | String array | false | - |
|
||||
|
||||
visible: Default is **[form, content]**
|
||||
|
||||
## Syntax for issue config
|
||||
|
||||
|
@ -291,15 +313,15 @@ contact_links:
|
|||
|
||||
### Possible Options
|
||||
|
||||
| Key | Description | Type | Default |
|
||||
|----------------------|-------------------------------------------------------------------------------------------------------|--------------------|----------------|
|
||||
| blank_issues_enabled | If set to false, the User is forced to use a Template | Boolean | true |
|
||||
| contact_links | Custom Links to show in the Choose Box | Contact Link Array | Empty Array |
|
||||
| Key | Description | Type | Default |
|
||||
|----------------------|-------------------------------------------------------|--------------------|-------------|
|
||||
| blank_issues_enabled | If set to false, the User is forced to use a Template | Boolean | true |
|
||||
| contact_links | Custom Links to show in the Choose Box | Contact Link Array | Empty Array |
|
||||
|
||||
### Contact Link
|
||||
|
||||
| Key | Description | Type | Required |
|
||||
|----------------------|-------------------------------------------------------------------------------------------------------|---------|----------|
|
||||
| name | the name of your link | String | true |
|
||||
| url | The URL of your Link | String | true |
|
||||
| about | A short description of your Link | String | true |
|
||||
| Key | Description | Type | Required |
|
||||
|-------|----------------------------------|--------|----------|
|
||||
| name | the name of your link | String | true |
|
||||
| url | The URL of your Link | String | true |
|
||||
| about | A short description of your Link | String | true |
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
#
|
||||
# This script is used to copy the en-US content to our available locales as a
|
||||
# fallback to always show all pages when displaying a specific locale that is
|
||||
# missing some documents to be translated.
|
||||
#
|
||||
# Just execute the script without any argument and you will get the missing
|
||||
# files copied into the content folder. We are calling this script within the CI
|
||||
# server simply by `make trans-copy`.
|
||||
#
|
||||
|
||||
declare -a LOCALES=(
|
||||
"fr-fr"
|
||||
"nl-nl"
|
||||
"pt-br"
|
||||
"zh-cn"
|
||||
"zh-tw"
|
||||
)
|
||||
|
||||
ROOT=$(realpath $(dirname $0)/..)
|
||||
|
||||
for SOURCE in $(find ${ROOT}/content -type f -iname *.en-us.md); do
|
||||
for LOCALE in "${LOCALES[@]}"; do
|
||||
DEST="${SOURCE%.en-us.md}.${LOCALE}.md"
|
||||
|
||||
if [[ ! -f ${DEST} ]]; then
|
||||
cp ${SOURCE} ${DEST}
|
||||
sed -i.bak "s/en\-us/${LOCALE}/g" ${DEST}
|
||||
rm ${DEST}.bak
|
||||
fi
|
||||
done
|
||||
done
|
54
go.mod
54
go.mod
|
@ -1,25 +1,24 @@
|
|||
module code.gitea.io/gitea
|
||||
|
||||
go 1.21
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
code.gitea.io/actions-proto-go v0.3.1
|
||||
code.gitea.io/actions-proto-go v0.4.0
|
||||
code.gitea.io/gitea-vet v0.2.3
|
||||
code.gitea.io/sdk/gitea v0.17.1
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
||||
connectrpc.com/connect v1.15.0
|
||||
gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669
|
||||
gitea.com/go-chi/cache v0.2.0
|
||||
gitea.com/go-chi/captcha v0.0.0-20230415143339-2c0754df4384
|
||||
gitea.com/go-chi/session v0.0.0-20230613035928-39541325faa3
|
||||
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
|
||||
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96
|
||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
|
||||
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
||||
github.com/PuerkitoBio/goquery v1.8.1
|
||||
github.com/alecthomas/chroma/v2 v2.12.0
|
||||
github.com/alecthomas/chroma/v2 v2.13.0
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||
github.com/blevesearch/bleve/v2 v2.3.10
|
||||
github.com/bufbuild/connect-go v1.10.0
|
||||
github.com/buildkite/terminal-to-html/v3 v3.10.1
|
||||
github.com/caddyserver/certmagic v0.20.0
|
||||
github.com/chi-middleware/proxy v1.1.1
|
||||
|
@ -29,33 +28,33 @@ require (
|
|||
github.com/djherbis/nio/v3 v3.0.1
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.0
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.1
|
||||
github.com/emersion/go-imap v1.2.1
|
||||
github.com/emirpasic/gods v1.18.1
|
||||
github.com/felixge/fgprof v0.9.3
|
||||
github.com/felixge/fgprof v0.9.4
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/gliderlabs/ssh v0.3.6
|
||||
github.com/gliderlabs/ssh v0.3.7
|
||||
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9
|
||||
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
|
||||
github.com/go-chi/chi/v5 v5.0.11
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-co-op/gocron v1.37.0
|
||||
github.com/go-enry/go-enry/v2 v2.8.6
|
||||
github.com/go-enry/go-enry/v2 v2.8.7
|
||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e
|
||||
github.com/go-git/go-billy/v5 v5.5.0
|
||||
github.com/go-git/go-git/v5 v5.11.0
|
||||
github.com/go-ldap/ldap/v3 v3.4.6
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/go-swagger/go-swagger v0.30.5
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.9.0
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.10.0
|
||||
github.com/go-webauthn/webauthn v0.10.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0
|
||||
github.com/google/go-github/v57 v57.0.0
|
||||
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/feeds v1.1.2
|
||||
github.com/gorilla/sessions v1.2.2
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
|
@ -66,22 +65,22 @@ require (
|
|||
github.com/json-iterator/go v1.1.12
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4
|
||||
github.com/klauspost/compress v1.17.4
|
||||
github.com/klauspost/compress v1.17.7
|
||||
github.com/klauspost/cpuid/v2 v2.2.6
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/markbates/goth v1.78.0
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mattn/go-sqlite3 v1.14.19
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/meilisearch/meilisearch-go v0.26.1
|
||||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/microcosm-cc/bluemonday v1.0.26
|
||||
github.com/minio/minio-go/v7 v7.0.66
|
||||
github.com/minio/minio-go/v7 v7.0.69
|
||||
github.com/msteinert/pam v1.2.0
|
||||
github.com/nektos/act v0.2.52
|
||||
github.com/niklasfasching/go-org v1.7.0
|
||||
github.com/olivere/elastic/v7 v7.0.32
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.0-rc6
|
||||
github.com/opencontainers/image-spec v1.1.0
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/prometheus/client_golang v1.18.0
|
||||
github.com/quasoft/websspi v1.1.2
|
||||
|
@ -101,15 +100,15 @@ require (
|
|||
github.com/yuin/goldmark v1.6.0
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
github.com/yuin/goldmark-meta v1.1.0
|
||||
golang.org/x/crypto v0.18.0
|
||||
golang.org/x/crypto v0.19.0
|
||||
golang.org/x/image v0.15.0
|
||||
golang.org/x/net v0.20.0
|
||||
golang.org/x/net v0.21.0
|
||||
golang.org/x/oauth2 v0.16.0
|
||||
golang.org/x/sys v0.16.0
|
||||
golang.org/x/sys v0.17.0
|
||||
golang.org/x/text v0.14.0
|
||||
golang.org/x/tools v0.17.0
|
||||
google.golang.org/grpc v1.60.1
|
||||
google.golang.org/protobuf v1.32.0
|
||||
google.golang.org/protobuf v1.33.0
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
|
@ -123,9 +122,10 @@ require (
|
|||
cloud.google.com/go/compute v1.23.3 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
|
||||
github.com/ClickHouse/ch-go v0.61.1 // indirect
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.17.1 // indirect
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.18.0 // indirect
|
||||
github.com/DataDog/zstd v1.5.5 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
|
@ -168,7 +168,7 @@ require (
|
|||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.0 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
|
@ -238,7 +238,7 @@ require (
|
|||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/paulmach/orb v0.11.0 // indirect
|
||||
github.com/paulmach/orb v0.11.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
|
@ -298,7 +298,7 @@ replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
|
|||
|
||||
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
|
||||
|
||||
replace github.com/nektos/act => gitea.com/gitea/act v0.2.51
|
||||
replace github.com/nektos/act => gitea.com/gitea/act v0.259.1
|
||||
|
||||
replace github.com/gorilla/feeds => github.com/yardenshoham/feeds v0.0.0-20240110072658-f3d0c21c0bd5
|
||||
|
||||
|
|
146
go.sum
146
go.sum
|
@ -35,32 +35,33 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
|||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
code.gitea.io/actions-proto-go v0.3.1 h1:PMyiQtBKb8dNnpEO2R5rcZdXSis+UQZVo/SciMtR1aU=
|
||||
code.gitea.io/actions-proto-go v0.3.1/go.mod h1:00ys5QDo1iHN1tHNvvddAcy2W/g+425hQya1cCSvq9A=
|
||||
code.gitea.io/actions-proto-go v0.4.0 h1:OsPBPhodXuQnsspG1sQ4eRE1PeoZyofd7+i73zCwnsU=
|
||||
code.gitea.io/actions-proto-go v0.4.0/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas=
|
||||
code.gitea.io/gitea-vet v0.2.3 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI=
|
||||
code.gitea.io/gitea-vet v0.2.3/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
|
||||
code.gitea.io/sdk/gitea v0.17.1 h1:3jCPOG2ojbl8AcfaUCRYLT5MUcBMFwS0OSK2mA5Zok8=
|
||||
code.gitea.io/sdk/gitea v0.17.1/go.mod h1:aCnBqhHpoEWA180gMbaCtdX9Pl6BWBAuuP2miadoTNM=
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY=
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM=
|
||||
connectrpc.com/connect v1.15.0 h1:lFdeCbZrVVDydAqwr4xGV2y+ULn+0Z73s5JBj2LikWo=
|
||||
connectrpc.com/connect v1.15.0/go.mod h1:bQmjpDY8xItMnttnurVgOkHUBMRT9cpsNi2O4AjKhmA=
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg=
|
||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
|
||||
gitea.com/gitea/act v0.2.51 h1:gXc/B4OlTciTTzAx9cmNyw04n2SDO7exPjAsR5Idu+c=
|
||||
gitea.com/gitea/act v0.2.51/go.mod h1:CoaX2053jqBlD6JMgu4d4UgFL/rp2I14Kt5mMqcs0Z0=
|
||||
gitea.com/gitea/act v0.259.1 h1:8GG1o/xtUHl3qjn5f0h/2FXrT5ubBn05TJOM5ry+FBw=
|
||||
gitea.com/gitea/act v0.259.1/go.mod h1:UxZWRYqQG2Yj4+4OqfGWW5a3HELwejyWFQyU7F1jUD8=
|
||||
gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669 h1:RUBX+MK/TsDxpHmymaOaydfigEbbzqUnG1OTZU/HAeo=
|
||||
gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc=
|
||||
gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0=
|
||||
gitea.com/go-chi/cache v0.2.0 h1:E0npuTfDW6CT1yD8NMDVc1SK6IeRjfmRL2zlEsCEd7w=
|
||||
gitea.com/go-chi/cache v0.2.0/go.mod h1:iQlVK2aKTZ/rE9UcHyz9pQWGvdP9i1eI2spOpzgCrtE=
|
||||
gitea.com/go-chi/captcha v0.0.0-20230415143339-2c0754df4384 h1:klh0LjhH7l4CuJkxlCM//o3rWLvWqxUpFxEtoYg5TNY=
|
||||
gitea.com/go-chi/captcha v0.0.0-20230415143339-2c0754df4384/go.mod h1:hQ9SYHKdOX968wJglb/NMQ+UqpOKwW4L+EYdvkWjHSo=
|
||||
gitea.com/go-chi/session v0.0.0-20230613035928-39541325faa3 h1:4FuO+MahrkDjdjVIS8ExmY9FEHTZS8TPheEm4uU5xLI=
|
||||
gitea.com/go-chi/session v0.0.0-20230613035928-39541325faa3/go.mod h1:fc/pjt5EqNKgqQXYzcas1Z5L5whkZHyOvTA7OzWVJck=
|
||||
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:+wWBi6Qfruqu7xJgjOIrKVQGiLUZdpKYCZewJ4clqhw=
|
||||
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:VyMQP6ue6MKHM8UsOXfNfuMKD0oSAWZdXVcpHIN2yaY=
|
||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR98IP2KZQKRzJJiV7aTeMAFwaWo=
|
||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098/go.mod h1:LjzIOHlRemuUyO7WR12fmm18VZIlCAaOt9L3yKw40pk=
|
||||
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96 h1:IFDiMBObsP6CZIRaDLd54SR6zPYAffPXiXck5Xslu0Q=
|
||||
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96/go.mod h1:0iEpFKnwO5dG0aF98O4eq6FMsAiXkNBaDIlUOlq4BtM=
|
||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHqdhS7keYWioqfmxdnfblFDTGoOwcZ+o=
|
||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||
|
@ -78,8 +79,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
|||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/ClickHouse/ch-go v0.61.1 h1:j5rx3qnvcnYjhnP1IdXE/vdIRQiqgwAzyqOaasA6QCw=
|
||||
github.com/ClickHouse/ch-go v0.61.1/go.mod h1:myxt/JZgy2BYHFGQqzmaIpbfr5CMbs3YHVULaWQj5YU=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.17.1 h1:ZCmAYWpu75IyEi7+Yrs/uaAjiCGY5wfW5kXo64exkX4=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.17.1/go.mod h1:rkGTvFDTLqLIm0ma+13xmcCfr/08Gvs7KmFt1tgiWHQ=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.18.0 h1:O1LicIeg2JS2V29fKRH4+yT3f6jvvcJBm506dpVQ4mQ=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.18.0/go.mod h1:ztQvX6wm7kAbhJslS87EXEhOVNY/TObXwyURnGju5FQ=
|
||||
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
|
||||
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||
|
@ -99,14 +100,14 @@ github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAc
|
|||
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||
github.com/RoaringBitmap/roaring v1.7.0 h1:OZF303tJCER1Tj3x+aArx/S5X7hrT186ri6JjrGvG68=
|
||||
github.com/RoaringBitmap/roaring v1.7.0/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
|
||||
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
|
||||
github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
|
||||
github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
|
||||
github.com/alecthomas/assert/v2 v2.6.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.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF7cmWgw=
|
||||
github.com/alecthomas/chroma/v2 v2.12.0/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
|
||||
github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI=
|
||||
github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk=
|
||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
||||
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA=
|
||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
|
@ -173,8 +174,6 @@ 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=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bufbuild/connect-go v1.10.0 h1:QAJ3G9A1OYQW2Jbk3DeoJbkCxuKArrvZgDt47mjdTbg=
|
||||
github.com/bufbuild/connect-go v1.10.0/go.mod h1:CAIePUgkDR5pAFaylSMtNK45ANQjp9JvpluG20rhpV8=
|
||||
github.com/buildkite/terminal-to-html/v3 v3.10.1 h1:znT9eD26LQ59dDJJEpMCwkP4wEptEAPi74hsTBuHdEo=
|
||||
github.com/buildkite/terminal-to-html/v3 v3.10.1/go.mod h1:qtuRyYs6/Sw3FS9jUyVEaANHgHGqZsGqMknPLyau5cQ=
|
||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
|
@ -183,26 +182,28 @@ github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NY
|
|||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chi-middleware/proxy v1.1.1 h1:4HaXUp8o2+bhHr1OhVy+VjN0+L7/07JDcn6v7YrTjrQ=
|
||||
github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdikvbVJVHv/M+0=
|
||||
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
|
||||
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A=
|
||||
github.com/couchbase/go-couchbase v0.1.1 h1:ClFXELcKj/ojyoTYbsY34QUrrYCBi/1G749sXSCkdhk=
|
||||
github.com/couchbase/go-couchbase v0.1.1/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A=
|
||||
github.com/couchbase/gomemcached v0.1.1/go.mod h1:mxliKQxOv84gQ0bJWbI+w9Wxdpt9HjDvgW9MjCym5Vo=
|
||||
github.com/couchbase/gomemcached v0.3.0 h1:XkMDdP6w7rtvLijDE0/RhcccX+XvAk5cboyBv1YcI0U=
|
||||
github.com/couchbase/gomemcached v0.3.0/go.mod h1:mxliKQxOv84gQ0bJWbI+w9Wxdpt9HjDvgW9MjCym5Vo=
|
||||
github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
|
||||
github.com/couchbase/goutils v0.1.2 h1:gWr8B6XNWPIhfalHNog3qQKfGiYyh4K4VhO3P2o9BCs=
|
||||
github.com/couchbase/goutils v0.1.2/go.mod h1:h89Ek/tiOxxqjz30nPPlwZdQbdB8BwgnuBxeoUe/ViE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
|
||||
|
@ -233,16 +234,16 @@ github.com/djherbis/nio/v3 v3.0.1/go.mod h1:Ng4h80pbZFMla1yKzm61cF0tqqilXZYrogmW
|
|||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.0 h1:5O8paxMLmi/5ONoKXzWNYxoSZU7+ITVbGcPga0IrzfE=
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.0/go.mod h1:hdTKe+hwa3mMnMn4JUQziT+yc3pF+6EVmK2LPbLZthE=
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.1 h1:iPCqofzMO41WVbcS/B5Ym7AwHQg9cyQ7Ie/R2XU5L3A=
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.1/go.mod h1:VY4oyqUnpULFB3SCRpl24GFDIN1PmfiQIvN/G4ScSNg=
|
||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
|
@ -261,8 +262,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
|||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||
github.com/felixge/fgprof v0.9.4 h1:ocDNwMFlnA0NU0zSB3I52xkO4sFXk80VK9lXjLClu88=
|
||||
github.com/felixge/fgprof v0.9.4/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||
|
@ -275,8 +276,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
|
|||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
|
||||
github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||
github.com/gliderlabs/ssh v0.3.6 h1:ZzjlDa05TcFRICb3anf/dSPN3ewz1Zx6CMLPWgkm3b8=
|
||||
github.com/gliderlabs/ssh v0.3.6/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
||||
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9 h1:j2TrkUG/NATGi/EQS+MvEoF79CxiRUmT16ErFroNcKI=
|
||||
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9/go.mod h1:cJ9Ye0ZNSMN7RzZDBRY3E+8M3Bpf/R1JX22Ir9yX6WI=
|
||||
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 h1:I2nuhyVI/48VXoRCCZR2hYBgnSXa+EuDJf/VyX06TC0=
|
||||
|
@ -293,8 +294,8 @@ github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
|||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
|
||||
github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
|
||||
github.com/go-enry/go-enry/v2 v2.8.6 h1:T6ljs5+qNiUTDqpfK5GUD5EvLNdDbf804u8iC30vw7U=
|
||||
github.com/go-enry/go-enry/v2 v2.8.6/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
|
||||
github.com/go-enry/go-enry/v2 v2.8.7 h1:vbab0pcf5Yo1cHQLzbWZ+QomUh3EfEU8EiR5n7W0lnQ=
|
||||
github.com/go-enry/go-enry/v2 v2.8.7/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-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
|
||||
|
@ -340,10 +341,9 @@ github.com/go-openapi/swag v0.22.7/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmrid
|
|||
github.com/go-openapi/validate v0.22.6 h1:+NhuwcEYpWdO5Nm4bmvhGLW0rt1Fcc532Mu3wpypXfo=
|
||||
github.com/go-openapi/validate v0.22.6/go.mod h1:eaddXSqKeTg5XpSmj1dYyFTK/95n/XHwcOY+BMxKMyM=
|
||||
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-redis/redis/v8 v8.4.0/go.mod h1:A1tbYoHSa1fXwN+//ljcCYYJeLmVrwL9hbQN45Jdy0M=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-swagger/go-swagger v0.30.5 h1:SQ2+xSonWjjoEMOV5tcOnZJVlfyUfCBhGQGArS1b9+U=
|
||||
github.com/go-swagger/go-swagger v0.30.5/go.mod h1:cWUhSyCNqV7J1wkkxfr5QmbcnCewetCdvEXqgPvbc/Q=
|
||||
github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0=
|
||||
|
@ -351,14 +351,17 @@ github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.m
|
|||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
|
||||
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.9.0 h1:938g5V+GWLVejm3Hc+nWCuEXRlcglZDDlN/t1gWzcSY=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.9.0/go.mod h1:cdsKD2ApFBjdog9jRsz6EJqF+LClq/hrwE9K/1Dzo4s=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.10.0 h1:BrBwN7AuC+74g5qtk9D59TLGOaEa8Bw1WmIsf+SyzWc=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.10.0/go.mod h1:z8RoleoNtibi6Ar8ziCW7e6PQ+jWiqbUWvuv8AMe4lo=
|
||||
github.com/go-webauthn/webauthn v0.10.0 h1:yuW2e1tXnRAwAvKrR4q4LQmc6XtCMH639/ypZGhZCwk=
|
||||
github.com/go-webauthn/webauthn v0.10.0/go.mod h1:l0NiauXhL6usIKqNLCUM3Qir43GK7ORg8ggold0Uv/Y=
|
||||
github.com/go-webauthn/x v0.1.6 h1:QNAX+AWeqRt9loE8mULeWJCqhVG5D/jvdmJ47fIWCkQ=
|
||||
github.com/go-webauthn/x v0.1.6/go.mod h1:W8dFVZ79o4f+nY1eOUICy/uq5dhrRl7mxQkYhXTo0FA=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
github.com/goccy/go-json v0.9.5/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
|
@ -425,7 +428,6 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
|
@ -448,15 +450,14 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf
|
|||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 h1:WzfWbQz/Ze8v6l++GGbGNFZnUShVpP/0xffCPLL+ax8=
|
||||
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
|
@ -501,7 +502,7 @@ github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq
|
|||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
|
@ -555,8 +556,8 @@ github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs
|
|||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
||||
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
|
@ -574,6 +575,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
||||
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
|
||||
github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
|
||||
|
@ -607,8 +609,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
|
|||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
|
||||
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/meilisearch/meilisearch-go v0.26.1 h1:3bmo2uLijX7kvBmiZ9LupVfC95TFcRJDgrRTzbOoE4A=
|
||||
github.com/meilisearch/meilisearch-go v0.26.1/go.mod h1:SxuSqDcPBIykjWz1PX+KzsYzArNLSCadQodWs8extS0=
|
||||
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
||||
|
@ -621,8 +623,8 @@ github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
|||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.66 h1:bnTOXOHjOqv/gcMuiVbN9o2ngRItvqE774dG9nq0Dzw=
|
||||
github.com/minio/minio-go/v7 v7.0.66/go.mod h1:DHAgmyQEGdW3Cif0UooKOyrT3Vxs82zNdV6tkKhRtbs=
|
||||
github.com/minio/minio-go/v7 v7.0.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0=
|
||||
github.com/minio/minio-go/v7 v7.0.69/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ=
|
||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
|
@ -665,22 +667,21 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
|||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc6/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/paulmach/orb v0.11.0 h1:JfVXJUBeH9ifc/OrhBY0lL16QsmPgpCHMlqSSYhcgAA=
|
||||
github.com/paulmach/orb v0.11.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
|
||||
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
|
||||
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
|
||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||
|
@ -867,7 +868,6 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
|||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/otel v0.14.0/go.mod h1:vH5xEuwy7Rts0GNtsCW3HYQoZDY+OmBJ6t1bFGGlxgw=
|
||||
go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y=
|
||||
go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI=
|
||||
go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0=
|
||||
|
@ -898,8 +898,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
|
|||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -967,7 +967,6 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
|
|||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
|
@ -981,8 +980,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -1035,7 +1034,6 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -1046,9 +1044,9 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -1061,8 +1059,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
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=
|
||||
|
@ -1073,8 +1071,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
|||
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.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -1174,7 +1172,6 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
|||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
|
@ -1239,8 +1236,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
|||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -1253,7 +1250,6 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
|
|||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||
gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
|
|
|
@ -26,6 +26,8 @@ const (
|
|||
ArtifactStatusUploadConfirmed // 2, ArtifactStatusUploadConfirmed is the status of an artifact upload that is confirmed
|
||||
ArtifactStatusUploadError // 3, ArtifactStatusUploadError is the status of an artifact upload that is errored
|
||||
ArtifactStatusExpired // 4, ArtifactStatusExpired is the status of an artifact that is expired
|
||||
ArtifactStatusPendingDeletion // 5, ArtifactStatusPendingDeletion is the status of an artifact that is pending deletion
|
||||
ArtifactStatusDeleted // 6, ArtifactStatusDeleted is the status of an artifact that is deleted
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -147,8 +149,28 @@ func ListNeedExpiredArtifacts(ctx context.Context) ([]*ActionArtifact, error) {
|
|||
Where("expired_unix < ? AND status = ?", timeutil.TimeStamp(time.Now().Unix()), ArtifactStatusUploadConfirmed).Find(&arts)
|
||||
}
|
||||
|
||||
// ListPendingDeleteArtifacts returns all artifacts in pending-delete status.
|
||||
// limit is the max number of artifacts to return.
|
||||
func ListPendingDeleteArtifacts(ctx context.Context, limit int) ([]*ActionArtifact, error) {
|
||||
arts := make([]*ActionArtifact, 0, limit)
|
||||
return arts, db.GetEngine(ctx).
|
||||
Where("status = ?", ArtifactStatusPendingDeletion).Limit(limit).Find(&arts)
|
||||
}
|
||||
|
||||
// SetArtifactExpired sets an artifact to expired
|
||||
func SetArtifactExpired(ctx context.Context, artifactID int64) error {
|
||||
_, err := db.GetEngine(ctx).Where("id=? AND status = ?", artifactID, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusExpired)})
|
||||
return err
|
||||
}
|
||||
|
||||
// SetArtifactNeedDelete sets an artifact to need-delete, cron job will delete it
|
||||
func SetArtifactNeedDelete(ctx context.Context, runID int64, name string) error {
|
||||
_, err := db.GetEngine(ctx).Where("run_id=? AND artifact_name=? AND status = ?", runID, name, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusPendingDeletion)})
|
||||
return err
|
||||
}
|
||||
|
||||
// SetArtifactDeleted sets an artifact to deleted
|
||||
func SetArtifactDeleted(ctx context.Context, artifactID int64) error {
|
||||
_, err := db.GetEngine(ctx).ID(artifactID).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusDeleted)})
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -170,14 +170,16 @@ func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) err
|
|||
return err
|
||||
}
|
||||
|
||||
// CancelRunningJobs cancels all running and waiting jobs associated with a specific workflow.
|
||||
func CancelRunningJobs(ctx context.Context, repoID int64, ref, workflowID string) error {
|
||||
// Find all runs in the specified repository, reference, and workflow with statuses 'Running' or 'Waiting'.
|
||||
// CancelPreviousJobs cancels all previous jobs of the same repository, reference, workflow, and event.
|
||||
// It's useful when a new run is triggered, and all previous runs needn't be continued anymore.
|
||||
func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) error {
|
||||
// Find all runs in the specified repository, reference, and workflow with non-final status
|
||||
runs, total, err := db.FindAndCount[ActionRun](ctx, FindRunOptions{
|
||||
RepoID: repoID,
|
||||
Ref: ref,
|
||||
WorkflowID: workflowID,
|
||||
Status: []Status{StatusRunning, StatusWaiting},
|
||||
RepoID: repoID,
|
||||
Ref: ref,
|
||||
WorkflowID: workflowID,
|
||||
TriggerEvent: event,
|
||||
Status: []Status{StatusRunning, StatusWaiting, StatusBlocked},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
@ -71,6 +72,7 @@ type FindRunOptions struct {
|
|||
WorkflowID string
|
||||
Ref string // the commit/tag/… that caused this workflow
|
||||
TriggerUserID int64
|
||||
TriggerEvent webhook_module.HookEventType
|
||||
Approved bool // not util.OptionalBool, it works only when it's true
|
||||
Status []Status
|
||||
}
|
||||
|
@ -98,6 +100,9 @@ func (opts FindRunOptions) ToConds() builder.Cond {
|
|||
if opts.Ref != "" {
|
||||
cond = cond.And(builder.Eq{"ref": opts.Ref})
|
||||
}
|
||||
if opts.TriggerEvent != "" {
|
||||
cond = cond.And(builder.Eq{"trigger_event": opts.TriggerEvent})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/shared/types"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
@ -159,7 +160,7 @@ type FindRunnerOptions struct {
|
|||
OwnerID int64
|
||||
Sort string
|
||||
Filter string
|
||||
IsOnline util.OptionalBool
|
||||
IsOnline optional.Option[bool]
|
||||
WithAvailable bool // not only runners belong to, but also runners can be used
|
||||
}
|
||||
|
||||
|
@ -186,10 +187,12 @@ func (opts FindRunnerOptions) ToConds() builder.Cond {
|
|||
cond = cond.And(builder.Like{"name", opts.Filter})
|
||||
}
|
||||
|
||||
if opts.IsOnline.IsTrue() {
|
||||
cond = cond.And(builder.Gt{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
|
||||
} else if opts.IsOnline.IsFalse() {
|
||||
cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
|
||||
if opts.IsOnline.Has() {
|
||||
if opts.IsOnline.Value() {
|
||||
cond = cond.And(builder.Gt{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
|
||||
} else {
|
||||
cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
|
||||
}
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ package actions
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
|
@ -118,3 +119,22 @@ func DeleteScheduleTaskByRepo(ctx context.Context, id int64) error {
|
|||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) error {
|
||||
// If actions disabled when there is schedule task, this will remove the outdated schedule tasks
|
||||
// There is no other place we can do this because the app.ini will be changed manually
|
||||
if err := DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
|
||||
return fmt.Errorf("DeleteCronTaskByRepo: %v", err)
|
||||
}
|
||||
// cancel running cron jobs of this repository and delete old schedules
|
||||
if err := CancelPreviousJobs(
|
||||
ctx,
|
||||
repo.ID,
|
||||
repo.DefaultBranch,
|
||||
"",
|
||||
webhook_module.HookEventSchedule,
|
||||
); err != nil {
|
||||
return fmt.Errorf("CancelPreviousJobs: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
|
@ -82,3 +83,35 @@ func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error)
|
|||
})
|
||||
return count != 0, err
|
||||
}
|
||||
|
||||
func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, error) {
|
||||
variables := map[string]string{}
|
||||
|
||||
// Global
|
||||
globalVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{})
|
||||
if err != nil {
|
||||
log.Error("find global variables: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Org / User level
|
||||
ownerVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{OwnerID: run.Repo.OwnerID})
|
||||
if err != nil {
|
||||
log.Error("find variables of org: %d, error: %v", run.Repo.OwnerID, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Repo level
|
||||
repoVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{RepoID: run.RepoID})
|
||||
if err != nil {
|
||||
log.Error("find variables of repo: %d, error: %v", run.RepoID, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Level precedence: Repo > Org / User > Global
|
||||
for _, v := range append(globalVariables, append(ownerVariables, repoVariables...)...) {
|
||||
variables[v.Name] = v.Data
|
||||
}
|
||||
|
||||
return variables, nil
|
||||
}
|
||||
|
|
|
@ -150,6 +150,7 @@ type Action struct {
|
|||
Repo *repo_model.Repository `xorm:"-"`
|
||||
CommentID int64 `xorm:"INDEX"`
|
||||
Comment *issues_model.Comment `xorm:"-"`
|
||||
Issue *issues_model.Issue `xorm:"-"` // get the issue id from content
|
||||
IsDeleted bool `xorm:"NOT NULL DEFAULT false"`
|
||||
RefName string
|
||||
IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
||||
|
@ -227,8 +228,8 @@ func (a *Action) ShortActUserName(ctx context.Context) string {
|
|||
return base.EllipsisString(a.GetActUserName(ctx), 20)
|
||||
}
|
||||
|
||||
// GetDisplayName gets the action's display name based on DEFAULT_SHOW_FULL_NAME, or falls back to the username if it is blank.
|
||||
func (a *Action) GetDisplayName(ctx context.Context) string {
|
||||
// GetActDisplayName gets the action's display name based on DEFAULT_SHOW_FULL_NAME, or falls back to the username if it is blank.
|
||||
func (a *Action) GetActDisplayName(ctx context.Context) string {
|
||||
if setting.UI.DefaultShowFullName {
|
||||
trimmedFullName := strings.TrimSpace(a.GetActFullName(ctx))
|
||||
if len(trimmedFullName) > 0 {
|
||||
|
@ -238,8 +239,8 @@ func (a *Action) GetDisplayName(ctx context.Context) string {
|
|||
return a.ShortActUserName(ctx)
|
||||
}
|
||||
|
||||
// GetDisplayNameTitle gets the action's display name used for the title (tooltip) based on DEFAULT_SHOW_FULL_NAME
|
||||
func (a *Action) GetDisplayNameTitle(ctx context.Context) string {
|
||||
// GetActDisplayNameTitle gets the action's display name used for the title (tooltip) based on DEFAULT_SHOW_FULL_NAME
|
||||
func (a *Action) GetActDisplayNameTitle(ctx context.Context) string {
|
||||
if setting.UI.DefaultShowFullName {
|
||||
return a.ShortActUserName(ctx)
|
||||
}
|
||||
|
@ -292,11 +293,6 @@ func (a *Action) GetRepoAbsoluteLink(ctx context.Context) string {
|
|||
return setting.AppURL + url.PathEscape(a.GetRepoUserName(ctx)) + "/" + url.PathEscape(a.GetRepoName(ctx))
|
||||
}
|
||||
|
||||
// GetCommentHTMLURL returns link to action comment.
|
||||
func (a *Action) GetCommentHTMLURL(ctx context.Context) string {
|
||||
return a.getCommentHTMLURL(ctx)
|
||||
}
|
||||
|
||||
func (a *Action) loadComment(ctx context.Context) (err error) {
|
||||
if a.CommentID == 0 || a.Comment != nil {
|
||||
return nil
|
||||
|
@ -305,7 +301,8 @@ func (a *Action) loadComment(ctx context.Context) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
func (a *Action) getCommentHTMLURL(ctx context.Context) string {
|
||||
// GetCommentHTMLURL returns link to action comment.
|
||||
func (a *Action) GetCommentHTMLURL(ctx context.Context) string {
|
||||
if a == nil {
|
||||
return "#"
|
||||
}
|
||||
|
@ -313,34 +310,19 @@ func (a *Action) getCommentHTMLURL(ctx context.Context) string {
|
|||
if a.Comment != nil {
|
||||
return a.Comment.HTMLURL(ctx)
|
||||
}
|
||||
if len(a.GetIssueInfos()) == 0 {
|
||||
|
||||
if err := a.LoadIssue(ctx); err != nil || a.Issue == nil {
|
||||
return "#"
|
||||
}
|
||||
// Return link to issue
|
||||
issueIDString := a.GetIssueInfos()[0]
|
||||
issueID, err := strconv.ParseInt(issueIDString, 10, 64)
|
||||
if err != nil {
|
||||
if err := a.Issue.LoadRepo(ctx); err != nil {
|
||||
return "#"
|
||||
}
|
||||
|
||||
issue, err := issues_model.GetIssueByID(ctx, issueID)
|
||||
if err != nil {
|
||||
return "#"
|
||||
}
|
||||
|
||||
if err = issue.LoadRepo(ctx); err != nil {
|
||||
return "#"
|
||||
}
|
||||
|
||||
return issue.HTMLURL()
|
||||
return a.Issue.HTMLURL()
|
||||
}
|
||||
|
||||
// GetCommentLink returns link to action comment.
|
||||
func (a *Action) GetCommentLink(ctx context.Context) string {
|
||||
return a.getCommentLink(ctx)
|
||||
}
|
||||
|
||||
func (a *Action) getCommentLink(ctx context.Context) string {
|
||||
if a == nil {
|
||||
return "#"
|
||||
}
|
||||
|
@ -348,26 +330,15 @@ func (a *Action) getCommentLink(ctx context.Context) string {
|
|||
if a.Comment != nil {
|
||||
return a.Comment.Link(ctx)
|
||||
}
|
||||
if len(a.GetIssueInfos()) == 0 {
|
||||
|
||||
if err := a.LoadIssue(ctx); err != nil || a.Issue == nil {
|
||||
return "#"
|
||||
}
|
||||
// Return link to issue
|
||||
issueIDString := a.GetIssueInfos()[0]
|
||||
issueID, err := strconv.ParseInt(issueIDString, 10, 64)
|
||||
if err != nil {
|
||||
if err := a.Issue.LoadRepo(ctx); err != nil {
|
||||
return "#"
|
||||
}
|
||||
|
||||
issue, err := issues_model.GetIssueByID(ctx, issueID)
|
||||
if err != nil {
|
||||
return "#"
|
||||
}
|
||||
|
||||
if err = issue.LoadRepo(ctx); err != nil {
|
||||
return "#"
|
||||
}
|
||||
|
||||
return issue.Link()
|
||||
return a.Issue.Link()
|
||||
}
|
||||
|
||||
// GetBranch returns the action's repository branch.
|
||||
|
@ -395,33 +366,66 @@ func (a *Action) GetCreate() time.Time {
|
|||
return a.CreatedUnix.AsTime()
|
||||
}
|
||||
|
||||
// GetIssueInfos returns a list of issues associated with
|
||||
// the action.
|
||||
func (a *Action) IsIssueEvent() bool {
|
||||
return a.OpType.InActions("comment_issue", "approve_pull_request", "reject_pull_request", "comment_pull", "merge_pull_request")
|
||||
}
|
||||
|
||||
// GetIssueInfos returns a list of associated information with the action.
|
||||
func (a *Action) GetIssueInfos() []string {
|
||||
return strings.SplitN(a.Content, "|", 3)
|
||||
// make sure it always returns 3 elements, because there are some access to the a[1] and a[2] without checking the length
|
||||
ret := strings.SplitN(a.Content, "|", 3)
|
||||
for len(ret) < 3 {
|
||||
ret = append(ret, "")
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (a *Action) getIssueIndex() int64 {
|
||||
infos := a.GetIssueInfos()
|
||||
if len(infos) == 0 {
|
||||
return 0
|
||||
}
|
||||
index, _ := strconv.ParseInt(infos[0], 10, 64)
|
||||
return index
|
||||
}
|
||||
|
||||
func (a *Action) LoadIssue(ctx context.Context) error {
|
||||
if a.Issue != nil {
|
||||
return nil
|
||||
}
|
||||
if index := a.getIssueIndex(); index > 0 {
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, a.RepoID, index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.Issue = issue
|
||||
a.Issue.Repo = a.Repo
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetIssueTitle returns the title of first issue associated with the action.
|
||||
func (a *Action) GetIssueTitle(ctx context.Context) string {
|
||||
index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64)
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, a.RepoID, index)
|
||||
if err != nil {
|
||||
log.Error("GetIssueByIndex: %v", err)
|
||||
return "500 when get issue"
|
||||
if err := a.LoadIssue(ctx); err != nil {
|
||||
log.Error("LoadIssue: %v", err)
|
||||
return "<500 when get issue>"
|
||||
}
|
||||
return issue.Title
|
||||
if a.Issue == nil {
|
||||
return "<Issue not found>"
|
||||
}
|
||||
return a.Issue.Title
|
||||
}
|
||||
|
||||
// GetIssueContent returns the content of first issue associated with
|
||||
// this action.
|
||||
// GetIssueContent returns the content of first issue associated with this action.
|
||||
func (a *Action) GetIssueContent(ctx context.Context) string {
|
||||
index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64)
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, a.RepoID, index)
|
||||
if err != nil {
|
||||
log.Error("GetIssueByIndex: %v", err)
|
||||
return "500 when get issue"
|
||||
if err := a.LoadIssue(ctx); err != nil {
|
||||
log.Error("LoadIssue: %v", err)
|
||||
return "<500 when get issue>"
|
||||
}
|
||||
return issue.Content
|
||||
if a.Issue == nil {
|
||||
return "<Content not found>"
|
||||
}
|
||||
return a.Issue.Content
|
||||
}
|
||||
|
||||
// GetFeedsOptions options for retrieving feeds
|
||||
|
@ -461,7 +465,7 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
|
|||
return nil, 0, fmt.Errorf("FindAndCount: %w", err)
|
||||
}
|
||||
|
||||
if err := ActionList(actions).loadAttributes(ctx); err != nil {
|
||||
if err := ActionList(actions).LoadAttributes(ctx); err != nil {
|
||||
return nil, 0, fmt.Errorf("LoadAttributes: %w", err)
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,16 @@ package activities
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// ActionList defines a list of actions
|
||||
|
@ -24,7 +29,7 @@ func (actions ActionList) getUserIDs() []int64 {
|
|||
return userIDs.Values()
|
||||
}
|
||||
|
||||
func (actions ActionList) loadUsers(ctx context.Context) (map[int64]*user_model.User, error) {
|
||||
func (actions ActionList) LoadActUsers(ctx context.Context) (map[int64]*user_model.User, error) {
|
||||
if len(actions) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -52,7 +57,7 @@ func (actions ActionList) getRepoIDs() []int64 {
|
|||
return repoIDs.Values()
|
||||
}
|
||||
|
||||
func (actions ActionList) loadRepositories(ctx context.Context) error {
|
||||
func (actions ActionList) LoadRepositories(ctx context.Context) error {
|
||||
if len(actions) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
@ -63,11 +68,11 @@ func (actions ActionList) loadRepositories(ctx context.Context) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("find repository: %w", err)
|
||||
}
|
||||
|
||||
for _, action := range actions {
|
||||
action.Repo = repoMaps[action.RepoID]
|
||||
}
|
||||
return nil
|
||||
repos := repo_model.RepositoryList(util.ValuesOfMap(repoMaps))
|
||||
return repos.LoadUnits(ctx)
|
||||
}
|
||||
|
||||
func (actions ActionList) loadRepoOwner(ctx context.Context, userMap map[int64]*user_model.User) (err error) {
|
||||
|
@ -75,37 +80,124 @@ func (actions ActionList) loadRepoOwner(ctx context.Context, userMap map[int64]*
|
|||
userMap = make(map[int64]*user_model.User)
|
||||
}
|
||||
|
||||
userSet := make(container.Set[int64], len(actions))
|
||||
for _, action := range actions {
|
||||
if action.Repo == nil {
|
||||
continue
|
||||
}
|
||||
repoOwner, ok := userMap[action.Repo.OwnerID]
|
||||
if !ok {
|
||||
repoOwner, err = user_model.GetUserByID(ctx, action.Repo.OwnerID)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
userMap[repoOwner.ID] = repoOwner
|
||||
if _, ok := userMap[action.Repo.OwnerID]; !ok {
|
||||
userSet.Add(action.Repo.OwnerID)
|
||||
}
|
||||
}
|
||||
|
||||
if err := db.GetEngine(ctx).
|
||||
In("id", userSet.Values()).
|
||||
Find(&userMap); err != nil {
|
||||
return fmt.Errorf("find user: %w", err)
|
||||
}
|
||||
|
||||
for _, action := range actions {
|
||||
if action.Repo != nil {
|
||||
action.Repo.Owner = userMap[action.Repo.OwnerID]
|
||||
}
|
||||
action.Repo.Owner = repoOwner
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadAttributes loads all attributes
|
||||
func (actions ActionList) loadAttributes(ctx context.Context) error {
|
||||
userMap, err := actions.loadUsers(ctx)
|
||||
// LoadAttributes loads all attributes
|
||||
func (actions ActionList) LoadAttributes(ctx context.Context) error {
|
||||
// the load sequence cannot be changed because of the dependencies
|
||||
userMap, err := actions.LoadActUsers(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := actions.loadRepositories(ctx); err != nil {
|
||||
if err := actions.LoadRepositories(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return actions.loadRepoOwner(ctx, userMap)
|
||||
if err := actions.loadRepoOwner(ctx, userMap); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := actions.LoadIssues(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return actions.LoadComments(ctx)
|
||||
}
|
||||
|
||||
func (actions ActionList) LoadComments(ctx context.Context) error {
|
||||
if len(actions) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
commentIDs := make([]int64, 0, len(actions))
|
||||
for _, action := range actions {
|
||||
if action.CommentID > 0 {
|
||||
commentIDs = append(commentIDs, action.CommentID)
|
||||
}
|
||||
}
|
||||
|
||||
commentsMap := make(map[int64]*issues_model.Comment, len(commentIDs))
|
||||
if err := db.GetEngine(ctx).In("id", commentIDs).Find(&commentsMap); err != nil {
|
||||
return fmt.Errorf("find comment: %w", err)
|
||||
}
|
||||
|
||||
for _, action := range actions {
|
||||
if action.CommentID > 0 {
|
||||
action.Comment = commentsMap[action.CommentID]
|
||||
if action.Comment != nil {
|
||||
action.Comment.Issue = action.Issue
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (actions ActionList) LoadIssues(ctx context.Context) error {
|
||||
if len(actions) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
conditions := builder.NewCond()
|
||||
issueNum := 0
|
||||
for _, action := range actions {
|
||||
if action.IsIssueEvent() {
|
||||
infos := action.GetIssueInfos()
|
||||
if len(infos) == 0 {
|
||||
continue
|
||||
}
|
||||
index, _ := strconv.ParseInt(infos[0], 10, 64)
|
||||
if index > 0 {
|
||||
conditions = conditions.Or(builder.Eq{
|
||||
"repo_id": action.RepoID,
|
||||
"`index`": index,
|
||||
})
|
||||
issueNum++
|
||||
}
|
||||
}
|
||||
}
|
||||
if !conditions.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
issuesMap := make(map[string]*issues_model.Issue, issueNum)
|
||||
issues := make([]*issues_model.Issue, 0, issueNum)
|
||||
if err := db.GetEngine(ctx).Where(conditions).Find(&issues); err != nil {
|
||||
return fmt.Errorf("find issue: %w", err)
|
||||
}
|
||||
for _, issue := range issues {
|
||||
issuesMap[fmt.Sprintf("%d-%d", issue.RepoID, issue.Index)] = issue
|
||||
}
|
||||
|
||||
for _, action := range actions {
|
||||
if !action.IsIssueEvent() {
|
||||
continue
|
||||
}
|
||||
if index := action.getIssueIndex(); index > 0 {
|
||||
if issue, ok := issuesMap[fmt.Sprintf("%d-%d", action.RepoID, index)]; ok {
|
||||
action.Issue = issue
|
||||
action.Issue.Repo = action.Repo
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -12,14 +12,11 @@ import (
|
|||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
@ -79,53 +76,6 @@ func init() {
|
|||
db.RegisterModel(new(Notification))
|
||||
}
|
||||
|
||||
// FindNotificationOptions represent the filters for notifications. If an ID is 0 it will be ignored.
|
||||
type FindNotificationOptions struct {
|
||||
db.ListOptions
|
||||
UserID int64
|
||||
RepoID int64
|
||||
IssueID int64
|
||||
Status []NotificationStatus
|
||||
Source []NotificationSource
|
||||
UpdatedAfterUnix int64
|
||||
UpdatedBeforeUnix int64
|
||||
}
|
||||
|
||||
// ToCond will convert each condition into a xorm-Cond
|
||||
func (opts FindNotificationOptions) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.UserID != 0 {
|
||||
cond = cond.And(builder.Eq{"notification.user_id": opts.UserID})
|
||||
}
|
||||
if opts.RepoID != 0 {
|
||||
cond = cond.And(builder.Eq{"notification.repo_id": opts.RepoID})
|
||||
}
|
||||
if opts.IssueID != 0 {
|
||||
cond = cond.And(builder.Eq{"notification.issue_id": opts.IssueID})
|
||||
}
|
||||
if len(opts.Status) > 0 {
|
||||
if len(opts.Status) == 1 {
|
||||
cond = cond.And(builder.Eq{"notification.status": opts.Status[0]})
|
||||
} else {
|
||||
cond = cond.And(builder.In("notification.status", opts.Status))
|
||||
}
|
||||
}
|
||||
if len(opts.Source) > 0 {
|
||||
cond = cond.And(builder.In("notification.source", opts.Source))
|
||||
}
|
||||
if opts.UpdatedAfterUnix != 0 {
|
||||
cond = cond.And(builder.Gte{"notification.updated_unix": opts.UpdatedAfterUnix})
|
||||
}
|
||||
if opts.UpdatedBeforeUnix != 0 {
|
||||
cond = cond.And(builder.Lte{"notification.updated_unix": opts.UpdatedBeforeUnix})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
func (opts FindNotificationOptions) ToOrders() string {
|
||||
return "notification.updated_unix DESC"
|
||||
}
|
||||
|
||||
// CreateRepoTransferNotification creates notification for the user a repository was transferred to
|
||||
func CreateRepoTransferNotification(ctx context.Context, doer, newOwner *user_model.User, repo *repo_model.Repository) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
|
@ -159,118 +109,6 @@ func CreateRepoTransferNotification(ctx context.Context, doer, newOwner *user_mo
|
|||
})
|
||||
}
|
||||
|
||||
// CreateOrUpdateIssueNotifications creates an issue notification
|
||||
// for each watcher, or updates it if already exists
|
||||
// receiverID > 0 just send to receiver, else send to all watcher
|
||||
func CreateOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
if err := createOrUpdateIssueNotifications(ctx, issueID, commentID, notificationAuthorID, receiverID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error {
|
||||
// init
|
||||
var toNotify container.Set[int64]
|
||||
notifications, err := db.Find[Notification](ctx, FindNotificationOptions{
|
||||
IssueID: issueID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
issue, err := issues_model.GetIssueByID(ctx, issueID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if receiverID > 0 {
|
||||
toNotify = make(container.Set[int64], 1)
|
||||
toNotify.Add(receiverID)
|
||||
} else {
|
||||
toNotify = make(container.Set[int64], 32)
|
||||
issueWatches, err := issues_model.GetIssueWatchersIDs(ctx, issueID, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
toNotify.AddMultiple(issueWatches...)
|
||||
if !(issue.IsPull && issues_model.HasWorkInProgressPrefix(issue.Title)) {
|
||||
repoWatches, err := repo_model.GetRepoWatchersIDs(ctx, issue.RepoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
toNotify.AddMultiple(repoWatches...)
|
||||
}
|
||||
issueParticipants, err := issue.GetParticipantIDsByIssue(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
toNotify.AddMultiple(issueParticipants...)
|
||||
|
||||
// dont notify user who cause notification
|
||||
delete(toNotify, notificationAuthorID)
|
||||
// explicit unwatch on issue
|
||||
issueUnWatches, err := issues_model.GetIssueWatchersIDs(ctx, issueID, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, id := range issueUnWatches {
|
||||
toNotify.Remove(id)
|
||||
}
|
||||
|
||||
// Remove users who have the notification author blocked.
|
||||
blockedAuthorIDs, err := user_model.ListBlockedByUsersID(ctx, notificationAuthorID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, id := range blockedAuthorIDs {
|
||||
toNotify.Remove(id)
|
||||
}
|
||||
}
|
||||
|
||||
err = issue.LoadRepo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// notify
|
||||
for userID := range toNotify {
|
||||
issue.Repo.Units = nil
|
||||
user, err := user_model.GetUserByID(ctx, userID)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
if issue.IsPull && !access_model.CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypePullRequests) {
|
||||
continue
|
||||
}
|
||||
if !issue.IsPull && !access_model.CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypeIssues) {
|
||||
continue
|
||||
}
|
||||
|
||||
if notificationExists(notifications, issue.ID, userID) {
|
||||
if err = updateIssueNotification(ctx, userID, issue.ID, commentID, notificationAuthorID); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err = createIssueNotification(ctx, userID, issue, commentID, notificationAuthorID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createIssueNotification(ctx context.Context, userID int64, issue *issues_model.Issue, commentID, updatedByID int64) error {
|
||||
notification := &Notification{
|
||||
UserID: userID,
|
||||
|
@ -458,309 +296,6 @@ func GetUIDsAndNotificationCounts(ctx context.Context, since, until timeutil.Tim
|
|||
return res, db.GetEngine(ctx).SQL(sql, since, until, NotificationStatusUnread).Find(&res)
|
||||
}
|
||||
|
||||
// NotificationList contains a list of notifications
|
||||
type NotificationList []*Notification
|
||||
|
||||
// LoadAttributes load Repo Issue User and Comment if not loaded
|
||||
func (nl NotificationList) LoadAttributes(ctx context.Context) error {
|
||||
if _, _, err := nl.LoadRepos(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := nl.LoadIssues(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := nl.LoadUsers(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := nl.LoadComments(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nl NotificationList) getPendingRepoIDs() []int64 {
|
||||
ids := make(container.Set[int64], len(nl))
|
||||
for _, notification := range nl {
|
||||
if notification.Repository != nil {
|
||||
continue
|
||||
}
|
||||
ids.Add(notification.RepoID)
|
||||
}
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
// LoadRepos loads repositories from database
|
||||
func (nl NotificationList) LoadRepos(ctx context.Context) (repo_model.RepositoryList, []int, error) {
|
||||
if len(nl) == 0 {
|
||||
return repo_model.RepositoryList{}, []int{}, nil
|
||||
}
|
||||
|
||||
repoIDs := nl.getPendingRepoIDs()
|
||||
repos := make(map[int64]*repo_model.Repository, len(repoIDs))
|
||||
left := len(repoIDs)
|
||||
for left > 0 {
|
||||
limit := db.DefaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
rows, err := db.GetEngine(ctx).
|
||||
In("id", repoIDs[:limit]).
|
||||
Rows(new(repo_model.Repository))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var repo repo_model.Repository
|
||||
err = rows.Scan(&repo)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
repos[repo.ID] = &repo
|
||||
}
|
||||
_ = rows.Close()
|
||||
|
||||
left -= limit
|
||||
repoIDs = repoIDs[limit:]
|
||||
}
|
||||
|
||||
failed := []int{}
|
||||
|
||||
reposList := make(repo_model.RepositoryList, 0, len(repoIDs))
|
||||
for i, notification := range nl {
|
||||
if notification.Repository == nil {
|
||||
notification.Repository = repos[notification.RepoID]
|
||||
}
|
||||
if notification.Repository == nil {
|
||||
log.Error("Notification[%d]: RepoID: %d not found", notification.ID, notification.RepoID)
|
||||
failed = append(failed, i)
|
||||
continue
|
||||
}
|
||||
var found bool
|
||||
for _, r := range reposList {
|
||||
if r.ID == notification.RepoID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
reposList = append(reposList, notification.Repository)
|
||||
}
|
||||
}
|
||||
return reposList, failed, nil
|
||||
}
|
||||
|
||||
func (nl NotificationList) getPendingIssueIDs() []int64 {
|
||||
ids := make(container.Set[int64], len(nl))
|
||||
for _, notification := range nl {
|
||||
if notification.Issue != nil {
|
||||
continue
|
||||
}
|
||||
ids.Add(notification.IssueID)
|
||||
}
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
// LoadIssues loads issues from database
|
||||
func (nl NotificationList) LoadIssues(ctx context.Context) ([]int, error) {
|
||||
if len(nl) == 0 {
|
||||
return []int{}, nil
|
||||
}
|
||||
|
||||
issueIDs := nl.getPendingIssueIDs()
|
||||
issues := make(map[int64]*issues_model.Issue, len(issueIDs))
|
||||
left := len(issueIDs)
|
||||
for left > 0 {
|
||||
limit := db.DefaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
rows, err := db.GetEngine(ctx).
|
||||
In("id", issueIDs[:limit]).
|
||||
Rows(new(issues_model.Issue))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var issue issues_model.Issue
|
||||
err = rows.Scan(&issue)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
issues[issue.ID] = &issue
|
||||
}
|
||||
_ = rows.Close()
|
||||
|
||||
left -= limit
|
||||
issueIDs = issueIDs[limit:]
|
||||
}
|
||||
|
||||
failures := []int{}
|
||||
|
||||
for i, notification := range nl {
|
||||
if notification.Issue == nil {
|
||||
notification.Issue = issues[notification.IssueID]
|
||||
if notification.Issue == nil {
|
||||
if notification.IssueID != 0 {
|
||||
log.Error("Notification[%d]: IssueID: %d Not Found", notification.ID, notification.IssueID)
|
||||
failures = append(failures, i)
|
||||
}
|
||||
continue
|
||||
}
|
||||
notification.Issue.Repo = notification.Repository
|
||||
}
|
||||
}
|
||||
return failures, nil
|
||||
}
|
||||
|
||||
// Without returns the notification list without the failures
|
||||
func (nl NotificationList) Without(failures []int) NotificationList {
|
||||
if len(failures) == 0 {
|
||||
return nl
|
||||
}
|
||||
remaining := make([]*Notification, 0, len(nl))
|
||||
last := -1
|
||||
var i int
|
||||
for _, i = range failures {
|
||||
remaining = append(remaining, nl[last+1:i]...)
|
||||
last = i
|
||||
}
|
||||
if len(nl) > i {
|
||||
remaining = append(remaining, nl[i+1:]...)
|
||||
}
|
||||
return remaining
|
||||
}
|
||||
|
||||
func (nl NotificationList) getPendingCommentIDs() []int64 {
|
||||
ids := make(container.Set[int64], len(nl))
|
||||
for _, notification := range nl {
|
||||
if notification.CommentID == 0 || notification.Comment != nil {
|
||||
continue
|
||||
}
|
||||
ids.Add(notification.CommentID)
|
||||
}
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
func (nl NotificationList) getUserIDs() []int64 {
|
||||
ids := make(container.Set[int64], len(nl))
|
||||
for _, notification := range nl {
|
||||
if notification.UserID == 0 || notification.User != nil {
|
||||
continue
|
||||
}
|
||||
ids.Add(notification.UserID)
|
||||
}
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
// LoadUsers loads users from database
|
||||
func (nl NotificationList) LoadUsers(ctx context.Context) ([]int, error) {
|
||||
if len(nl) == 0 {
|
||||
return []int{}, nil
|
||||
}
|
||||
|
||||
userIDs := nl.getUserIDs()
|
||||
users := make(map[int64]*user_model.User, len(userIDs))
|
||||
left := len(userIDs)
|
||||
for left > 0 {
|
||||
limit := db.DefaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
rows, err := db.GetEngine(ctx).
|
||||
In("id", userIDs[:limit]).
|
||||
Rows(new(user_model.User))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var user user_model.User
|
||||
err = rows.Scan(&user)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users[user.ID] = &user
|
||||
}
|
||||
_ = rows.Close()
|
||||
|
||||
left -= limit
|
||||
userIDs = userIDs[limit:]
|
||||
}
|
||||
|
||||
failures := []int{}
|
||||
for i, notification := range nl {
|
||||
if notification.UserID > 0 && notification.User == nil && users[notification.UserID] != nil {
|
||||
notification.User = users[notification.UserID]
|
||||
if notification.User == nil {
|
||||
log.Error("Notification[%d]: UserID[%d] failed to load", notification.ID, notification.UserID)
|
||||
failures = append(failures, i)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return failures, nil
|
||||
}
|
||||
|
||||
// LoadComments loads comments from database
|
||||
func (nl NotificationList) LoadComments(ctx context.Context) ([]int, error) {
|
||||
if len(nl) == 0 {
|
||||
return []int{}, nil
|
||||
}
|
||||
|
||||
commentIDs := nl.getPendingCommentIDs()
|
||||
comments := make(map[int64]*issues_model.Comment, len(commentIDs))
|
||||
left := len(commentIDs)
|
||||
for left > 0 {
|
||||
limit := db.DefaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
rows, err := db.GetEngine(ctx).
|
||||
In("id", commentIDs[:limit]).
|
||||
Rows(new(issues_model.Comment))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var comment issues_model.Comment
|
||||
err = rows.Scan(&comment)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
comments[comment.ID] = &comment
|
||||
}
|
||||
_ = rows.Close()
|
||||
|
||||
left -= limit
|
||||
commentIDs = commentIDs[limit:]
|
||||
}
|
||||
|
||||
failures := []int{}
|
||||
for i, notification := range nl {
|
||||
if notification.CommentID > 0 && notification.Comment == nil && comments[notification.CommentID] != nil {
|
||||
notification.Comment = comments[notification.CommentID]
|
||||
if notification.Comment == nil {
|
||||
log.Error("Notification[%d]: CommentID[%d] failed to load", notification.ID, notification.CommentID)
|
||||
failures = append(failures, i)
|
||||
continue
|
||||
}
|
||||
notification.Comment.Issue = notification.Issue
|
||||
}
|
||||
}
|
||||
return failures, nil
|
||||
}
|
||||
|
||||
// SetIssueReadBy sets issue to be read by given user.
|
||||
func SetIssueReadBy(ctx context.Context, issueID, userID int64) error {
|
||||
if err := issues_model.UpdateIssueUserByRead(ctx, userID, issueID); err != nil {
|
||||
|
@ -841,3 +376,31 @@ func UpdateNotificationStatuses(ctx context.Context, user *user_model.User, curr
|
|||
Update(n)
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadIssuePullRequests loads all issues' pull requests if possible
|
||||
func (nl NotificationList) LoadIssuePullRequests(ctx context.Context) error {
|
||||
issues := make(map[int64]*issues_model.Issue, len(nl))
|
||||
for _, notification := range nl {
|
||||
if notification.Issue != nil && notification.Issue.IsPull && notification.Issue.PullRequest == nil {
|
||||
issues[notification.Issue.ID] = notification.Issue
|
||||
}
|
||||
}
|
||||
|
||||
if len(issues) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
pulls, err := issues_model.GetPullRequestByIssueIDs(ctx, util.KeysOfMap(issues))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, pull := range pulls {
|
||||
if issue := issues[pull.IssueID]; issue != nil {
|
||||
issue.PullRequest = pull
|
||||
issue.PullRequest.Issue = issue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
480
models/activities/notification_list.go
Normal file
480
models/activities/notification_list.go
Normal file
|
@ -0,0 +1,480 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package activities
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// FindNotificationOptions represent the filters for notifications. If an ID is 0 it will be ignored.
|
||||
type FindNotificationOptions struct {
|
||||
db.ListOptions
|
||||
UserID int64
|
||||
RepoID int64
|
||||
IssueID int64
|
||||
Status []NotificationStatus
|
||||
Source []NotificationSource
|
||||
UpdatedAfterUnix int64
|
||||
UpdatedBeforeUnix int64
|
||||
}
|
||||
|
||||
// ToCond will convert each condition into a xorm-Cond
|
||||
func (opts FindNotificationOptions) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.UserID != 0 {
|
||||
cond = cond.And(builder.Eq{"notification.user_id": opts.UserID})
|
||||
}
|
||||
if opts.RepoID != 0 {
|
||||
cond = cond.And(builder.Eq{"notification.repo_id": opts.RepoID})
|
||||
}
|
||||
if opts.IssueID != 0 {
|
||||
cond = cond.And(builder.Eq{"notification.issue_id": opts.IssueID})
|
||||
}
|
||||
if len(opts.Status) > 0 {
|
||||
if len(opts.Status) == 1 {
|
||||
cond = cond.And(builder.Eq{"notification.status": opts.Status[0]})
|
||||
} else {
|
||||
cond = cond.And(builder.In("notification.status", opts.Status))
|
||||
}
|
||||
}
|
||||
if len(opts.Source) > 0 {
|
||||
cond = cond.And(builder.In("notification.source", opts.Source))
|
||||
}
|
||||
if opts.UpdatedAfterUnix != 0 {
|
||||
cond = cond.And(builder.Gte{"notification.updated_unix": opts.UpdatedAfterUnix})
|
||||
}
|
||||
if opts.UpdatedBeforeUnix != 0 {
|
||||
cond = cond.And(builder.Lte{"notification.updated_unix": opts.UpdatedBeforeUnix})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
func (opts FindNotificationOptions) ToOrders() string {
|
||||
return "notification.updated_unix DESC"
|
||||
}
|
||||
|
||||
// CreateOrUpdateIssueNotifications creates an issue notification
|
||||
// for each watcher, or updates it if already exists
|
||||
// receiverID > 0 just send to receiver, else send to all watcher
|
||||
func CreateOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
if err := createOrUpdateIssueNotifications(ctx, issueID, commentID, notificationAuthorID, receiverID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error {
|
||||
// init
|
||||
var toNotify container.Set[int64]
|
||||
notifications, err := db.Find[Notification](ctx, FindNotificationOptions{
|
||||
IssueID: issueID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
issue, err := issues_model.GetIssueByID(ctx, issueID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if receiverID > 0 {
|
||||
toNotify = make(container.Set[int64], 1)
|
||||
toNotify.Add(receiverID)
|
||||
} else {
|
||||
toNotify = make(container.Set[int64], 32)
|
||||
issueWatches, err := issues_model.GetIssueWatchersIDs(ctx, issueID, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
toNotify.AddMultiple(issueWatches...)
|
||||
if !(issue.IsPull && issues_model.HasWorkInProgressPrefix(issue.Title)) {
|
||||
repoWatches, err := repo_model.GetRepoWatchersIDs(ctx, issue.RepoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
toNotify.AddMultiple(repoWatches...)
|
||||
}
|
||||
issueParticipants, err := issue.GetParticipantIDsByIssue(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
toNotify.AddMultiple(issueParticipants...)
|
||||
|
||||
// dont notify user who cause notification
|
||||
delete(toNotify, notificationAuthorID)
|
||||
// explicit unwatch on issue
|
||||
issueUnWatches, err := issues_model.GetIssueWatchersIDs(ctx, issueID, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, id := range issueUnWatches {
|
||||
toNotify.Remove(id)
|
||||
}
|
||||
// Remove users who have the notification author blocked.
|
||||
blockedAuthorIDs, err := user_model.ListBlockedByUsersID(ctx, notificationAuthorID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, id := range blockedAuthorIDs {
|
||||
toNotify.Remove(id)
|
||||
}
|
||||
}
|
||||
|
||||
err = issue.LoadRepo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// notify
|
||||
for userID := range toNotify {
|
||||
issue.Repo.Units = nil
|
||||
user, err := user_model.GetUserByID(ctx, userID)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
if issue.IsPull && !access_model.CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypePullRequests) {
|
||||
continue
|
||||
}
|
||||
if !issue.IsPull && !access_model.CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypeIssues) {
|
||||
continue
|
||||
}
|
||||
|
||||
if notificationExists(notifications, issue.ID, userID) {
|
||||
if err = updateIssueNotification(ctx, userID, issue.ID, commentID, notificationAuthorID); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err = createIssueNotification(ctx, userID, issue, commentID, notificationAuthorID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NotificationList contains a list of notifications
|
||||
type NotificationList []*Notification
|
||||
|
||||
// LoadAttributes load Repo Issue User and Comment if not loaded
|
||||
func (nl NotificationList) LoadAttributes(ctx context.Context) error {
|
||||
if _, _, err := nl.LoadRepos(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := nl.LoadIssues(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := nl.LoadUsers(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := nl.LoadComments(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nl NotificationList) getPendingRepoIDs() []int64 {
|
||||
ids := make(container.Set[int64], len(nl))
|
||||
for _, notification := range nl {
|
||||
if notification.Repository != nil {
|
||||
continue
|
||||
}
|
||||
ids.Add(notification.RepoID)
|
||||
}
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
// LoadRepos loads repositories from database
|
||||
func (nl NotificationList) LoadRepos(ctx context.Context) (repo_model.RepositoryList, []int, error) {
|
||||
if len(nl) == 0 {
|
||||
return repo_model.RepositoryList{}, []int{}, nil
|
||||
}
|
||||
|
||||
repoIDs := nl.getPendingRepoIDs()
|
||||
repos := make(map[int64]*repo_model.Repository, len(repoIDs))
|
||||
left := len(repoIDs)
|
||||
for left > 0 {
|
||||
limit := db.DefaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
rows, err := db.GetEngine(ctx).
|
||||
In("id", repoIDs[:limit]).
|
||||
Rows(new(repo_model.Repository))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var repo repo_model.Repository
|
||||
err = rows.Scan(&repo)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
repos[repo.ID] = &repo
|
||||
}
|
||||
_ = rows.Close()
|
||||
|
||||
left -= limit
|
||||
repoIDs = repoIDs[limit:]
|
||||
}
|
||||
|
||||
failed := []int{}
|
||||
|
||||
reposList := make(repo_model.RepositoryList, 0, len(repoIDs))
|
||||
for i, notification := range nl {
|
||||
if notification.Repository == nil {
|
||||
notification.Repository = repos[notification.RepoID]
|
||||
}
|
||||
if notification.Repository == nil {
|
||||
log.Error("Notification[%d]: RepoID: %d not found", notification.ID, notification.RepoID)
|
||||
failed = append(failed, i)
|
||||
continue
|
||||
}
|
||||
var found bool
|
||||
for _, r := range reposList {
|
||||
if r.ID == notification.RepoID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
reposList = append(reposList, notification.Repository)
|
||||
}
|
||||
}
|
||||
return reposList, failed, nil
|
||||
}
|
||||
|
||||
func (nl NotificationList) getPendingIssueIDs() []int64 {
|
||||
ids := make(container.Set[int64], len(nl))
|
||||
for _, notification := range nl {
|
||||
if notification.Issue != nil {
|
||||
continue
|
||||
}
|
||||
ids.Add(notification.IssueID)
|
||||
}
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
// LoadIssues loads issues from database
|
||||
func (nl NotificationList) LoadIssues(ctx context.Context) ([]int, error) {
|
||||
if len(nl) == 0 {
|
||||
return []int{}, nil
|
||||
}
|
||||
|
||||
issueIDs := nl.getPendingIssueIDs()
|
||||
issues := make(map[int64]*issues_model.Issue, len(issueIDs))
|
||||
left := len(issueIDs)
|
||||
for left > 0 {
|
||||
limit := db.DefaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
rows, err := db.GetEngine(ctx).
|
||||
In("id", issueIDs[:limit]).
|
||||
Rows(new(issues_model.Issue))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var issue issues_model.Issue
|
||||
err = rows.Scan(&issue)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
issues[issue.ID] = &issue
|
||||
}
|
||||
_ = rows.Close()
|
||||
|
||||
left -= limit
|
||||
issueIDs = issueIDs[limit:]
|
||||
}
|
||||
|
||||
failures := []int{}
|
||||
|
||||
for i, notification := range nl {
|
||||
if notification.Issue == nil {
|
||||
notification.Issue = issues[notification.IssueID]
|
||||
if notification.Issue == nil {
|
||||
if notification.IssueID != 0 {
|
||||
log.Error("Notification[%d]: IssueID: %d Not Found", notification.ID, notification.IssueID)
|
||||
failures = append(failures, i)
|
||||
}
|
||||
continue
|
||||
}
|
||||
notification.Issue.Repo = notification.Repository
|
||||
}
|
||||
}
|
||||
return failures, nil
|
||||
}
|
||||
|
||||
// Without returns the notification list without the failures
|
||||
func (nl NotificationList) Without(failures []int) NotificationList {
|
||||
if len(failures) == 0 {
|
||||
return nl
|
||||
}
|
||||
remaining := make([]*Notification, 0, len(nl))
|
||||
last := -1
|
||||
var i int
|
||||
for _, i = range failures {
|
||||
remaining = append(remaining, nl[last+1:i]...)
|
||||
last = i
|
||||
}
|
||||
if len(nl) > i {
|
||||
remaining = append(remaining, nl[i+1:]...)
|
||||
}
|
||||
return remaining
|
||||
}
|
||||
|
||||
func (nl NotificationList) getPendingCommentIDs() []int64 {
|
||||
ids := make(container.Set[int64], len(nl))
|
||||
for _, notification := range nl {
|
||||
if notification.CommentID == 0 || notification.Comment != nil {
|
||||
continue
|
||||
}
|
||||
ids.Add(notification.CommentID)
|
||||
}
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
func (nl NotificationList) getUserIDs() []int64 {
|
||||
ids := make(container.Set[int64], len(nl))
|
||||
for _, notification := range nl {
|
||||
if notification.UserID == 0 || notification.User != nil {
|
||||
continue
|
||||
}
|
||||
ids.Add(notification.UserID)
|
||||
}
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
// LoadUsers loads users from database
|
||||
func (nl NotificationList) LoadUsers(ctx context.Context) ([]int, error) {
|
||||
if len(nl) == 0 {
|
||||
return []int{}, nil
|
||||
}
|
||||
|
||||
userIDs := nl.getUserIDs()
|
||||
users := make(map[int64]*user_model.User, len(userIDs))
|
||||
left := len(userIDs)
|
||||
for left > 0 {
|
||||
limit := db.DefaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
rows, err := db.GetEngine(ctx).
|
||||
In("id", userIDs[:limit]).
|
||||
Rows(new(user_model.User))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var user user_model.User
|
||||
err = rows.Scan(&user)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users[user.ID] = &user
|
||||
}
|
||||
_ = rows.Close()
|
||||
|
||||
left -= limit
|
||||
userIDs = userIDs[limit:]
|
||||
}
|
||||
|
||||
failures := []int{}
|
||||
for i, notification := range nl {
|
||||
if notification.UserID > 0 && notification.User == nil && users[notification.UserID] != nil {
|
||||
notification.User = users[notification.UserID]
|
||||
if notification.User == nil {
|
||||
log.Error("Notification[%d]: UserID[%d] failed to load", notification.ID, notification.UserID)
|
||||
failures = append(failures, i)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return failures, nil
|
||||
}
|
||||
|
||||
// LoadComments loads comments from database
|
||||
func (nl NotificationList) LoadComments(ctx context.Context) ([]int, error) {
|
||||
if len(nl) == 0 {
|
||||
return []int{}, nil
|
||||
}
|
||||
|
||||
commentIDs := nl.getPendingCommentIDs()
|
||||
comments := make(map[int64]*issues_model.Comment, len(commentIDs))
|
||||
left := len(commentIDs)
|
||||
for left > 0 {
|
||||
limit := db.DefaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
rows, err := db.GetEngine(ctx).
|
||||
In("id", commentIDs[:limit]).
|
||||
Rows(new(issues_model.Comment))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var comment issues_model.Comment
|
||||
err = rows.Scan(&comment)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
comments[comment.ID] = &comment
|
||||
}
|
||||
_ = rows.Close()
|
||||
|
||||
left -= limit
|
||||
commentIDs = commentIDs[limit:]
|
||||
}
|
||||
|
||||
failures := []int{}
|
||||
for i, notification := range nl {
|
||||
if notification.CommentID > 0 && notification.Comment == nil && comments[notification.CommentID] != nil {
|
||||
notification.Comment = comments[notification.CommentID]
|
||||
if notification.Comment == nil {
|
||||
log.Error("Notification[%d]: CommentID[%d] failed to load", notification.ID, notification.CommentID)
|
||||
failures = append(failures, i)
|
||||
continue
|
||||
}
|
||||
notification.Comment.Issue = notification.Issue
|
||||
}
|
||||
}
|
||||
return failures, nil
|
||||
}
|
|
@ -9,6 +9,7 @@ import (
|
|||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
|
@ -29,7 +30,8 @@ type Statistic struct {
|
|||
Mirror, Release, AuthSource, Webhook,
|
||||
Milestone, Label, HookTask,
|
||||
Team, UpdateTask, Project,
|
||||
ProjectBoard, Attachment int64
|
||||
ProjectBoard, Attachment,
|
||||
Branches, Tags, CommitStatus int64
|
||||
IssueByLabel []IssueByLabelCount
|
||||
IssueByRepository []IssueByRepositoryCount
|
||||
}
|
||||
|
@ -58,6 +60,9 @@ func GetStatistic(ctx context.Context) (stats Statistic) {
|
|||
stats.Counter.Watch, _ = e.Count(new(repo_model.Watch))
|
||||
stats.Counter.Star, _ = e.Count(new(repo_model.Star))
|
||||
stats.Counter.Access, _ = e.Count(new(access_model.Access))
|
||||
stats.Counter.Branches, _ = e.Count(new(git_model.Branch))
|
||||
stats.Counter.Tags, _ = e.Where("is_draft=?", false).Count(new(repo_model.Release))
|
||||
stats.Counter.CommitStatus, _ = e.Count(new(git_model.CommitStatus))
|
||||
|
||||
type IssueCount struct {
|
||||
Count int64
|
||||
|
|
|
@ -5,18 +5,10 @@ package asymkey
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"hash"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/keybase/go-crypto/openpgp/packet"
|
||||
)
|
||||
|
||||
// __________________ ________ ____ __.
|
||||
|
@ -40,45 +32,22 @@ import (
|
|||
|
||||
// This file provides functions relating commit verification
|
||||
|
||||
// CommitVerification represents a commit validation of signature
|
||||
type CommitVerification struct {
|
||||
Verified bool
|
||||
Warning bool
|
||||
Reason string
|
||||
SigningUser *user_model.User
|
||||
CommittingUser *user_model.User
|
||||
SigningEmail string
|
||||
SigningKey *GPGKey
|
||||
SigningSSHKey *PublicKey
|
||||
TrustStatus string
|
||||
}
|
||||
|
||||
// SignCommit represents a commit with validation of signature.
|
||||
type SignCommit struct {
|
||||
Verification *CommitVerification
|
||||
Verification *ObjectVerification
|
||||
*user_model.UserCommit
|
||||
}
|
||||
|
||||
const (
|
||||
// BadSignature is used as the reason when the signature has a KeyID that is in the db
|
||||
// but no key that has that ID verifies the signature. This is a suspicious failure.
|
||||
BadSignature = "gpg.error.probable_bad_signature"
|
||||
// BadDefaultSignature is used as the reason when the signature has a KeyID that matches the
|
||||
// default Key but is not verified by the default key. This is a suspicious failure.
|
||||
BadDefaultSignature = "gpg.error.probable_bad_default_signature"
|
||||
// NoKeyFound is used as the reason when no key can be found to verify the signature.
|
||||
NoKeyFound = "gpg.error.no_gpg_keys_found"
|
||||
)
|
||||
|
||||
// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys.
|
||||
func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error)) []*SignCommit {
|
||||
newCommits := make([]*SignCommit, 0, len(oldCommits))
|
||||
keyMap := map[string]bool{}
|
||||
|
||||
for _, c := range oldCommits {
|
||||
o := commitToGitObject(c.Commit)
|
||||
signCommit := &SignCommit{
|
||||
UserCommit: c,
|
||||
Verification: ParseCommitWithSignature(ctx, c.Commit),
|
||||
Verification: ParseObjectWithSignature(ctx, &o),
|
||||
}
|
||||
|
||||
_ = CalculateTrustStatus(signCommit.Verification, repoTrustModel, isOwnerMemberCollaborator, &keyMap)
|
||||
|
@ -88,456 +57,7 @@ func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.Use
|
|||
return newCommits
|
||||
}
|
||||
|
||||
// ParseCommitWithSignature check if signature is good against keystore.
|
||||
func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerification {
|
||||
var committer *user_model.User
|
||||
if c.Committer != nil {
|
||||
var err error
|
||||
// Find Committer account
|
||||
committer, err = user_model.GetUserByEmail(ctx, c.Committer.Email) // This finds the user by primary email or activated email so commit will not be valid if email is not
|
||||
if err != nil { // Skipping not user for committer
|
||||
committer = &user_model.User{
|
||||
Name: c.Committer.Name,
|
||||
Email: c.Committer.Email,
|
||||
}
|
||||
// We can expect this to often be an ErrUserNotExist. in the case
|
||||
// it is not, however, it is important to log it.
|
||||
if !user_model.IsErrUserNotExist(err) {
|
||||
log.Error("GetUserByEmail: %v", err)
|
||||
return &CommitVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.no_committer_account",
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// If no signature just report the committer
|
||||
if c.Signature == nil {
|
||||
return &CommitVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false, // Default value
|
||||
Reason: "gpg.error.not_signed_commit", // Default value
|
||||
}
|
||||
}
|
||||
|
||||
// If this a SSH signature handle it differently
|
||||
if strings.HasPrefix(c.Signature.Signature, "-----BEGIN SSH SIGNATURE-----") {
|
||||
return ParseCommitWithSSHSignature(ctx, c, committer)
|
||||
}
|
||||
|
||||
// Parsing signature
|
||||
sig, err := extractSignature(c.Signature.Signature)
|
||||
if err != nil { // Skipping failed to extract sign
|
||||
log.Error("SignatureRead err: %v", err)
|
||||
return &CommitVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.extract_sign",
|
||||
}
|
||||
}
|
||||
|
||||
keyID := ""
|
||||
if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 {
|
||||
keyID = fmt.Sprintf("%X", *sig.IssuerKeyId)
|
||||
}
|
||||
if keyID == "" && sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
|
||||
keyID = fmt.Sprintf("%X", sig.IssuerFingerprint[12:20])
|
||||
}
|
||||
defaultReason := NoKeyFound
|
||||
|
||||
// First check if the sig has a keyID and if so just look at that
|
||||
if commitVerification := hashAndVerifyForKeyID(
|
||||
ctx,
|
||||
sig,
|
||||
c.Signature.Payload,
|
||||
committer,
|
||||
keyID,
|
||||
setting.AppName,
|
||||
""); commitVerification != nil {
|
||||
if commitVerification.Reason == BadSignature {
|
||||
defaultReason = BadSignature
|
||||
} else {
|
||||
return commitVerification
|
||||
}
|
||||
}
|
||||
|
||||
// Now try to associate the signature with the committer, if present
|
||||
if committer.ID != 0 {
|
||||
keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{
|
||||
OwnerID: committer.ID,
|
||||
})
|
||||
if err != nil { // Skipping failed to get gpg keys of user
|
||||
log.Error("ListGPGKeys: %v", err)
|
||||
return &CommitVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
||||
}
|
||||
}
|
||||
|
||||
if err := GPGKeyList(keys).LoadSubKeys(ctx); err != nil {
|
||||
log.Error("LoadSubKeys: %v", err)
|
||||
return &CommitVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
||||
}
|
||||
}
|
||||
|
||||
committerEmailAddresses, _ := user_model.GetEmailAddresses(ctx, committer.ID)
|
||||
activated := false
|
||||
for _, e := range committerEmailAddresses {
|
||||
if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
|
||||
activated = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, k := range keys {
|
||||
// Pre-check (& optimization) that emails attached to key can be attached to the committer email and can validate
|
||||
canValidate := false
|
||||
email := ""
|
||||
if k.Verified && activated {
|
||||
canValidate = true
|
||||
email = c.Committer.Email
|
||||
}
|
||||
if !canValidate {
|
||||
for _, e := range k.Emails {
|
||||
if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
|
||||
canValidate = true
|
||||
email = e.Email
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !canValidate {
|
||||
continue // Skip this key
|
||||
}
|
||||
|
||||
commitVerification := hashAndVerifyWithSubKeysCommitVerification(sig, c.Signature.Payload, k, committer, committer, email)
|
||||
if commitVerification != nil {
|
||||
return commitVerification
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if setting.Repository.Signing.SigningKey != "" && setting.Repository.Signing.SigningKey != "default" && setting.Repository.Signing.SigningKey != "none" {
|
||||
// OK we should try the default key
|
||||
gpgSettings := git.GPGSettings{
|
||||
Sign: true,
|
||||
KeyID: setting.Repository.Signing.SigningKey,
|
||||
Name: setting.Repository.Signing.SigningName,
|
||||
Email: setting.Repository.Signing.SigningEmail,
|
||||
}
|
||||
if err := gpgSettings.LoadPublicKeyContent(); err != nil {
|
||||
log.Error("Error getting default signing key: %s %v", gpgSettings.KeyID, err)
|
||||
} else if commitVerification := verifyWithGPGSettings(ctx, &gpgSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
|
||||
if commitVerification.Reason == BadSignature {
|
||||
defaultReason = BadSignature
|
||||
} else {
|
||||
return commitVerification
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defaultGPGSettings, err := c.GetRepositoryDefaultPublicGPGKey(false)
|
||||
if err != nil {
|
||||
log.Error("Error getting default public gpg key: %v", err)
|
||||
} else if defaultGPGSettings == nil {
|
||||
log.Warn("Unable to get defaultGPGSettings for unattached commit: %s", c.ID.String())
|
||||
} else if defaultGPGSettings.Sign {
|
||||
if commitVerification := verifyWithGPGSettings(ctx, defaultGPGSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
|
||||
if commitVerification.Reason == BadSignature {
|
||||
defaultReason = BadSignature
|
||||
} else {
|
||||
return commitVerification
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &CommitVerification{ // Default at this stage
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Warning: defaultReason != NoKeyFound,
|
||||
Reason: defaultReason,
|
||||
SigningKey: &GPGKey{
|
||||
KeyID: keyID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func verifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, sig *packet.Signature, payload string, committer *user_model.User, keyID string) *CommitVerification {
|
||||
// First try to find the key in the db
|
||||
if commitVerification := hashAndVerifyForKeyID(ctx, sig, payload, committer, gpgSettings.KeyID, gpgSettings.Name, gpgSettings.Email); commitVerification != nil {
|
||||
return commitVerification
|
||||
}
|
||||
|
||||
// Otherwise we have to parse the key
|
||||
ekeys, err := checkArmoredGPGKeyString(gpgSettings.PublicKeyContent)
|
||||
if err != nil {
|
||||
log.Error("Unable to get default signing key: %v", err)
|
||||
return &CommitVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.generate_hash",
|
||||
}
|
||||
}
|
||||
for _, ekey := range ekeys {
|
||||
pubkey := ekey.PrimaryKey
|
||||
content, err := base64EncPubKey(pubkey)
|
||||
if err != nil {
|
||||
return &CommitVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.generate_hash",
|
||||
}
|
||||
}
|
||||
k := &GPGKey{
|
||||
Content: content,
|
||||
CanSign: pubkey.CanSign(),
|
||||
KeyID: pubkey.KeyIdString(),
|
||||
}
|
||||
for _, subKey := range ekey.Subkeys {
|
||||
content, err := base64EncPubKey(subKey.PublicKey)
|
||||
if err != nil {
|
||||
return &CommitVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.generate_hash",
|
||||
}
|
||||
}
|
||||
k.SubsKey = append(k.SubsKey, &GPGKey{
|
||||
Content: content,
|
||||
CanSign: subKey.PublicKey.CanSign(),
|
||||
KeyID: subKey.PublicKey.KeyIdString(),
|
||||
})
|
||||
}
|
||||
if commitVerification := hashAndVerifyWithSubKeysCommitVerification(sig, payload, k, committer, &user_model.User{
|
||||
Name: gpgSettings.Name,
|
||||
Email: gpgSettings.Email,
|
||||
}, gpgSettings.Email); commitVerification != nil {
|
||||
return commitVerification
|
||||
}
|
||||
if keyID == k.KeyID {
|
||||
// This is a bad situation ... We have a key id that matches our default key but the signature doesn't match.
|
||||
return &CommitVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Warning: true,
|
||||
Reason: BadSignature,
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error {
|
||||
// Check if key can sign
|
||||
if !k.CanSign {
|
||||
return fmt.Errorf("key can not sign")
|
||||
}
|
||||
// Decode key
|
||||
pkey, err := base64DecPubKey(k.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return pkey.VerifySignature(h, s)
|
||||
}
|
||||
|
||||
func hashAndVerify(sig *packet.Signature, payload string, k *GPGKey) (*GPGKey, error) {
|
||||
// Generating hash of commit
|
||||
hash, err := populateHash(sig.Hash, []byte(payload))
|
||||
if err != nil { // Skipping as failed to generate hash
|
||||
log.Error("PopulateHash: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
// We will ignore errors in verification as they don't need to be propagated up
|
||||
err = verifySign(sig, hash, k)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
return k, nil
|
||||
}
|
||||
|
||||
func hashAndVerifyWithSubKeys(sig *packet.Signature, payload string, k *GPGKey) (*GPGKey, error) {
|
||||
verified, err := hashAndVerify(sig, payload, k)
|
||||
if err != nil || verified != nil {
|
||||
return verified, err
|
||||
}
|
||||
for _, sk := range k.SubsKey {
|
||||
verified, err := hashAndVerify(sig, payload, sk)
|
||||
if err != nil || verified != nil {
|
||||
return verified, err
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func hashAndVerifyWithSubKeysCommitVerification(sig *packet.Signature, payload string, k *GPGKey, committer, signer *user_model.User, email string) *CommitVerification {
|
||||
key, err := hashAndVerifyWithSubKeys(sig, payload, k)
|
||||
if err != nil { // Skipping failed to generate hash
|
||||
return &CommitVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.generate_hash",
|
||||
}
|
||||
}
|
||||
|
||||
if key != nil {
|
||||
return &CommitVerification{ // Everything is ok
|
||||
CommittingUser: committer,
|
||||
Verified: true,
|
||||
Reason: fmt.Sprintf("%s / %s", signer.Name, key.KeyID),
|
||||
SigningUser: signer,
|
||||
SigningKey: key,
|
||||
SigningEmail: email,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload string, committer *user_model.User, keyID, name, email string) *CommitVerification {
|
||||
if keyID == "" {
|
||||
return nil
|
||||
}
|
||||
keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{
|
||||
KeyID: keyID,
|
||||
IncludeSubKeys: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("GetGPGKeysByKeyID: %v", err)
|
||||
return &CommitVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
||||
}
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, key := range keys {
|
||||
var primaryKeys []*GPGKey
|
||||
if key.PrimaryKeyID != "" {
|
||||
primaryKeys, err = db.Find[GPGKey](ctx, FindGPGKeyOptions{
|
||||
KeyID: key.PrimaryKeyID,
|
||||
IncludeSubKeys: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("GetGPGKeysByKeyID: %v", err)
|
||||
return &CommitVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activated, email := checkKeyEmails(ctx, email, append([]*GPGKey{key}, primaryKeys...)...)
|
||||
if !activated {
|
||||
continue
|
||||
}
|
||||
|
||||
signer := &user_model.User{
|
||||
Name: name,
|
||||
Email: email,
|
||||
}
|
||||
if key.OwnerID != 0 {
|
||||
owner, err := user_model.GetUserByID(ctx, key.OwnerID)
|
||||
if err == nil {
|
||||
signer = owner
|
||||
} else if !user_model.IsErrUserNotExist(err) {
|
||||
log.Error("Failed to user_model.GetUserByID: %d for key ID: %d (%s) %v", key.OwnerID, key.ID, key.KeyID, err)
|
||||
return &CommitVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.no_committer_account",
|
||||
}
|
||||
}
|
||||
}
|
||||
commitVerification := hashAndVerifyWithSubKeysCommitVerification(sig, payload, key, committer, signer, email)
|
||||
if commitVerification != nil {
|
||||
return commitVerification
|
||||
}
|
||||
}
|
||||
// This is a bad situation ... We have a key id that is in our database but the signature doesn't match.
|
||||
return &CommitVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Warning: true,
|
||||
Reason: BadSignature,
|
||||
}
|
||||
}
|
||||
|
||||
// CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository
|
||||
// There are several trust models in Gitea
|
||||
func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error), keyMap *map[string]bool) error {
|
||||
if !verification.Verified {
|
||||
return nil
|
||||
}
|
||||
|
||||
// In the Committer trust model a signature is trusted if it matches the committer
|
||||
// - it doesn't matter if they're a collaborator, the owner, Gitea or Github
|
||||
// NB: This model is commit verification only
|
||||
if repoTrustModel == repo_model.CommitterTrustModel {
|
||||
// default to "unmatched"
|
||||
verification.TrustStatus = "unmatched"
|
||||
|
||||
// We can only verify against users in our database but the default key will match
|
||||
// against by email if it is not in the db.
|
||||
if (verification.SigningUser.ID != 0 &&
|
||||
verification.CommittingUser.ID == verification.SigningUser.ID) ||
|
||||
(verification.SigningUser.ID == 0 && verification.CommittingUser.ID == 0 &&
|
||||
verification.SigningUser.Email == verification.CommittingUser.Email) {
|
||||
verification.TrustStatus = "trusted"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Now we drop to the more nuanced trust models...
|
||||
verification.TrustStatus = "trusted"
|
||||
|
||||
if verification.SigningUser.ID == 0 {
|
||||
// This commit is signed by the default key - but this key is not assigned to a user in the DB.
|
||||
|
||||
// However in the repo_model.CollaboratorCommitterTrustModel we cannot mark this as trusted
|
||||
// unless the default key matches the email of a non-user.
|
||||
if repoTrustModel == repo_model.CollaboratorCommitterTrustModel && (verification.CommittingUser.ID != 0 ||
|
||||
verification.SigningUser.Email != verification.CommittingUser.Email) {
|
||||
verification.TrustStatus = "untrusted"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check we actually have a GPG SigningKey
|
||||
var err error
|
||||
if verification.SigningKey != nil {
|
||||
var isMember bool
|
||||
if keyMap != nil {
|
||||
var has bool
|
||||
isMember, has = (*keyMap)[verification.SigningKey.KeyID]
|
||||
if !has {
|
||||
isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
|
||||
(*keyMap)[verification.SigningKey.KeyID] = isMember
|
||||
}
|
||||
} else {
|
||||
isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
|
||||
}
|
||||
|
||||
if !isMember {
|
||||
verification.TrustStatus = "untrusted"
|
||||
if verification.CommittingUser.ID != verification.SigningUser.ID {
|
||||
// The committing user and the signing user are not the same
|
||||
// This should be marked as questionable unless the signing user is a collaborator/team member etc.
|
||||
verification.TrustStatus = "unmatched"
|
||||
}
|
||||
} else if repoTrustModel == repo_model.CollaboratorCommitterTrustModel && verification.CommittingUser.ID != verification.SigningUser.ID {
|
||||
// The committing user and the signing user are not the same and our trustmodel states that they must match
|
||||
verification.TrustStatus = "unmatched"
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *ObjectVerification {
|
||||
o := commitToGitObject(c)
|
||||
return ParseObjectWithSignature(ctx, &o)
|
||||
}
|
||||
|
|
527
models/asymkey/gpg_key_object_verification.go
Normal file
527
models/asymkey/gpg_key_object_verification.go
Normal file
|
@ -0,0 +1,527 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package asymkey
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"hash"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/keybase/go-crypto/openpgp/packet"
|
||||
)
|
||||
|
||||
// This file provides functions related to object (commit, tag) verification
|
||||
|
||||
// ObjectVerification represents a commit validation of signature
|
||||
type ObjectVerification struct {
|
||||
Verified bool
|
||||
Warning bool
|
||||
Reason string
|
||||
SigningUser *user_model.User
|
||||
CommittingUser *user_model.User
|
||||
SigningEmail string
|
||||
SigningKey *GPGKey
|
||||
SigningSSHKey *PublicKey
|
||||
TrustStatus string
|
||||
}
|
||||
|
||||
const (
|
||||
// BadSignature is used as the reason when the signature has a KeyID that is in the db
|
||||
// but no key that has that ID verifies the signature. This is a suspicious failure.
|
||||
BadSignature = "gpg.error.probable_bad_signature"
|
||||
// BadDefaultSignature is used as the reason when the signature has a KeyID that matches the
|
||||
// default Key but is not verified by the default key. This is a suspicious failure.
|
||||
BadDefaultSignature = "gpg.error.probable_bad_default_signature"
|
||||
// NoKeyFound is used as the reason when no key can be found to verify the signature.
|
||||
NoKeyFound = "gpg.error.no_gpg_keys_found"
|
||||
)
|
||||
|
||||
type GitObject struct {
|
||||
ID git.ObjectID
|
||||
Committer *git.Signature
|
||||
Signature *git.ObjectSignature
|
||||
Commit *git.Commit
|
||||
}
|
||||
|
||||
func commitToGitObject(c *git.Commit) GitObject {
|
||||
return GitObject{
|
||||
ID: c.ID,
|
||||
Committer: c.Committer,
|
||||
Signature: c.Signature,
|
||||
Commit: c,
|
||||
}
|
||||
}
|
||||
|
||||
func tagToGitObject(t *git.Tag, gitRepo *git.Repository) GitObject {
|
||||
commit, _ := t.Commit(gitRepo)
|
||||
return GitObject{
|
||||
ID: t.ID,
|
||||
Committer: t.Tagger,
|
||||
Signature: t.Signature,
|
||||
Commit: commit,
|
||||
}
|
||||
}
|
||||
|
||||
// ParseObjectWithSignature check if signature is good against keystore.
|
||||
func ParseObjectWithSignature(ctx context.Context, c *GitObject) *ObjectVerification {
|
||||
var committer *user_model.User
|
||||
if c.Committer != nil {
|
||||
var err error
|
||||
// Find Committer account
|
||||
committer, err = user_model.GetUserByEmail(ctx, c.Committer.Email) // This finds the user by primary email or activated email so commit will not be valid if email is not
|
||||
if err != nil { // Skipping not user for committer
|
||||
committer = &user_model.User{
|
||||
Name: c.Committer.Name,
|
||||
Email: c.Committer.Email,
|
||||
}
|
||||
// We can expect this to often be an ErrUserNotExist. in the case
|
||||
// it is not, however, it is important to log it.
|
||||
if !user_model.IsErrUserNotExist(err) {
|
||||
log.Error("GetUserByEmail: %v", err)
|
||||
return &ObjectVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.no_committer_account",
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// If no signature just report the committer
|
||||
if c.Signature == nil {
|
||||
return &ObjectVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false, // Default value
|
||||
Reason: "gpg.error.not_signed_commit", // Default value
|
||||
}
|
||||
}
|
||||
|
||||
// If this a SSH signature handle it differently
|
||||
if strings.HasPrefix(c.Signature.Signature, "-----BEGIN SSH SIGNATURE-----") {
|
||||
return ParseObjectWithSSHSignature(ctx, c, committer)
|
||||
}
|
||||
|
||||
// Parsing signature
|
||||
sig, err := extractSignature(c.Signature.Signature)
|
||||
if err != nil { // Skipping failed to extract sign
|
||||
log.Error("SignatureRead err: %v", err)
|
||||
return &ObjectVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.extract_sign",
|
||||
}
|
||||
}
|
||||
|
||||
keyID := ""
|
||||
if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 {
|
||||
keyID = fmt.Sprintf("%X", *sig.IssuerKeyId)
|
||||
}
|
||||
if keyID == "" && sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
|
||||
keyID = fmt.Sprintf("%X", sig.IssuerFingerprint[12:20])
|
||||
}
|
||||
defaultReason := NoKeyFound
|
||||
|
||||
// First check if the sig has a keyID and if so just look at that
|
||||
if commitVerification := hashAndVerifyForKeyID(
|
||||
ctx,
|
||||
sig,
|
||||
c.Signature.Payload,
|
||||
committer,
|
||||
keyID,
|
||||
setting.AppName,
|
||||
""); commitVerification != nil {
|
||||
if commitVerification.Reason == BadSignature {
|
||||
defaultReason = BadSignature
|
||||
} else {
|
||||
return commitVerification
|
||||
}
|
||||
}
|
||||
|
||||
// Now try to associate the signature with the committer, if present
|
||||
if committer.ID != 0 {
|
||||
keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{
|
||||
OwnerID: committer.ID,
|
||||
})
|
||||
if err != nil { // Skipping failed to get gpg keys of user
|
||||
log.Error("ListGPGKeys: %v", err)
|
||||
return &ObjectVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
||||
}
|
||||
}
|
||||
|
||||
if err := GPGKeyList(keys).LoadSubKeys(ctx); err != nil {
|
||||
log.Error("LoadSubKeys: %v", err)
|
||||
return &ObjectVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
||||
}
|
||||
}
|
||||
|
||||
committerEmailAddresses, _ := user_model.GetEmailAddresses(ctx, committer.ID)
|
||||
activated := false
|
||||
for _, e := range committerEmailAddresses {
|
||||
if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
|
||||
activated = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, k := range keys {
|
||||
// Pre-check (& optimization) that emails attached to key can be attached to the committer email and can validate
|
||||
canValidate := false
|
||||
email := ""
|
||||
if k.Verified && activated {
|
||||
canValidate = true
|
||||
email = c.Committer.Email
|
||||
}
|
||||
if !canValidate {
|
||||
for _, e := range k.Emails {
|
||||
if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
|
||||
canValidate = true
|
||||
email = e.Email
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !canValidate {
|
||||
continue // Skip this key
|
||||
}
|
||||
|
||||
commitVerification := hashAndVerifyWithSubKeysObjectVerification(sig, c.Signature.Payload, k, committer, committer, email)
|
||||
if commitVerification != nil {
|
||||
return commitVerification
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if setting.Repository.Signing.SigningKey != "" && setting.Repository.Signing.SigningKey != "default" && setting.Repository.Signing.SigningKey != "none" {
|
||||
// OK we should try the default key
|
||||
gpgSettings := git.GPGSettings{
|
||||
Sign: true,
|
||||
KeyID: setting.Repository.Signing.SigningKey,
|
||||
Name: setting.Repository.Signing.SigningName,
|
||||
Email: setting.Repository.Signing.SigningEmail,
|
||||
}
|
||||
if err := gpgSettings.LoadPublicKeyContent(); err != nil {
|
||||
log.Error("Error getting default signing key: %s %v", gpgSettings.KeyID, err)
|
||||
} else if commitVerification := verifyWithGPGSettings(ctx, &gpgSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
|
||||
if commitVerification.Reason == BadSignature {
|
||||
defaultReason = BadSignature
|
||||
} else {
|
||||
return commitVerification
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defaultGPGSettings, err := c.Commit.GetRepositoryDefaultPublicGPGKey(false)
|
||||
if err != nil {
|
||||
log.Error("Error getting default public gpg key: %v", err)
|
||||
} else if defaultGPGSettings == nil {
|
||||
log.Warn("Unable to get defaultGPGSettings for unattached commit: %s", c.Commit.ID.String())
|
||||
} else if defaultGPGSettings.Sign {
|
||||
if commitVerification := verifyWithGPGSettings(ctx, defaultGPGSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
|
||||
if commitVerification.Reason == BadSignature {
|
||||
defaultReason = BadSignature
|
||||
} else {
|
||||
return commitVerification
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &ObjectVerification{ // Default at this stage
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Warning: defaultReason != NoKeyFound,
|
||||
Reason: defaultReason,
|
||||
SigningKey: &GPGKey{
|
||||
KeyID: keyID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func verifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, sig *packet.Signature, payload string, committer *user_model.User, keyID string) *ObjectVerification {
|
||||
// First try to find the key in the db
|
||||
if commitVerification := hashAndVerifyForKeyID(ctx, sig, payload, committer, gpgSettings.KeyID, gpgSettings.Name, gpgSettings.Email); commitVerification != nil {
|
||||
return commitVerification
|
||||
}
|
||||
|
||||
// Otherwise we have to parse the key
|
||||
ekeys, err := checkArmoredGPGKeyString(gpgSettings.PublicKeyContent)
|
||||
if err != nil {
|
||||
log.Error("Unable to get default signing key: %v", err)
|
||||
return &ObjectVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.generate_hash",
|
||||
}
|
||||
}
|
||||
for _, ekey := range ekeys {
|
||||
pubkey := ekey.PrimaryKey
|
||||
content, err := base64EncPubKey(pubkey)
|
||||
if err != nil {
|
||||
return &ObjectVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.generate_hash",
|
||||
}
|
||||
}
|
||||
k := &GPGKey{
|
||||
Content: content,
|
||||
CanSign: pubkey.CanSign(),
|
||||
KeyID: pubkey.KeyIdString(),
|
||||
}
|
||||
for _, subKey := range ekey.Subkeys {
|
||||
content, err := base64EncPubKey(subKey.PublicKey)
|
||||
if err != nil {
|
||||
return &ObjectVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.generate_hash",
|
||||
}
|
||||
}
|
||||
k.SubsKey = append(k.SubsKey, &GPGKey{
|
||||
Content: content,
|
||||
CanSign: subKey.PublicKey.CanSign(),
|
||||
KeyID: subKey.PublicKey.KeyIdString(),
|
||||
})
|
||||
}
|
||||
if commitVerification := hashAndVerifyWithSubKeysObjectVerification(sig, payload, k, committer, &user_model.User{
|
||||
Name: gpgSettings.Name,
|
||||
Email: gpgSettings.Email,
|
||||
}, gpgSettings.Email); commitVerification != nil {
|
||||
return commitVerification
|
||||
}
|
||||
if keyID == k.KeyID {
|
||||
// This is a bad situation ... We have a key id that matches our default key but the signature doesn't match.
|
||||
return &ObjectVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Warning: true,
|
||||
Reason: BadSignature,
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error {
|
||||
// Check if key can sign
|
||||
if !k.CanSign {
|
||||
return fmt.Errorf("key can not sign")
|
||||
}
|
||||
// Decode key
|
||||
pkey, err := base64DecPubKey(k.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return pkey.VerifySignature(h, s)
|
||||
}
|
||||
|
||||
func hashAndVerify(sig *packet.Signature, payload string, k *GPGKey) (*GPGKey, error) {
|
||||
// Generating hash of commit
|
||||
hash, err := populateHash(sig.Hash, []byte(payload))
|
||||
if err != nil { // Skipping as failed to generate hash
|
||||
log.Error("PopulateHash: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
// We will ignore errors in verification as they don't need to be propagated up
|
||||
err = verifySign(sig, hash, k)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
return k, nil
|
||||
}
|
||||
|
||||
func hashAndVerifyWithSubKeys(sig *packet.Signature, payload string, k *GPGKey) (*GPGKey, error) {
|
||||
verified, err := hashAndVerify(sig, payload, k)
|
||||
if err != nil || verified != nil {
|
||||
return verified, err
|
||||
}
|
||||
for _, sk := range k.SubsKey {
|
||||
verified, err := hashAndVerify(sig, payload, sk)
|
||||
if err != nil || verified != nil {
|
||||
return verified, err
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func hashAndVerifyWithSubKeysObjectVerification(sig *packet.Signature, payload string, k *GPGKey, committer, signer *user_model.User, email string) *ObjectVerification {
|
||||
key, err := hashAndVerifyWithSubKeys(sig, payload, k)
|
||||
if err != nil { // Skipping failed to generate hash
|
||||
return &ObjectVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.generate_hash",
|
||||
}
|
||||
}
|
||||
|
||||
if key != nil {
|
||||
return &ObjectVerification{ // Everything is ok
|
||||
CommittingUser: committer,
|
||||
Verified: true,
|
||||
Reason: fmt.Sprintf("%s / %s", signer.Name, key.KeyID),
|
||||
SigningUser: signer,
|
||||
SigningKey: key,
|
||||
SigningEmail: email,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload string, committer *user_model.User, keyID, name, email string) *ObjectVerification {
|
||||
if keyID == "" {
|
||||
return nil
|
||||
}
|
||||
keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{
|
||||
KeyID: keyID,
|
||||
IncludeSubKeys: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("GetGPGKeysByKeyID: %v", err)
|
||||
return &ObjectVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
||||
}
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, key := range keys {
|
||||
var primaryKeys []*GPGKey
|
||||
if key.PrimaryKeyID != "" {
|
||||
primaryKeys, err = db.Find[GPGKey](ctx, FindGPGKeyOptions{
|
||||
KeyID: key.PrimaryKeyID,
|
||||
IncludeSubKeys: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("GetGPGKeysByKeyID: %v", err)
|
||||
return &ObjectVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activated, email := checkKeyEmails(ctx, email, append([]*GPGKey{key}, primaryKeys...)...)
|
||||
if !activated {
|
||||
continue
|
||||
}
|
||||
|
||||
signer := &user_model.User{
|
||||
Name: name,
|
||||
Email: email,
|
||||
}
|
||||
if key.OwnerID != 0 {
|
||||
owner, err := user_model.GetUserByID(ctx, key.OwnerID)
|
||||
if err == nil {
|
||||
signer = owner
|
||||
} else if !user_model.IsErrUserNotExist(err) {
|
||||
log.Error("Failed to user_model.GetUserByID: %d for key ID: %d (%s) %v", key.OwnerID, key.ID, key.KeyID, err)
|
||||
return &ObjectVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.no_committer_account",
|
||||
}
|
||||
}
|
||||
}
|
||||
commitVerification := hashAndVerifyWithSubKeysObjectVerification(sig, payload, key, committer, signer, email)
|
||||
if commitVerification != nil {
|
||||
return commitVerification
|
||||
}
|
||||
}
|
||||
// This is a bad situation ... We have a key id that is in our database but the signature doesn't match.
|
||||
return &ObjectVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Warning: true,
|
||||
Reason: BadSignature,
|
||||
}
|
||||
}
|
||||
|
||||
// CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository
|
||||
// There are several trust models in Gitea
|
||||
func CalculateTrustStatus(verification *ObjectVerification, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error), keyMap *map[string]bool) error {
|
||||
if !verification.Verified {
|
||||
return nil
|
||||
}
|
||||
|
||||
// In the Committer trust model a signature is trusted if it matches the committer
|
||||
// - it doesn't matter if they're a collaborator, the owner, Gitea or Github
|
||||
// NB: This model is commit verification only
|
||||
if repoTrustModel == repo_model.CommitterTrustModel {
|
||||
// default to "unmatched"
|
||||
verification.TrustStatus = "unmatched"
|
||||
|
||||
// We can only verify against users in our database but the default key will match
|
||||
// against by email if it is not in the db.
|
||||
if (verification.SigningUser.ID != 0 &&
|
||||
verification.CommittingUser.ID == verification.SigningUser.ID) ||
|
||||
(verification.SigningUser.ID == 0 && verification.CommittingUser.ID == 0 &&
|
||||
verification.SigningUser.Email == verification.CommittingUser.Email) {
|
||||
verification.TrustStatus = "trusted"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Now we drop to the more nuanced trust models...
|
||||
verification.TrustStatus = "trusted"
|
||||
|
||||
if verification.SigningUser.ID == 0 {
|
||||
// This commit is signed by the default key - but this key is not assigned to a user in the DB.
|
||||
|
||||
// However in the repo_model.CollaboratorCommitterTrustModel we cannot mark this as trusted
|
||||
// unless the default key matches the email of a non-user.
|
||||
if repoTrustModel == repo_model.CollaboratorCommitterTrustModel && (verification.CommittingUser.ID != 0 ||
|
||||
verification.SigningUser.Email != verification.CommittingUser.Email) {
|
||||
verification.TrustStatus = "untrusted"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check we actually have a GPG SigningKey
|
||||
var err error
|
||||
if verification.SigningKey != nil {
|
||||
var isMember bool
|
||||
if keyMap != nil {
|
||||
var has bool
|
||||
isMember, has = (*keyMap)[verification.SigningKey.KeyID]
|
||||
if !has {
|
||||
isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
|
||||
(*keyMap)[verification.SigningKey.KeyID] = isMember
|
||||
}
|
||||
} else {
|
||||
isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
|
||||
}
|
||||
|
||||
if !isMember {
|
||||
verification.TrustStatus = "untrusted"
|
||||
if verification.CommittingUser.ID != verification.SigningUser.ID {
|
||||
// The committing user and the signing user are not the same
|
||||
// This should be marked as questionable unless the signing user is a collaborator/team member etc.
|
||||
verification.TrustStatus = "unmatched"
|
||||
}
|
||||
} else if repoTrustModel == repo_model.CollaboratorCommitterTrustModel && verification.CommittingUser.ID != verification.SigningUser.ID {
|
||||
// The committing user and the signing user are not the same and our trustmodel states that they must match
|
||||
verification.TrustStatus = "unmatched"
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
15
models/asymkey/gpg_key_tag_verification.go
Normal file
15
models/asymkey/gpg_key_tag_verification.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package asymkey
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
)
|
||||
|
||||
func ParseTagWithSignature(ctx context.Context, gitRepo *git.Repository, t *git.Tag) *ObjectVerification {
|
||||
o := tagToGitObject(t, gitRepo)
|
||||
return ParseObjectWithSignature(ctx, &o)
|
||||
}
|
|
@ -198,6 +198,8 @@ func RegeneratePublicKeys(ctx context.Context, t io.StringWriter) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
@ -207,11 +209,12 @@ func RegeneratePublicKeys(ctx context.Context, t io.StringWriter) error {
|
|||
}
|
||||
_, err = t.WriteString(line + "\n")
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
}
|
||||
f.Close()
|
||||
if err = scanner.Err(); err != nil {
|
||||
return fmt.Errorf("RegeneratePublicKeys scan: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -120,6 +120,8 @@ func regeneratePrincipalKeys(ctx context.Context, t io.StringWriter) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
@ -129,11 +131,12 @@ func regeneratePrincipalKeys(ctx context.Context, t io.StringWriter) error {
|
|||
}
|
||||
_, err = t.WriteString(line + "\n")
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
}
|
||||
f.Close()
|
||||
if err = scanner.Err(); err != nil {
|
||||
return fmt.Errorf("regeneratePrincipalKeys scan: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -11,14 +11,13 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
"github.com/42wim/sshsig"
|
||||
)
|
||||
|
||||
// ParseCommitWithSSHSignature check if signature is good against keystore.
|
||||
func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *user_model.User) *CommitVerification {
|
||||
// ParseObjectWithSSHSignature check if signature is good against keystore.
|
||||
func ParseObjectWithSSHSignature(ctx context.Context, c *GitObject, committer *user_model.User) *ObjectVerification {
|
||||
// Now try to associate the signature with the committer, if present
|
||||
if committer.ID != 0 {
|
||||
keys, err := db.Find[PublicKey](ctx, FindPublicKeyOptions{
|
||||
|
@ -27,7 +26,7 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
|
|||
})
|
||||
if err != nil { // Skipping failed to get ssh keys of user
|
||||
log.Error("ListPublicKeys: %v", err)
|
||||
return &CommitVerification{
|
||||
return &ObjectVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
||||
|
@ -55,7 +54,7 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
|
|||
|
||||
for _, k := range keys {
|
||||
if k.Verified && activated {
|
||||
commitVerification := verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, k, committer, committer, c.Committer.Email)
|
||||
commitVerification := verifySSHObjectVerification(c.Signature.Signature, c.Signature.Payload, k, committer, committer, c.Committer.Email)
|
||||
if commitVerification != nil {
|
||||
return commitVerification
|
||||
}
|
||||
|
@ -63,19 +62,19 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
|
|||
}
|
||||
}
|
||||
|
||||
return &CommitVerification{
|
||||
return &ObjectVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
Reason: NoKeyFound,
|
||||
}
|
||||
}
|
||||
|
||||
func verifySSHCommitVerification(sig, payload string, k *PublicKey, committer, signer *user_model.User, email string) *CommitVerification {
|
||||
func verifySSHObjectVerification(sig, payload string, k *PublicKey, committer, signer *user_model.User, email string) *ObjectVerification {
|
||||
if err := sshsig.Verify(bytes.NewBuffer([]byte(payload)), []byte(sig), []byte(k.Content), "git"); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &CommitVerification{ // Everything is ok
|
||||
return &ObjectVerification{ // Everything is ok
|
||||
CommittingUser: committer,
|
||||
Verified: true,
|
||||
Reason: fmt.Sprintf("%s / %s", signer.Name, k.Fingerprint),
|
|
@ -22,7 +22,8 @@ func TestParseCommitWithSSHSignature(t *testing.T) {
|
|||
sshKey := unittest.AssertExistsAndLoadBean(t, &PublicKey{ID: 1000, OwnerID: 2})
|
||||
|
||||
t.Run("No commiter", func(t *testing.T) {
|
||||
commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, &git.Commit{}, &user_model.User{})
|
||||
o := commitToGitObject(&git.Commit{})
|
||||
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, &user_model.User{})
|
||||
assert.False(t, commitVerification.Verified)
|
||||
assert.Equal(t, NoKeyFound, commitVerification.Reason)
|
||||
})
|
||||
|
@ -30,7 +31,8 @@ func TestParseCommitWithSSHSignature(t *testing.T) {
|
|||
t.Run("Commiter without keys", func(t *testing.T) {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, &git.Commit{Committer: &git.Signature{Email: user.Email}}, user)
|
||||
o := commitToGitObject(&git.Commit{Committer: &git.Signature{Email: user.Email}})
|
||||
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user)
|
||||
assert.False(t, commitVerification.Verified)
|
||||
assert.Equal(t, NoKeyFound, commitVerification.Reason)
|
||||
})
|
||||
|
@ -40,7 +42,7 @@ func TestParseCommitWithSSHSignature(t *testing.T) {
|
|||
Committer: &git.Signature{
|
||||
Email: "non-existent",
|
||||
},
|
||||
Signature: &git.CommitGPGSignature{
|
||||
Signature: &git.ObjectSignature{
|
||||
Payload: `tree 2d491b2985a7ff848d5c02748e7ea9f9f7619f9f
|
||||
parent 45b03601635a1f463b81963a4022c7f87ce96ef9
|
||||
author user2 <non-existent> 1699710556 +0100
|
||||
|
@ -57,7 +59,8 @@ AAAAQIMufOuSjZeDUujrkVK4sl7ICa0WwEftas8UAYxx0Thdkiw2qWjR1U1PKfTLm16/w8
|
|||
`,
|
||||
},
|
||||
}
|
||||
commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
|
||||
o := commitToGitObject(gitCommit)
|
||||
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user2)
|
||||
assert.False(t, commitVerification.Verified)
|
||||
assert.Equal(t, NoKeyFound, commitVerification.Reason)
|
||||
})
|
||||
|
@ -67,7 +70,7 @@ AAAAQIMufOuSjZeDUujrkVK4sl7ICa0WwEftas8UAYxx0Thdkiw2qWjR1U1PKfTLm16/w8
|
|||
Committer: &git.Signature{
|
||||
Email: "user2@example.com",
|
||||
},
|
||||
Signature: &git.CommitGPGSignature{
|
||||
Signature: &git.ObjectSignature{
|
||||
Payload: `tree 853694aae8816094a0d875fee7ea26278dbf5d0f
|
||||
parent c2780d5c313da2a947eae22efd7dacf4213f4e7f
|
||||
author user2 <user2@example.com> 1699707877 +0100
|
||||
|
@ -79,7 +82,8 @@ Add content
|
|||
},
|
||||
}
|
||||
|
||||
commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
|
||||
o := commitToGitObject(gitCommit)
|
||||
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user2)
|
||||
assert.False(t, commitVerification.Verified)
|
||||
assert.Equal(t, NoKeyFound, commitVerification.Reason)
|
||||
})
|
||||
|
@ -89,7 +93,7 @@ Add content
|
|||
Committer: &git.Signature{
|
||||
Email: "user2@example.com",
|
||||
},
|
||||
Signature: &git.CommitGPGSignature{
|
||||
Signature: &git.ObjectSignature{
|
||||
Payload: `tree 853694aae8816094a0d875fee7ea26278dbf5d0f
|
||||
parent c2780d5c313da2a947eae22efd7dacf4213f4e7f
|
||||
author user2 <user2@example.com> 1699707877 +0100
|
||||
|
@ -107,7 +111,8 @@ fs9cMpZVM9BfIKNUSO8QY=
|
|||
},
|
||||
}
|
||||
|
||||
commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
|
||||
o := commitToGitObject(gitCommit)
|
||||
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user2)
|
||||
assert.True(t, commitVerification.Verified)
|
||||
assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason)
|
||||
assert.Equal(t, sshKey, commitVerification.SigningSSHKey)
|
||||
|
@ -120,7 +125,7 @@ fs9cMpZVM9BfIKNUSO8QY=
|
|||
Committer: &git.Signature{
|
||||
Email: "user2@noreply.example.com",
|
||||
},
|
||||
Signature: &git.CommitGPGSignature{
|
||||
Signature: &git.ObjectSignature{
|
||||
Payload: `tree 4836c7f639f37388bab4050ef5c97bbbd54272fc
|
||||
parent 795be1b0117ea5c65456050bb9fd84744d4fd9c6
|
||||
author user2 <user2@noreply.example.com> 1699709594 +0100
|
||||
|
@ -138,7 +143,8 @@ muPLbvEduU+Ze/1Ol1pgk=
|
|||
},
|
||||
}
|
||||
|
||||
commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
|
||||
o := commitToGitObject(gitCommit)
|
||||
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user2)
|
||||
assert.True(t, commitVerification.Verified)
|
||||
assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason)
|
||||
assert.Equal(t, sshKey, commitVerification.SigningSSHKey)
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
|
@ -243,14 +244,14 @@ func CreateSource(ctx context.Context, source *Source) error {
|
|||
|
||||
type FindSourcesOptions struct {
|
||||
db.ListOptions
|
||||
IsActive util.OptionalBool
|
||||
IsActive optional.Option[bool]
|
||||
LoginType Type
|
||||
}
|
||||
|
||||
func (opts FindSourcesOptions) ToConds() builder.Cond {
|
||||
conds := builder.NewCond()
|
||||
if !opts.IsActive.IsNone() {
|
||||
conds = conds.And(builder.Eq{"is_active": opts.IsActive.IsTrue()})
|
||||
if opts.IsActive.Has() {
|
||||
conds = conds.And(builder.Eq{"is_active": opts.IsActive.Value()})
|
||||
}
|
||||
if opts.LoginType != NoType {
|
||||
conds = conds.And(builder.Eq{"`type`": opts.LoginType})
|
||||
|
@ -262,7 +263,7 @@ func (opts FindSourcesOptions) ToConds() builder.Cond {
|
|||
// source of type LoginSSPI
|
||||
func IsSSPIEnabled(ctx context.Context) bool {
|
||||
exist, err := db.Exist[Source](ctx, FindSourcesOptions{
|
||||
IsActive: util.OptionalBoolTrue,
|
||||
IsActive: optional.Some(true),
|
||||
LoginType: SSPI,
|
||||
}.ToConds())
|
||||
if err != nil {
|
||||
|
|
|
@ -24,7 +24,7 @@ import (
|
|||
|
||||
const (
|
||||
// DefaultAvatarClass is the default class of a rendered avatar
|
||||
DefaultAvatarClass = "ui avatar gt-vm"
|
||||
DefaultAvatarClass = "ui avatar tw-align-middle"
|
||||
// DefaultAvatarPixelSize is the default size in pixels of a rendered avatar
|
||||
DefaultAvatarPixelSize = 28
|
||||
)
|
||||
|
|
|
@ -166,8 +166,7 @@ func preprocessDatabaseCollation(x *xorm.Engine) {
|
|||
|
||||
// try to alter database collation to expected if the database is empty, it might fail in some cases (and it isn't necessary to succeed)
|
||||
// at the moment, there is no "altering" solution for MSSQL, site admin should manually change the database collation
|
||||
// and there is a bug https://github.com/go-testfixtures/testfixtures/pull/182 mssql: Invalid object name 'information_schema.tables'.
|
||||
if !r.CollationEquals(r.DatabaseCollation, r.ExpectedCollation) && r.ExistingTableNumber == 0 && x.Dialect().URI().DBType == schemas.MYSQL {
|
||||
if !r.CollationEquals(r.DatabaseCollation, r.ExpectedCollation) && r.ExistingTableNumber == 0 {
|
||||
if err = alterDatabaseCollation(x, r.ExpectedCollation); err != nil {
|
||||
log.Error("Failed to change database collation to %q: %v", r.ExpectedCollation, err)
|
||||
} else {
|
||||
|
|
|
@ -120,6 +120,16 @@ func (c *halfCommitter) Close() error {
|
|||
|
||||
// TxContext represents a transaction Context,
|
||||
// it will reuse the existing transaction in the parent context or create a new one.
|
||||
// Some tips to use:
|
||||
//
|
||||
// 1 It's always recommended to use `WithTx` in new code instead of `TxContext`, since `WithTx` will handle the transaction automatically.
|
||||
// 2. To maintain the old code which uses `TxContext`:
|
||||
// a. Always call `Close()` before returning regardless of whether `Commit()` has been called.
|
||||
// b. Always call `Commit()` before returning if there are no errors, even if the code did not change any data.
|
||||
// c. Remember the `Committer` will be a halfCommitter when a transaction is being reused.
|
||||
// So calling `Commit()` will do nothing, but calling `Close()` without calling `Commit()` will rollback the transaction.
|
||||
// And all operations submitted by the caller stack will be rollbacked as well, not only the operations in the current function.
|
||||
// d. It doesn't mean rollback is forbidden, but always do it only when there is an error, and you do want to rollback.
|
||||
func TxContext(parentCtx context.Context) (*Context, Committer, error) {
|
||||
if sess, ok := inTransaction(parentCtx); ok {
|
||||
return newContext(parentCtx, sess, true), &halfCommitter{committer: sess}, nil
|
||||
|
|
|
@ -146,6 +146,7 @@ func InitEngine(ctx context.Context) error {
|
|||
xormEngine.SetMaxOpenConns(setting.Database.MaxOpenConns)
|
||||
xormEngine.SetMaxIdleConns(setting.Database.MaxIdleConns)
|
||||
xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
|
||||
xormEngine.SetConnMaxIdleTime(setting.Database.ConnMaxIdleTime)
|
||||
xormEngine.SetDefaultContext(ctx)
|
||||
|
||||
if setting.Database.SlowQueryThreshold > 0 {
|
||||
|
@ -154,8 +155,14 @@ func InitEngine(ctx context.Context) error {
|
|||
Logger: log.GetLogger("xorm"),
|
||||
})
|
||||
}
|
||||
|
||||
errorLogger := log.GetLogger("xorm")
|
||||
if setting.IsInTesting {
|
||||
errorLogger = log.GetLogger(log.DEFAULT)
|
||||
}
|
||||
|
||||
xormEngine.AddHook(&ErrorQueryHook{
|
||||
Logger: log.GetLogger("xorm"),
|
||||
Logger: errorLogger,
|
||||
})
|
||||
|
||||
SetDefaultEngine(ctx, xormEngine)
|
||||
|
|
|
@ -135,3 +135,27 @@
|
|||
user_id: 31
|
||||
repo_id: 28
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 24
|
||||
user_id: 38
|
||||
repo_id: 60
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 25
|
||||
user_id: 38
|
||||
repo_id: 61
|
||||
mode: 1
|
||||
|
||||
-
|
||||
id: 26
|
||||
user_id: 39
|
||||
repo_id: 61
|
||||
mode: 1
|
||||
|
||||
-
|
||||
id: 27
|
||||
user_id: 40
|
||||
repo_id: 61
|
||||
mode: 4
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
repo_id: 2 # private
|
||||
is_private: true
|
||||
created_unix: 1603228283
|
||||
content: '1|' # issueId 4
|
||||
|
||||
-
|
||||
id: 2
|
||||
|
|
|
@ -17,3 +17,399 @@
|
|||
updated: 1683636626
|
||||
need_approval: 0
|
||||
approved_by: 0
|
||||
event_payload: |
|
||||
{
|
||||
"after": "7a3858dc7f059543a8807a8b551304b7e362a7ef",
|
||||
"before": "0000000000000000000000000000000000000000",
|
||||
"commits": [
|
||||
{
|
||||
"added": [
|
||||
".forgejo/workflows/test.yml"
|
||||
],
|
||||
"author": {
|
||||
"email": "root@example.com",
|
||||
"name": "username",
|
||||
"username": "root"
|
||||
},
|
||||
"committer": {
|
||||
"email": "root@example.com",
|
||||
"name": "username",
|
||||
"username": "root"
|
||||
},
|
||||
"id": "7a3858dc7f059543a8807a8b551304b7e362a7ef",
|
||||
"message": "initial commit\n",
|
||||
"modified": [],
|
||||
"removed": [],
|
||||
"timestamp": "2024-01-24T18:59:25Z",
|
||||
"url": "http://10.201.14.40:3000/root/example-push/commit/7a3858dc7f059543a8807a8b551304b7e362a7ef",
|
||||
"verification": null
|
||||
}
|
||||
],
|
||||
"compare_url": "http://10.201.14.40:3000/",
|
||||
"head_commit": {
|
||||
"added": [
|
||||
".forgejo/workflows/test.yml"
|
||||
],
|
||||
"author": {
|
||||
"email": "root@example.com",
|
||||
"name": "username",
|
||||
"username": "root"
|
||||
},
|
||||
"committer": {
|
||||
"email": "root@example.com",
|
||||
"name": "username",
|
||||
"username": "root"
|
||||
},
|
||||
"id": "7a3858dc7f059543a8807a8b551304b7e362a7ef",
|
||||
"message": "initial commit\n",
|
||||
"modified": [],
|
||||
"removed": [],
|
||||
"timestamp": "2024-01-24T18:59:25Z",
|
||||
"url": "http://10.201.14.40:3000/root/example-push/commit/7a3858dc7f059543a8807a8b551304b7e362a7ef",
|
||||
"verification": null
|
||||
},
|
||||
"pusher": {
|
||||
"active": false,
|
||||
"avatar_url": "http://10.201.14.40:3000/avatars/04edfc0ef6c6cf6d6b88fbc69f9f9071",
|
||||
"created": "2024-01-24T18:57:32Z",
|
||||
"description": "",
|
||||
"email": "root@noreply.10.201.14.40",
|
||||
"followers_count": 0,
|
||||
"following_count": 0,
|
||||
"full_name": "",
|
||||
"id": 1,
|
||||
"is_admin": false,
|
||||
"language": "",
|
||||
"last_login": "0001-01-01T00:00:00Z",
|
||||
"location": "",
|
||||
"login": "root",
|
||||
"login_name": "",
|
||||
"prohibit_login": false,
|
||||
"restricted": false,
|
||||
"starred_repos_count": 0,
|
||||
"username": "root",
|
||||
"visibility": "public",
|
||||
"website": ""
|
||||
},
|
||||
"ref": "refs/heads/main",
|
||||
"repository": {
|
||||
"allow_merge_commits": true,
|
||||
"allow_rebase": true,
|
||||
"allow_rebase_explicit": true,
|
||||
"allow_rebase_update": true,
|
||||
"allow_squash_merge": true,
|
||||
"archived": false,
|
||||
"archived_at": "1970-01-01T00:00:00Z",
|
||||
"avatar_url": "",
|
||||
"clone_url": "http://10.201.14.40:3000/root/example-push.git",
|
||||
"created_at": "2024-01-24T18:59:25Z",
|
||||
"default_allow_maintainer_edit": false,
|
||||
"default_branch": "main",
|
||||
"default_delete_branch_after_merge": false,
|
||||
"default_merge_style": "merge",
|
||||
"description": "",
|
||||
"empty": false,
|
||||
"fork": false,
|
||||
"forks_count": 0,
|
||||
"full_name": "root/example-push",
|
||||
"has_actions": true,
|
||||
"has_issues": true,
|
||||
"has_packages": true,
|
||||
"has_projects": true,
|
||||
"has_pull_requests": true,
|
||||
"has_releases": true,
|
||||
"has_wiki": true,
|
||||
"html_url": "http://10.201.14.40:3000/root/example-push",
|
||||
"id": 2,
|
||||
"ignore_whitespace_conflicts": false,
|
||||
"internal": false,
|
||||
"internal_tracker": {
|
||||
"allow_only_contributors_to_track_time": true,
|
||||
"enable_issue_dependencies": true,
|
||||
"enable_time_tracker": true
|
||||
},
|
||||
"language": "",
|
||||
"languages_url": "http://10.201.14.40:3000/api/v1/repos/root/example-push/languages",
|
||||
"link": "",
|
||||
"mirror": false,
|
||||
"mirror_interval": "",
|
||||
"mirror_updated": "0001-01-01T00:00:00Z",
|
||||
"name": "example-push",
|
||||
"object_format_name": "",
|
||||
"open_issues_count": 0,
|
||||
"open_pr_counter": 0,
|
||||
"original_url": "",
|
||||
"owner": {
|
||||
"active": false,
|
||||
"avatar_url": "http://10.201.14.40:3000/avatars/04edfc0ef6c6cf6d6b88fbc69f9f9071",
|
||||
"created": "2024-01-24T18:57:32Z",
|
||||
"description": "",
|
||||
"email": "root@example.com",
|
||||
"followers_count": 0,
|
||||
"following_count": 0,
|
||||
"full_name": "",
|
||||
"id": 1,
|
||||
"is_admin": false,
|
||||
"language": "",
|
||||
"last_login": "0001-01-01T00:00:00Z",
|
||||
"location": "",
|
||||
"login": "root",
|
||||
"login_name": "",
|
||||
"prohibit_login": false,
|
||||
"restricted": false,
|
||||
"starred_repos_count": 0,
|
||||
"username": "root",
|
||||
"visibility": "public",
|
||||
"website": ""
|
||||
},
|
||||
"parent": null,
|
||||
"permissions": {
|
||||
"admin": true,
|
||||
"pull": true,
|
||||
"push": true
|
||||
},
|
||||
"private": false,
|
||||
"release_counter": 0,
|
||||
"repo_transfer": null,
|
||||
"size": 25,
|
||||
"ssh_url": "forgejo@10.201.14.40:root/example-push.git",
|
||||
"stars_count": 0,
|
||||
"template": false,
|
||||
"updated_at": "2024-01-24T18:59:25Z",
|
||||
"url": "http://10.201.14.40:3000/api/v1/repos/root/example-push",
|
||||
"watchers_count": 1,
|
||||
"website": ""
|
||||
},
|
||||
"sender": {
|
||||
"active": false,
|
||||
"avatar_url": "http://10.201.14.40:3000/avatars/04edfc0ef6c6cf6d6b88fbc69f9f9071",
|
||||
"created": "2024-01-24T18:57:32Z",
|
||||
"description": "",
|
||||
"email": "root@noreply.10.201.14.40",
|
||||
"followers_count": 0,
|
||||
"following_count": 0,
|
||||
"full_name": "",
|
||||
"id": 1,
|
||||
"is_admin": false,
|
||||
"language": "",
|
||||
"last_login": "0001-01-01T00:00:00Z",
|
||||
"location": "",
|
||||
"login": "root",
|
||||
"login_name": "",
|
||||
"prohibit_login": false,
|
||||
"restricted": false,
|
||||
"starred_repos_count": 0,
|
||||
"username": "root",
|
||||
"visibility": "public",
|
||||
"website": ""
|
||||
},
|
||||
"total_commits": 0
|
||||
}
|
||||
|
||||
-
|
||||
id: 792
|
||||
title: "update actions"
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
workflow_id: "artifact.yaml"
|
||||
index: 188
|
||||
trigger_user_id: 1
|
||||
ref: "refs/heads/master"
|
||||
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
||||
event: "push"
|
||||
is_fork_pull_request: 0
|
||||
status: 1
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
created: 1683636108
|
||||
updated: 1683636626
|
||||
need_approval: 0
|
||||
approved_by: 0
|
||||
event_payload: |
|
||||
{
|
||||
"after": "7a3858dc7f059543a8807a8b551304b7e362a7ef",
|
||||
"before": "0000000000000000000000000000000000000000",
|
||||
"commits": [
|
||||
{
|
||||
"added": [
|
||||
".forgejo/workflows/test.yml"
|
||||
],
|
||||
"author": {
|
||||
"email": "root@example.com",
|
||||
"name": "username",
|
||||
"username": "root"
|
||||
},
|
||||
"committer": {
|
||||
"email": "root@example.com",
|
||||
"name": "username",
|
||||
"username": "root"
|
||||
},
|
||||
"id": "7a3858dc7f059543a8807a8b551304b7e362a7ef",
|
||||
"message": "initial commit\n",
|
||||
"modified": [],
|
||||
"removed": [],
|
||||
"timestamp": "2024-01-24T18:59:25Z",
|
||||
"url": "http://10.201.14.40:3000/root/example-push/commit/7a3858dc7f059543a8807a8b551304b7e362a7ef",
|
||||
"verification": null
|
||||
}
|
||||
],
|
||||
"compare_url": "http://10.201.14.40:3000/",
|
||||
"head_commit": {
|
||||
"added": [
|
||||
".forgejo/workflows/test.yml"
|
||||
],
|
||||
"author": {
|
||||
"email": "root@example.com",
|
||||
"name": "username",
|
||||
"username": "root"
|
||||
},
|
||||
"committer": {
|
||||
"email": "root@example.com",
|
||||
"name": "username",
|
||||
"username": "root"
|
||||
},
|
||||
"id": "7a3858dc7f059543a8807a8b551304b7e362a7ef",
|
||||
"message": "initial commit\n",
|
||||
"modified": [],
|
||||
"removed": [],
|
||||
"timestamp": "2024-01-24T18:59:25Z",
|
||||
"url": "http://10.201.14.40:3000/root/example-push/commit/7a3858dc7f059543a8807a8b551304b7e362a7ef",
|
||||
"verification": null
|
||||
},
|
||||
"pusher": {
|
||||
"active": false,
|
||||
"avatar_url": "http://10.201.14.40:3000/avatars/04edfc0ef6c6cf6d6b88fbc69f9f9071",
|
||||
"created": "2024-01-24T18:57:32Z",
|
||||
"description": "",
|
||||
"email": "root@noreply.10.201.14.40",
|
||||
"followers_count": 0,
|
||||
"following_count": 0,
|
||||
"full_name": "",
|
||||
"id": 1,
|
||||
"is_admin": false,
|
||||
"language": "",
|
||||
"last_login": "0001-01-01T00:00:00Z",
|
||||
"location": "",
|
||||
"login": "root",
|
||||
"login_name": "",
|
||||
"prohibit_login": false,
|
||||
"restricted": false,
|
||||
"starred_repos_count": 0,
|
||||
"username": "root",
|
||||
"visibility": "public",
|
||||
"website": ""
|
||||
},
|
||||
"ref": "refs/heads/main",
|
||||
"repository": {
|
||||
"allow_merge_commits": true,
|
||||
"allow_rebase": true,
|
||||
"allow_rebase_explicit": true,
|
||||
"allow_rebase_update": true,
|
||||
"allow_squash_merge": true,
|
||||
"archived": false,
|
||||
"archived_at": "1970-01-01T00:00:00Z",
|
||||
"avatar_url": "",
|
||||
"clone_url": "http://10.201.14.40:3000/root/example-push.git",
|
||||
"created_at": "2024-01-24T18:59:25Z",
|
||||
"default_allow_maintainer_edit": false,
|
||||
"default_branch": "main",
|
||||
"default_delete_branch_after_merge": false,
|
||||
"default_merge_style": "merge",
|
||||
"description": "",
|
||||
"empty": false,
|
||||
"fork": false,
|
||||
"forks_count": 0,
|
||||
"full_name": "root/example-push",
|
||||
"has_actions": true,
|
||||
"has_issues": true,
|
||||
"has_packages": true,
|
||||
"has_projects": true,
|
||||
"has_pull_requests": true,
|
||||
"has_releases": true,
|
||||
"has_wiki": true,
|
||||
"html_url": "http://10.201.14.40:3000/root/example-push",
|
||||
"id": 2,
|
||||
"ignore_whitespace_conflicts": false,
|
||||
"internal": false,
|
||||
"internal_tracker": {
|
||||
"allow_only_contributors_to_track_time": true,
|
||||
"enable_issue_dependencies": true,
|
||||
"enable_time_tracker": true
|
||||
},
|
||||
"language": "",
|
||||
"languages_url": "http://10.201.14.40:3000/api/v1/repos/root/example-push/languages",
|
||||
"link": "",
|
||||
"mirror": false,
|
||||
"mirror_interval": "",
|
||||
"mirror_updated": "0001-01-01T00:00:00Z",
|
||||
"name": "example-push",
|
||||
"object_format_name": "",
|
||||
"open_issues_count": 0,
|
||||
"open_pr_counter": 0,
|
||||
"original_url": "",
|
||||
"owner": {
|
||||
"active": false,
|
||||
"avatar_url": "http://10.201.14.40:3000/avatars/04edfc0ef6c6cf6d6b88fbc69f9f9071",
|
||||
"created": "2024-01-24T18:57:32Z",
|
||||
"description": "",
|
||||
"email": "root@example.com",
|
||||
"followers_count": 0,
|
||||
"following_count": 0,
|
||||
"full_name": "",
|
||||
"id": 1,
|
||||
"is_admin": false,
|
||||
"language": "",
|
||||
"last_login": "0001-01-01T00:00:00Z",
|
||||
"location": "",
|
||||
"login": "root",
|
||||
"login_name": "",
|
||||
"prohibit_login": false,
|
||||
"restricted": false,
|
||||
"starred_repos_count": 0,
|
||||
"username": "root",
|
||||
"visibility": "public",
|
||||
"website": ""
|
||||
},
|
||||
"parent": null,
|
||||
"permissions": {
|
||||
"admin": true,
|
||||
"pull": true,
|
||||
"push": true
|
||||
},
|
||||
"private": false,
|
||||
"release_counter": 0,
|
||||
"repo_transfer": null,
|
||||
"size": 25,
|
||||
"ssh_url": "forgejo@10.201.14.40:root/example-push.git",
|
||||
"stars_count": 0,
|
||||
"template": false,
|
||||
"updated_at": "2024-01-24T18:59:25Z",
|
||||
"url": "http://10.201.14.40:3000/api/v1/repos/root/example-push",
|
||||
"watchers_count": 1,
|
||||
"website": ""
|
||||
},
|
||||
"sender": {
|
||||
"active": false,
|
||||
"avatar_url": "http://10.201.14.40:3000/avatars/04edfc0ef6c6cf6d6b88fbc69f9f9071",
|
||||
"created": "2024-01-24T18:57:32Z",
|
||||
"description": "",
|
||||
"email": "root@noreply.10.201.14.40",
|
||||
"followers_count": 0,
|
||||
"following_count": 0,
|
||||
"full_name": "",
|
||||
"id": 1,
|
||||
"is_admin": false,
|
||||
"language": "",
|
||||
"last_login": "0001-01-01T00:00:00Z",
|
||||
"location": "",
|
||||
"login": "root",
|
||||
"login_name": "",
|
||||
"prohibit_login": false,
|
||||
"restricted": false,
|
||||
"starred_repos_count": 0,
|
||||
"username": "root",
|
||||
"visibility": "public",
|
||||
"website": ""
|
||||
},
|
||||
"total_commits": 0
|
||||
}
|
||||
|
|
|
@ -12,3 +12,17 @@
|
|||
status: 1
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
-
|
||||
id: 193
|
||||
run_id: 792
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: 0
|
||||
name: job_2
|
||||
attempt: 1
|
||||
job_id: job_2
|
||||
task_id: 48
|
||||
status: 1
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
|
|
|
@ -18,3 +18,23 @@
|
|||
log_length: 707
|
||||
log_size: 90179
|
||||
log_expired: 0
|
||||
-
|
||||
id: 48
|
||||
job_id: 193
|
||||
attempt: 1
|
||||
runner_id: 1
|
||||
status: 6 # 6 is the status code for "running", running task can upload artifacts
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: 0
|
||||
token_hash: ffffcfffffffbffffffffffffffffefffffffafffffffffffffffffffffffffffffdffffffffffffffffffffffffffffffff
|
||||
token_salt: ffffffffff
|
||||
token_last_eight: ffffffff
|
||||
log_filename: artifact-test2/2f/47.log
|
||||
log_in_storage: 1
|
||||
log_length: 707
|
||||
log_size: 90179
|
||||
log_expired: 0
|
||||
|
|
|
@ -45,3 +45,9 @@
|
|||
repo_id: 22
|
||||
user_id: 18
|
||||
mode: 2 # write
|
||||
|
||||
-
|
||||
id: 9
|
||||
repo_id: 60
|
||||
user_id: 38
|
||||
mode: 2 # write
|
||||
|
|
|
@ -75,3 +75,11 @@
|
|||
content: "comment in private pository"
|
||||
created_unix: 946684811
|
||||
updated_unix: 946684811
|
||||
|
||||
-
|
||||
id: 9
|
||||
type: 22 # review
|
||||
poster_id: 2
|
||||
issue_id: 2 # in repo_id 1
|
||||
review_id: 20
|
||||
created_unix: 946684810
|
||||
|
|
|
@ -293,3 +293,27 @@
|
|||
lower_email: user37@example.com
|
||||
is_activated: true
|
||||
is_primary: true
|
||||
|
||||
-
|
||||
id: 38
|
||||
uid: 38
|
||||
email: user38@example.com
|
||||
lower_email: user38@example.com
|
||||
is_activated: true
|
||||
is_primary: true
|
||||
|
||||
-
|
||||
id: 39
|
||||
uid: 39
|
||||
email: user39@example.com
|
||||
lower_email: user39@example.com
|
||||
is_activated: true
|
||||
is_primary: true
|
||||
|
||||
-
|
||||
id: 40
|
||||
uid: 40
|
||||
email: user40@example.com
|
||||
lower_email: user40@example.com
|
||||
is_activated: true
|
||||
is_primary: true
|
||||
|
|
|
@ -3,3 +3,35 @@
|
|||
hook_id: 1
|
||||
uuid: uuid1
|
||||
is_delivered: true
|
||||
is_succeed: false
|
||||
request_content: >
|
||||
{
|
||||
"url": "/matrix-delivered",
|
||||
"http_method":"PUT",
|
||||
"headers": {
|
||||
"X-Head": "42"
|
||||
},
|
||||
"body": "{}"
|
||||
}
|
||||
|
||||
-
|
||||
id: 2
|
||||
hook_id: 1
|
||||
uuid: uuid2
|
||||
is_delivered: false
|
||||
|
||||
-
|
||||
id: 3
|
||||
hook_id: 1
|
||||
uuid: uuid3
|
||||
is_delivered: true
|
||||
is_succeed: true
|
||||
payload_content: '{"key":"value"}' # legacy task, payload saved in payload_content (and not in request_content)
|
||||
request_content: >
|
||||
{
|
||||
"url": "/matrix-success",
|
||||
"http_method":"PUT",
|
||||
"headers": {
|
||||
"X-Head": "42"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -338,3 +338,37 @@
|
|||
created_unix: 978307210
|
||||
updated_unix: 978307210
|
||||
is_locked: false
|
||||
|
||||
-
|
||||
id: 21
|
||||
repo_id: 60
|
||||
index: 1
|
||||
poster_id: 39
|
||||
original_author_id: 0
|
||||
name: repo60 pull1
|
||||
content: content for the 1st issue
|
||||
milestone_id: 0
|
||||
priority: 0
|
||||
is_closed: false
|
||||
is_pull: true
|
||||
num_comments: 0
|
||||
created_unix: 1707270422
|
||||
updated_unix: 1707270422
|
||||
is_locked: false
|
||||
|
||||
-
|
||||
id: 22
|
||||
repo_id: 61
|
||||
index: 1
|
||||
poster_id: 40
|
||||
original_author_id: 0
|
||||
name: repo61 pull1
|
||||
content: content for the 1st issue
|
||||
milestone_id: 0
|
||||
priority: 0
|
||||
is_closed: false
|
||||
is_pull: true
|
||||
num_comments: 0
|
||||
created_unix: 1707270422
|
||||
updated_unix: 1707270422
|
||||
is_locked: false
|
||||
|
|
|
@ -99,3 +99,21 @@
|
|||
uid: 5
|
||||
org_id: 36
|
||||
is_public: true
|
||||
|
||||
-
|
||||
id: 18
|
||||
uid: 38
|
||||
org_id: 41
|
||||
is_public: true
|
||||
|
||||
-
|
||||
id: 19
|
||||
uid: 39
|
||||
org_id: 41
|
||||
is_public: true
|
||||
|
||||
-
|
||||
id: 20
|
||||
uid: 40
|
||||
org_id: 41
|
||||
is_public: true
|
||||
|
|
|
@ -45,3 +45,27 @@
|
|||
type: 2
|
||||
created_unix: 1688973000
|
||||
updated_unix: 1688973000
|
||||
|
||||
-
|
||||
id: 5
|
||||
title: project without default column
|
||||
owner_id: 2
|
||||
repo_id: 0
|
||||
is_closed: false
|
||||
creator_id: 2
|
||||
board_type: 1
|
||||
type: 2
|
||||
created_unix: 1688973000
|
||||
updated_unix: 1688973000
|
||||
|
||||
-
|
||||
id: 6
|
||||
title: project with multiple default columns
|
||||
owner_id: 2
|
||||
repo_id: 0
|
||||
is_closed: false
|
||||
creator_id: 2
|
||||
board_type: 1
|
||||
type: 2
|
||||
created_unix: 1688973000
|
||||
updated_unix: 1688973000
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
project_id: 1
|
||||
title: To Do
|
||||
creator_id: 2
|
||||
default: true
|
||||
created_unix: 1588117528
|
||||
updated_unix: 1588117528
|
||||
|
||||
|
@ -29,3 +30,48 @@
|
|||
creator_id: 2
|
||||
created_unix: 1588117528
|
||||
updated_unix: 1588117528
|
||||
|
||||
-
|
||||
id: 5
|
||||
project_id: 2
|
||||
title: Backlog
|
||||
creator_id: 2
|
||||
default: true
|
||||
created_unix: 1588117528
|
||||
updated_unix: 1588117528
|
||||
|
||||
-
|
||||
id: 6
|
||||
project_id: 4
|
||||
title: Backlog
|
||||
creator_id: 2
|
||||
default: true
|
||||
created_unix: 1588117528
|
||||
updated_unix: 1588117528
|
||||
|
||||
-
|
||||
id: 7
|
||||
project_id: 5
|
||||
title: Done
|
||||
creator_id: 2
|
||||
default: false
|
||||
created_unix: 1588117528
|
||||
updated_unix: 1588117528
|
||||
|
||||
-
|
||||
id: 8
|
||||
project_id: 6
|
||||
title: Backlog
|
||||
creator_id: 2
|
||||
default: true
|
||||
created_unix: 1588117528
|
||||
updated_unix: 1588117528
|
||||
|
||||
-
|
||||
id: 9
|
||||
project_id: 6
|
||||
title: Uncategorized
|
||||
creator_id: 2
|
||||
default: true
|
||||
created_unix: 1588117528
|
||||
updated_unix: 1588117528
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
head_branch: branch1
|
||||
base_branch: master
|
||||
merge_base: 4a357436d925b5c974181ff12a994538ddc5a269
|
||||
merged_commit_id: 1a8823cd1a9549fde083f992f6b9b87a7ab74fb3
|
||||
has_merged: true
|
||||
merger_id: 2
|
||||
|
||||
|
@ -98,3 +99,21 @@
|
|||
index: 1
|
||||
head_repo_id: 23
|
||||
base_repo_id: 23
|
||||
|
||||
-
|
||||
id: 9
|
||||
type: 0 # gitea pull request
|
||||
status: 2 # mergable
|
||||
issue_id: 21
|
||||
index: 1
|
||||
head_repo_id: 60
|
||||
base_repo_id: 60
|
||||
|
||||
-
|
||||
id: 10
|
||||
type: 0 # gitea pull request
|
||||
status: 2 # mergable
|
||||
issue_id: 22
|
||||
index: 1
|
||||
head_repo_id: 61
|
||||
base_repo_id: 61
|
||||
|
|
|
@ -708,3 +708,45 @@
|
|||
type: 1
|
||||
config: "{}"
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 102
|
||||
repo_id: 60
|
||||
type: 1
|
||||
config: "{}"
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 103
|
||||
repo_id: 60
|
||||
type: 2
|
||||
config: "{\"EnableTimetracker\":true,\"AllowOnlyContributorsToTrackTime\":true}"
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 104
|
||||
repo_id: 60
|
||||
type: 3
|
||||
config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 105
|
||||
repo_id: 61
|
||||
type: 1
|
||||
config: "{}"
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 106
|
||||
repo_id: 61
|
||||
type: 2
|
||||
config: "{\"EnableTimetracker\":true,\"AllowOnlyContributorsToTrackTime\":true}"
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 107
|
||||
repo_id: 61
|
||||
type: 3
|
||||
config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
|
||||
created_unix: 946684810
|
||||
|
|
|
@ -1720,3 +1720,65 @@
|
|||
is_private: true
|
||||
status: 0
|
||||
num_issues: 0
|
||||
|
||||
-
|
||||
id: 60
|
||||
owner_id: 40
|
||||
owner_name: user40
|
||||
lower_name: repo60
|
||||
name: repo60
|
||||
default_branch: main
|
||||
num_watches: 0
|
||||
num_stars: 0
|
||||
num_forks: 0
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
||||
num_pulls: 1
|
||||
num_closed_pulls: 0
|
||||
num_milestones: 0
|
||||
num_closed_milestones: 0
|
||||
num_projects: 0
|
||||
num_closed_projects: 0
|
||||
is_private: false
|
||||
is_empty: false
|
||||
is_archived: false
|
||||
is_mirror: false
|
||||
status: 0
|
||||
is_fork: false
|
||||
fork_id: 0
|
||||
is_template: false
|
||||
template_id: 0
|
||||
size: 0
|
||||
is_fsck_enabled: true
|
||||
close_issues_via_commit_in_any_branch: false
|
||||
|
||||
-
|
||||
id: 61
|
||||
owner_id: 41
|
||||
owner_name: org41
|
||||
lower_name: repo61
|
||||
name: repo61
|
||||
default_branch: main
|
||||
num_watches: 0
|
||||
num_stars: 0
|
||||
num_forks: 0
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
||||
num_pulls: 1
|
||||
num_closed_pulls: 0
|
||||
num_milestones: 0
|
||||
num_closed_milestones: 0
|
||||
num_projects: 0
|
||||
num_closed_projects: 0
|
||||
is_private: false
|
||||
is_empty: false
|
||||
is_archived: false
|
||||
is_mirror: false
|
||||
status: 0
|
||||
is_fork: false
|
||||
fork_id: 0
|
||||
is_template: false
|
||||
template_id: 0
|
||||
size: 0
|
||||
is_fsck_enabled: true
|
||||
close_issues_via_commit_in_any_branch: false
|
||||
|
|
|
@ -170,3 +170,12 @@
|
|||
content: "review request for user15"
|
||||
updated_unix: 946684835
|
||||
created_unix: 946684835
|
||||
|
||||
-
|
||||
id: 20
|
||||
type: 22
|
||||
reviewer_id: 1
|
||||
issue_id: 2
|
||||
content: "Review Comment"
|
||||
updated_unix: 946684810
|
||||
created_unix: 946684810
|
||||
|
|
|
@ -217,3 +217,25 @@
|
|||
num_members: 1
|
||||
includes_all_repositories: false
|
||||
can_create_org_repo: true
|
||||
|
||||
-
|
||||
id: 21
|
||||
org_id: 41
|
||||
lower_name: owners
|
||||
name: Owners
|
||||
authorize: 4 # owner
|
||||
num_repos: 1
|
||||
num_members: 1
|
||||
includes_all_repositories: true
|
||||
can_create_org_repo: true
|
||||
|
||||
-
|
||||
id: 22
|
||||
org_id: 41
|
||||
lower_name: team1
|
||||
name: Team1
|
||||
authorize: 1 # read
|
||||
num_repos: 1
|
||||
num_members: 2
|
||||
includes_all_repositories: false
|
||||
can_create_org_repo: false
|
||||
|
|
|
@ -63,3 +63,15 @@
|
|||
org_id: 17
|
||||
team_id: 9
|
||||
repo_id: 24
|
||||
|
||||
-
|
||||
id: 12
|
||||
org_id: 41
|
||||
team_id: 21
|
||||
repo_id: 61
|
||||
|
||||
-
|
||||
id: 13
|
||||
org_id: 41
|
||||
team_id: 22
|
||||
repo_id: 61
|
||||
|
|
|
@ -286,3 +286,39 @@
|
|||
team_id: 2
|
||||
type: 8
|
||||
access_mode: 2
|
||||
|
||||
-
|
||||
id: 49
|
||||
team_id: 21
|
||||
type: 1
|
||||
access_mode: 4
|
||||
|
||||
-
|
||||
id: 50
|
||||
team_id: 21
|
||||
type: 2
|
||||
access_mode: 4
|
||||
|
||||
-
|
||||
id: 51
|
||||
team_id: 21
|
||||
type: 3
|
||||
access_mode: 4
|
||||
|
||||
-
|
||||
id: 52
|
||||
team_id: 22
|
||||
type: 1
|
||||
access_mode: 1
|
||||
|
||||
-
|
||||
id: 53
|
||||
team_id: 22
|
||||
type: 2
|
||||
access_mode: 1
|
||||
|
||||
-
|
||||
id: 54
|
||||
team_id: 22
|
||||
type: 3
|
||||
access_mode: 1
|
||||
|
|
|
@ -129,3 +129,21 @@
|
|||
org_id: 17
|
||||
team_id: 9
|
||||
uid: 15
|
||||
|
||||
-
|
||||
id: 23
|
||||
org_id: 41
|
||||
team_id: 21
|
||||
uid: 40
|
||||
|
||||
-
|
||||
id: 24
|
||||
org_id: 41
|
||||
team_id: 22
|
||||
uid: 38
|
||||
|
||||
-
|
||||
id: 25
|
||||
org_id: 41
|
||||
team_id: 22
|
||||
uid: 39
|
||||
|
|
|
@ -1369,3 +1369,151 @@
|
|||
repo_admin_change_team_access: false
|
||||
theme: ""
|
||||
keep_activity_private: false
|
||||
|
||||
-
|
||||
id: 38
|
||||
lower_name: user38
|
||||
name: user38
|
||||
full_name: User38
|
||||
email: user38@example.com
|
||||
keep_email_private: false
|
||||
email_notifications_preference: enabled
|
||||
passwd: ZogKvWdyEx:password
|
||||
passwd_hash_algo: dummy
|
||||
must_change_password: false
|
||||
login_source: 0
|
||||
login_name: user38
|
||||
type: 0
|
||||
salt: ZogKvWdyEx
|
||||
max_repo_creation: -1
|
||||
is_active: true
|
||||
is_admin: false
|
||||
is_restricted: false
|
||||
allow_git_hook: false
|
||||
allow_import_local: false
|
||||
allow_create_organization: true
|
||||
prohibit_login: false
|
||||
avatar: avatar38
|
||||
avatar_email: user38@example.com
|
||||
use_custom_avatar: false
|
||||
num_followers: 0
|
||||
num_following: 0
|
||||
num_stars: 0
|
||||
num_repos: 0
|
||||
num_teams: 0
|
||||
num_members: 0
|
||||
visibility: 0
|
||||
repo_admin_change_team_access: false
|
||||
theme: ""
|
||||
keep_activity_private: false
|
||||
|
||||
-
|
||||
id: 39
|
||||
lower_name: user39
|
||||
name: user39
|
||||
full_name: User39
|
||||
email: user39@example.com
|
||||
keep_email_private: false
|
||||
email_notifications_preference: enabled
|
||||
passwd: ZogKvWdyEx:password
|
||||
passwd_hash_algo: dummy
|
||||
must_change_password: false
|
||||
login_source: 0
|
||||
login_name: user39
|
||||
type: 0
|
||||
salt: ZogKvWdyEx
|
||||
max_repo_creation: -1
|
||||
is_active: true
|
||||
is_admin: false
|
||||
is_restricted: false
|
||||
allow_git_hook: false
|
||||
allow_import_local: false
|
||||
allow_create_organization: true
|
||||
prohibit_login: false
|
||||
avatar: avatar39
|
||||
avatar_email: user39@example.com
|
||||
use_custom_avatar: false
|
||||
num_followers: 0
|
||||
num_following: 0
|
||||
num_stars: 0
|
||||
num_repos: 0
|
||||
num_teams: 0
|
||||
num_members: 0
|
||||
visibility: 0
|
||||
repo_admin_change_team_access: false
|
||||
theme: ""
|
||||
keep_activity_private: false
|
||||
|
||||
-
|
||||
id: 40
|
||||
lower_name: user40
|
||||
name: user40
|
||||
full_name: User40
|
||||
email: user40@example.com
|
||||
keep_email_private: false
|
||||
email_notifications_preference: onmention
|
||||
passwd: ZogKvWdyEx:password
|
||||
passwd_hash_algo: dummy
|
||||
must_change_password: false
|
||||
login_source: 0
|
||||
login_name: user40
|
||||
type: 0
|
||||
salt: ZogKvWdyEx
|
||||
max_repo_creation: -1
|
||||
is_active: true
|
||||
is_admin: false
|
||||
is_restricted: false
|
||||
allow_git_hook: false
|
||||
allow_import_local: false
|
||||
allow_create_organization: true
|
||||
prohibit_login: false
|
||||
avatar: avatar40
|
||||
avatar_email: user40@example.com
|
||||
use_custom_avatar: false
|
||||
num_followers: 0
|
||||
num_following: 0
|
||||
num_stars: 0
|
||||
num_repos: 1
|
||||
num_teams: 0
|
||||
num_members: 0
|
||||
visibility: 0
|
||||
repo_admin_change_team_access: false
|
||||
theme: ""
|
||||
keep_activity_private: false
|
||||
|
||||
-
|
||||
id: 41
|
||||
lower_name: org41
|
||||
name: org41
|
||||
full_name: Org41
|
||||
email: org41@example.com
|
||||
keep_email_private: false
|
||||
email_notifications_preference: onmention
|
||||
passwd: ZogKvWdyEx:password
|
||||
passwd_hash_algo: dummy
|
||||
must_change_password: false
|
||||
login_source: 0
|
||||
login_name: org41
|
||||
type: 1
|
||||
salt: ZogKvWdyEx
|
||||
max_repo_creation: -1
|
||||
is_active: false
|
||||
is_admin: false
|
||||
is_restricted: false
|
||||
allow_git_hook: false
|
||||
allow_import_local: false
|
||||
allow_create_organization: true
|
||||
prohibit_login: false
|
||||
avatar: avatar41
|
||||
avatar_email: org41@example.com
|
||||
use_custom_avatar: false
|
||||
num_followers: 0
|
||||
num_following: 0
|
||||
num_stars: 0
|
||||
num_repos: 1
|
||||
num_teams: 2
|
||||
num_members: 3
|
||||
visibility: 0
|
||||
repo_admin_change_team_access: false
|
||||
theme: ""
|
||||
keep_activity_private: false
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
-
|
||||
id: 1
|
||||
repo_id: 1
|
||||
url: www.example.com/url1
|
||||
url: http://www.example.com/url1
|
||||
http_method: POST
|
||||
type: forgejo
|
||||
content_type: 1 # json
|
||||
events: '{"push_only":true,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":false}}'
|
||||
is_active: true
|
||||
is_active: false # disable to prevent sending hook task during unrelated tests
|
||||
|
||||
-
|
||||
id: 2
|
||||
repo_id: 1
|
||||
url: www.example.com/url2
|
||||
url: http://www.example.com/url2
|
||||
http_method: POST
|
||||
content_type: 1 # json
|
||||
events: '{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}'
|
||||
is_active: false
|
||||
|
@ -18,14 +21,16 @@
|
|||
id: 3
|
||||
owner_id: 3
|
||||
repo_id: 3
|
||||
url: www.example.com/url3
|
||||
url: http://www.example.com/url3
|
||||
http_method: POST
|
||||
content_type: 1 # json
|
||||
events: '{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}'
|
||||
is_active: true
|
||||
is_active: false
|
||||
-
|
||||
id: 4
|
||||
repo_id: 2
|
||||
url: www.example.com/url4
|
||||
url: http://www.example.com/url4
|
||||
http_method: POST
|
||||
content_type: 1 # json
|
||||
events: '{"push_only":true,"branch_filter":"{master,feature*}"}'
|
||||
is_active: true
|
||||
is_active: false
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue