Template
1
0
Fork 0
mirror of https://codeberg.org/forgejo/forgejo synced 2024-11-22 09:54:24 +01:00

Merge branch 'forgejo' into forgejo-federated-star

This commit is contained in:
erik 2024-04-09 10:42:42 +02:00
commit 97343470bc
687 changed files with 20874 additions and 7044 deletions

View file

@ -14,6 +14,7 @@ package "code.gitea.io/gitea/models"
func (ErrUpdateTaskNotExist).Error func (ErrUpdateTaskNotExist).Error
func (ErrUpdateTaskNotExist).Unwrap func (ErrUpdateTaskNotExist).Unwrap
func IsErrSHANotFound func IsErrSHANotFound
func IsErrMergeDivergingFastForwardOnly
func GetYamlFixturesAccess func GetYamlFixturesAccess
package "code.gitea.io/gitea/models/actions" package "code.gitea.io/gitea/models/actions"
@ -289,12 +290,14 @@ package "code.gitea.io/gitea/modules/timeutil"
package "code.gitea.io/gitea/modules/translation" package "code.gitea.io/gitea/modules/translation"
func (MockLocale).Language func (MockLocale).Language
func (MockLocale).TrString
func (MockLocale).Tr func (MockLocale).Tr
func (MockLocale).TrN func (MockLocale).TrN
func (MockLocale).PrettyNumber func (MockLocale).PrettyNumber
package "code.gitea.io/gitea/modules/util" package "code.gitea.io/gitea/modules/util"
func UnsafeStringToBytes func UnsafeStringToBytes
func OptionalBoolFromGeneric
package "code.gitea.io/gitea/modules/util/filebuffer" package "code.gitea.io/gitea/modules/util/filebuffer"
func CreateFromReader func CreateFromReader

View file

@ -71,7 +71,6 @@ cpu.out
/tests/e2e/test-artifacts /tests/e2e/test-artifacts
/tests/e2e/test-snapshots /tests/e2e/test-snapshots
/tests/*.ini /tests/*.ini
/node_modules
/yarn.lock /yarn.lock
/yarn-error.log /yarn-error.log
/npm-debug.log* /npm-debug.log*

View file

@ -12,6 +12,7 @@ plugins:
- "@eslint-community/eslint-plugin-eslint-comments" - "@eslint-community/eslint-plugin-eslint-comments"
- "@stylistic/eslint-plugin-js" - "@stylistic/eslint-plugin-js"
- eslint-plugin-array-func - eslint-plugin-array-func
- eslint-plugin-github
- eslint-plugin-i - eslint-plugin-i
- eslint-plugin-jquery - eslint-plugin-jquery
- eslint-plugin-no-jquery - eslint-plugin-no-jquery
@ -209,6 +210,29 @@ rules:
func-names: [0] func-names: [0]
func-style: [0] func-style: [0]
getter-return: [2] 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] grouped-accessor-pairs: [2]
guard-for-in: [0] guard-for-in: [0]
id-blacklist: [0] id-blacklist: [0]
@ -272,7 +296,7 @@ rules:
jquery/no-delegate: [2] jquery/no-delegate: [2]
jquery/no-each: [0] jquery/no-each: [0]
jquery/no-extend: [2] jquery/no-extend: [2]
jquery/no-fade: [0] jquery/no-fade: [2]
jquery/no-filter: [0] jquery/no-filter: [0]
jquery/no-find: [0] jquery/no-find: [0]
jquery/no-global-eval: [2] jquery/no-global-eval: [2]
@ -285,7 +309,7 @@ rules:
jquery/no-is-function: [2] jquery/no-is-function: [2]
jquery/no-is: [0] jquery/no-is: [0]
jquery/no-load: [2] jquery/no-load: [2]
jquery/no-map: [0] jquery/no-map: [2]
jquery/no-merge: [2] jquery/no-merge: [2]
jquery/no-param: [2] jquery/no-param: [2]
jquery/no-parent: [0] jquery/no-parent: [0]
@ -427,7 +451,7 @@ rules:
no-jquery/no-load: [2] no-jquery/no-load: [2]
no-jquery/no-map-collection: [0] no-jquery/no-map-collection: [0]
no-jquery/no-map-util: [2] no-jquery/no-map-util: [2]
no-jquery/no-map: [0] no-jquery/no-map: [2]
no-jquery/no-merge: [2] no-jquery/no-merge: [2]
no-jquery/no-node-name: [2] no-jquery/no-node-name: [2]
no-jquery/no-noop: [2] no-jquery/no-noop: [2]
@ -558,7 +582,6 @@ rules:
prefer-rest-params: [2] prefer-rest-params: [2]
prefer-spread: [2] prefer-spread: [2]
prefer-template: [2] prefer-template: [2]
quotes: [2, single, {avoidEscape: true, allowTemplateLiterals: true}]
radix: [2, as-needed] radix: [2, as-needed]
regexp/confusing-quantifier: [2] regexp/confusing-quantifier: [2]
regexp/control-character-escape: [2] regexp/control-character-escape: [2]
@ -811,7 +834,7 @@ rules:
wc/no-constructor-params: [2] wc/no-constructor-params: [2]
wc/no-constructor: [2] wc/no-constructor: [2]
wc/no-customized-built-in-elements: [2] wc/no-customized-built-in-elements: [2]
wc/no-exports-with-element: [2] wc/no-exports-with-element: [0]
wc/no-invalid-element-name: [2] wc/no-invalid-element-name: [2]
wc/no-invalid-extends: [2] wc/no-invalid-extends: [2]
wc/no-method-prefixed-with-on: [2] wc/no-method-prefixed-with-on: [2]

View file

@ -1,3 +1,4 @@
FROM public.ecr.aws/docker/library/alpine:3.18 FROM code.forgejo.org/oci/alpine:3.19
ARG RELEASE_VERSION=unkown
RUN mkdir -p /app/gitea RUN mkdir -p /app/gitea
RUN ( echo '#!/bin/sh' ; echo "echo forgejo v1.2.3" ) > /app/gitea/gitea ; chmod +x /app/gitea/gitea RUN ( echo '#!/bin/sh' ; echo "echo forgejo v$RELEASE_VERSION" ) > /app/gitea/gitea ; chmod +x /app/gitea/gitea

View file

@ -34,10 +34,10 @@ jobs:
lxc-ip-prefix: 10.0.9 lxc-ip-prefix: 10.0.9
- name: publish the forgejo release - name: publish the forgejo release
shell: bash
run: | run: |
set -x set -x
version=1.2.3
cat > /etc/docker/daemon.json <<EOF cat > /etc/docker/daemon.json <<EOF
{ {
"insecure-registries" : ["${{ steps.forgejo.outputs.host-port }}"] "insecure-registries" : ["${{ steps.forgejo.outputs.host-port }}"]
@ -53,38 +53,15 @@ jobs:
url=http://root:admin1234@${{ steps.forgejo.outputs.host-port }} url=http://root:admin1234@${{ steps.forgejo.outputs.host-port }}
export FORGEJO_RUNNER_LOGS="${{ steps.forgejo.outputs.runner-logs }}" export FORGEJO_RUNNER_LOGS="${{ steps.forgejo.outputs.runner-logs }}"
function sanity_check() {
local url=$1 version=$2
# #
# Create a new project with a fake forgejo and the release workflow only # Minimal sanity checks. Since the binary
#
cp -a .forgejo/testdata/build-release/* $dir
mkdir -p $dir/.forgejo/workflows
cp .forgejo/workflows/build-release.yml $dir/.forgejo/workflows
cp $dir/Dockerfile $dir/Dockerfile.rootless
forgejo-test-helper.sh push $dir $url root forgejo
sha=$(forgejo-test-helper.sh branch_tip $url root/forgejo main)
#
# Push a tag to trigger the release workflow and wait for it to complete
#
forgejo-curl.sh api_json --data-raw '{"tag_name": "v'$version'", "target": "'$sha'"}' $url/api/v1/repos/root/forgejo/tags
forgejo-curl.sh api_json -X PUT --data-raw '{"data":"${{ steps.forgejo.outputs.token }}"}' $url/api/v1/repos/root/forgejo/actions/secrets/TOKEN
forgejo-curl.sh api_json -X PUT --data-raw '{"data":"root"}' $url/api/v1/repos/root/forgejo/actions/secrets/DOER
LOOPS=180 forgejo-test-helper.sh wait_success "$url" root/forgejo $sha
#
# uncomment to see the logs even when everything is reported to be working ok
#
#cat $FORGEJO_RUNNER_LOGS
#
# Minimal sanity checks. e2e test is for the setup-forgejo
# action and the infrastructure playbook. Since the binary
# is a script shell it does not test the sanity of the cross # is a script shell it does not test the sanity of the cross
# build, only the sanity of the naming of the binaries. # build, only the sanity of the naming of the binaries.
# #
for arch in amd64 arm64 arm-6 ; do for arch in amd64 arm64 arm-6 ; do
binary=forgejo-$version-linux-$arch local binary=forgejo-$version-linux-$arch
for suffix in '' '.xz' ; do for suffix in '' '.xz' ; do
curl --fail -L -sS $url/root/forgejo/releases/download/v$version/$binary$suffix > $binary$suffix curl --fail -L -sS $url/root/forgejo/releases/download/v$version/$binary$suffix > $binary$suffix
if test "$suffix" = .xz ; then if test "$suffix" = .xz ; then
@ -98,10 +75,59 @@ jobs:
done done
done done
sources=forgejo-src-$version.tar.gz local sources=forgejo-src-$version.tar.gz
curl --fail -L -sS $url/root/forgejo/releases/download/v$version/$sources > $sources curl --fail -L -sS $url/root/forgejo/releases/download/v$version/$sources > $sources
curl --fail -L -sS $url/root/forgejo/releases/download/v$version/$sources.sha256 > $sources.sha256 curl --fail -L -sS $url/root/forgejo/releases/download/v$version/$sources.sha256 > $sources.sha256
shasum -a 256 --check $sources.sha256 shasum -a 256 --check $sources.sha256
docker pull ${{ steps.forgejo.outputs.host-port }}/root/forgejo:$version docker pull ${{ steps.forgejo.outputs.host-port }}/root/forgejo:$version
docker pull ${{ steps.forgejo.outputs.host-port }}/root/forgejo:$version-rootless docker pull ${{ steps.forgejo.outputs.host-port }}/root/forgejo:$version-rootless
}
#
# Create a new project with a fake forgejo and the release workflow only
#
cp -a .forgejo/testdata/build-release/* $dir
mkdir -p $dir/.forgejo/workflows
cp .forgejo/workflows/build-release.yml $dir/.forgejo/workflows
cp $dir/Dockerfile $dir/Dockerfile.rootless
forgejo-test-helper.sh push $dir $url root forgejo
forgejo-curl.sh api_json -X PUT --data-raw '{"data":"${{ steps.forgejo.outputs.token }}"}' $url/api/v1/repos/root/forgejo/actions/secrets/TOKEN
forgejo-curl.sh api_json -X PUT --data-raw '{"data":"root"}' $url/api/v1/repos/root/forgejo/actions/secrets/DOER
forgejo-curl.sh api_json -X PUT --data-raw '{"data":"true"}' $url/api/v1/repos/root/forgejo/actions/secrets/VERBOSE
#
# Push a tag to trigger the release workflow and wait for it to complete
#
version=1.2.3
sha=$(forgejo-test-helper.sh branch_tip $url root/forgejo main)
forgejo-curl.sh api_json --data-raw '{"tag_name": "v'$version'", "target": "'$sha'"}' $url/api/v1/repos/root/forgejo/tags
LOOPS=180 forgejo-test-helper.sh wait_success "$url" root/forgejo $sha
sanity_check $url $version
#
# Push a commit to a branch that triggers the build of a test release
#
version=1.2-test
(
git clone $url/root/forgejo /tmp/forgejo
cd /tmp/forgejo
date > DATE
git config user.email root@example.com
git config user.name username
git add .
git commit -m 'update'
git push $url/root/forgejo main:forgejo
)
sha=$(forgejo-test-helper.sh branch_tip $url root/forgejo forgejo)
LOOPS=180 forgejo-test-helper.sh wait_success "$url" root/forgejo $sha
sanity_check $url $version
- name: full logs
if: always()
run: |
sed -e 's/^/[RUNNER LOGS] /' ${{ steps.forgejo.outputs.runner-logs }}
docker logs forgejo | sed -e 's/^/[FORGEJO LOGS]/'
sleep 5 # hack to avoid mixing outputs in Forgejo v1.21

View file

@ -14,11 +14,12 @@
# secrets.CASCADE_DESTINATION_TOKEN: <generated from code.forgejo.org/forgejo-ci> scope read:user, write:repository, write:issue # secrets.CASCADE_DESTINATION_TOKEN: <generated from code.forgejo.org/forgejo-ci> scope read:user, write:repository, write:issue
# vars.CASCADE_DESTINATION_DOER: forgejo-ci # vars.CASCADE_DESTINATION_DOER: forgejo-ci
# #
name: Build release
on: on:
push: push:
tags: 'v*' tags: 'v[0-9]+.[0-9]+.*'
branches:
- 'forgejo'
- 'v*/forgejo'
jobs: jobs:
release: release:
@ -27,6 +28,8 @@ jobs:
if: vars.ROLE == 'forgejo-integration' || github.repository_owner == 'root' if: vars.ROLE == 'forgejo-integration' || github.repository_owner == 'root'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Sanitize the name of the repository - name: Sanitize the name of the repository
id: repository id: repository
@ -43,17 +46,39 @@ jobs:
go-version: "1.21" go-version: "1.21"
check-latest: true check-latest: true
- name: version from ref_name - name: version from ref
id: tag-version id: release-info
shell: bash
run: | run: |
version="${{ github.ref_name }}" set -x
version=${version##*v} ref="${{ github.ref }}"
echo "value=$version" >> "$GITHUB_OUTPUT" if [[ $ref =~ ^refs/heads/ ]] ; then
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
version=${ref#refs/tags/}
override=false
fi
if test -z "$version" ; then
echo failed to figure out the release version from the reference=$ref
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"
- name: release notes - name: release notes
id: release-notes id: release-notes
run: | run: |
anchor=${{ steps.tag-version.outputs.value }} anchor=${{ steps.release-info.outputs.version }}
anchor=${anchor//./-} anchor=${anchor//./-}
cat >> "$GITHUB_OUTPUT" <<EOF cat >> "$GITHUB_OUTPUT" <<EOF
value<<ENDVAR value<<ENDVAR
@ -61,11 +86,23 @@ jobs:
ENDVAR ENDVAR
EOF 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 - name: Build sources
run: | run: |
set -x set -x
apt-get -qq install -y make apt-get -qq install -y make
version=${{ steps.tag-version.outputs.value }} version=${{ steps.release-info.outputs.version }}
# #
# Make sure all files are owned by the current user. # Make sure all files are owned by the current user.
# When run as root `npx webpack` will assume the identity # When run as root `npx webpack` will assume the identity
@ -122,38 +159,42 @@ jobs:
- name: build container & release - name: build container & release
if: ${{ secrets.TOKEN != '' }} if: ${{ secrets.TOKEN != '' }}
uses: https://code.forgejo.org/forgejo/forgejo-build-publish/build@v1 uses: https://code.forgejo.org/forgejo/forgejo-build-publish/build@v3
with: with:
forgejo: "${{ env.GITHUB_SERVER_URL }}" forgejo: "${{ env.GITHUB_SERVER_URL }}"
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}" owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
repository: "${{ steps.repository.outputs.value }}" repository: "${{ steps.repository.outputs.value }}"
doer: "${{ secrets.DOER }}" doer: "${{ secrets.DOER }}"
tag-version: "${{ steps.tag-version.outputs.value }}" release-version: "${{ steps.release-info.outputs.version }}"
sha: "${{ steps.release-info.outputs.sha }}"
token: "${{ secrets.TOKEN }}" token: "${{ secrets.TOKEN }}"
platforms: linux/amd64,linux/arm64,linux/arm/v6 platforms: linux/amd64,linux/arm64,linux/arm/v6
release-notes: "${{ steps.release-notes.outputs.value }}" release-notes: "${{ steps.release-notes.outputs.value }}"
binary-name: forgejo binary-name: forgejo
binary-path: /app/gitea/gitea binary-path: /app/gitea/gitea
verbose: ${{ vars.VERBOSE || 'false' }} override: "${{ steps.release-info.outputs.override }}"
verbose: ${{ vars.VERBOSE || secrets.VERBOSE || 'false' }}
- name: build rootless container - name: build rootless container
if: ${{ secrets.TOKEN != '' }} if: ${{ secrets.TOKEN != '' }}
uses: https://code.forgejo.org/forgejo/forgejo-build-publish/build@v1 uses: https://code.forgejo.org/forgejo/forgejo-build-publish/build@v3
with: with:
forgejo: "${{ env.GITHUB_SERVER_URL }}" forgejo: "${{ env.GITHUB_SERVER_URL }}"
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}" owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
repository: "${{ steps.repository.outputs.value }}" repository: "${{ steps.repository.outputs.value }}"
doer: "${{ secrets.DOER }}" doer: "${{ secrets.DOER }}"
tag-version: "${{ steps.tag-version.outputs.value }}" release-version: "${{ steps.release-info.outputs.version }}"
sha: "${{ steps.release-info.outputs.sha }}"
token: "${{ secrets.TOKEN }}" token: "${{ secrets.TOKEN }}"
platforms: linux/amd64,linux/arm64,linux/arm/v6 platforms: linux/amd64,linux/arm64,linux/arm/v6
suffix: -rootless suffix: -rootless
dockerfile: Dockerfile.rootless dockerfile: Dockerfile.rootless
verbose: ${{ vars.VERBOSE || 'false' }} override: "${{ steps.release-info.outputs.override }}"
verbose: ${{ vars.VERBOSE || secrets.VERBOSE || 'false' }}
- name: end-to-end tests - name: end-to-end tests
if: ${{ secrets.TOKEN != '' && vars.ROLE == 'forgejo-integration' }} 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: with:
origin-url: ${{ env.GITHUB_SERVER_URL }} origin-url: ${{ env.GITHUB_SERVER_URL }}
origin-repo: ${{ github.repository }} origin-repo: ${{ github.repository }}
@ -166,4 +207,26 @@ jobs:
destination-token: ${{ secrets.CASCADE_DESTINATION_TOKEN }} destination-token: ${{ secrets.CASCADE_DESTINATION_TOKEN }}
update: .forgejo/cascading-release-end-to-end update: .forgejo/cascading-release-end-to-end
env: env:
FORGEJO_BINARY: "${{ env.GITHUB_SERVER_URL }}/${{ github.repository }}/releases/download/v${{ steps.tag-version.outputs.value }}/forgejo-${{ steps.tag-version.outputs.value }}-linux-amd64" 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

View file

@ -1,10 +1,8 @@
name: mirror name: mirror
on: on:
push: schedule:
branches: - cron: '@daily'
- 'forgejo'
- 'v*/forgejo'
jobs: jobs:
mirror: mirror:

View file

@ -42,16 +42,19 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: copy & sign - 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: with:
forgejo: ${{ vars.FORGEJO }} from-forgejo: ${{ vars.FORGEJO }}
to-forgejo: ${{ vars.FORGEJO }}
from-owner: ${{ vars.FROM_OWNER }} from-owner: ${{ vars.FROM_OWNER }}
to-owner: ${{ vars.TO_OWNER }} to-owner: ${{ vars.TO_OWNER }}
repo: ${{ vars.REPO }} repo: ${{ vars.REPO }}
ref-name: ${{ github.ref_name }}
release-notes: "See https://codeberg.org/forgejo/forgejo/src/branch/forgejo/RELEASE-NOTES.md#{ANCHOR}" release-notes: "See https://codeberg.org/forgejo/forgejo/src/branch/forgejo/RELEASE-NOTES.md#{ANCHOR}"
doer: ${{ vars.DOER }} ref-name: ${{ github.ref_name }}
token: ${{ secrets.TOKEN }} sha: ${{ github.sha }}
from-token: ${{ secrets.TOKEN }}
to-doer: ${{ vars.DOER }}
to-token: ${{ secrets.TOKEN }}
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }} gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }}
verbose: ${{ vars.VERBOSE }} verbose: ${{ vars.VERBOSE }}

View file

@ -23,10 +23,22 @@ jobs:
- run: make --always-make -j$(nproc) lint-backend checks-backend # ensure the "go-licenses" make target runs - run: make --always-make -j$(nproc) lint-backend checks-backend # ensure the "go-licenses" make target runs
env: env:
TAGS: bindata sqlite sqlite_unlock_notify 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: test-unit:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker runs-on: docker
needs: [backend-checks] needs: [backend-checks, frontend-checks]
container: container:
image: 'docker.io/node:20-bookworm' image: 'docker.io/node:20-bookworm'
services: services:
@ -67,7 +79,7 @@ jobs:
test-mysql: test-mysql:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker runs-on: docker
needs: [backend-checks] needs: [backend-checks, frontend-checks]
container: container:
image: 'docker.io/node:20-bookworm' image: 'docker.io/node:20-bookworm'
services: services:
@ -113,7 +125,7 @@ jobs:
test-pgsql: test-pgsql:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker runs-on: docker
needs: [backend-checks] needs: [backend-checks, frontend-checks]
container: container:
image: 'docker.io/node:20-bookworm' image: 'docker.io/node:20-bookworm'
services: services:
@ -161,7 +173,7 @@ jobs:
test-sqlite: test-sqlite:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker runs-on: docker
needs: [backend-checks] needs: [backend-checks, frontend-checks]
container: container:
image: 'docker.io/node:20-bookworm' image: 'docker.io/node:20-bookworm'
steps: steps:

View file

@ -10,10 +10,19 @@ tasks:
- name: Run backend - name: Run backend
command: | command: |
gp sync-await setup gp sync-await setup
if [ ! -f custom/conf/app.ini ]
then # Get the URL and extract the domain
url=$(gp url 3000)
domain=$(echo $url | awk -F[/:] '{print $4}')
if [ -f custom/conf/app.ini ]; then
sed -i "s|^ROOT_URL =.*|ROOT_URL = ${url}/|" custom/conf/app.ini
sed -i "s|^DOMAIN =.*|DOMAIN = ${domain}|" custom/conf/app.ini
sed -i "s|^SSH_DOMAIN =.*|SSH_DOMAIN = ${domain}|" custom/conf/app.ini
sed -i "s|^NO_REPLY_ADDRESS =.*|SSH_DOMAIN = noreply.${domain}|" custom/conf/app.ini
else
mkdir -p custom/conf/ mkdir -p custom/conf/
echo -e "[server]\nROOT_URL=$(gp url 3000)/" > custom/conf/app.ini echo -e "[server]\nROOT_URL = ${url}/" > custom/conf/app.ini
echo -e "\n[database]\nDB_TYPE = sqlite3\nPATH = $GITPOD_REPO_ROOT/data/gitea.db" >> custom/conf/app.ini echo -e "\n[database]\nDB_TYPE = sqlite3\nPATH = $GITPOD_REPO_ROOT/data/gitea.db" >> custom/conf/app.ini
fi fi
export TAGS="sqlite sqlite_unlock_notify" export TAGS="sqlite sqlite_unlock_notify"

View file

@ -98,7 +98,7 @@ rules:
at-rule-allowed-list: null at-rule-allowed-list: null
at-rule-disallowed-list: null at-rule-disallowed-list: null
at-rule-empty-line-before: 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-no-vendor-prefix: true
at-rule-property-required-list: null at-rule-property-required-list: null
block-no-empty: true block-no-empty: true

View file

@ -5,7 +5,7 @@ FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.21-alpine3.19 as build
ARG GOPROXY ARG GOPROXY
ENV GOPROXY ${GOPROXY:-direct} ENV GOPROXY ${GOPROXY:-direct}
ARG GITEA_VERSION ARG RELEASE_VERSION
ARG TAGS="sqlite sqlite_unlock_notify" ARG TAGS="sqlite sqlite_unlock_notify"
ENV TAGS "bindata timetzdata $TAGS" ENV TAGS "bindata timetzdata $TAGS"
ARG CGO_EXTRA_CFLAGS ARG CGO_EXTRA_CFLAGS
@ -33,10 +33,10 @@ RUN apk --no-cache add build-base git nodejs npm
COPY . ${GOPATH}/src/code.gitea.io/gitea COPY . ${GOPATH}/src/code.gitea.io/gitea
WORKDIR ${GOPATH}/src/code.gitea.io/gitea WORKDIR ${GOPATH}/src/code.gitea.io/gitea
RUN make clean-all RUN make clean
RUN make frontend RUN make frontend
RUN go build contrib/environment-to-ini/environment-to-ini.go && xx-verify environment-to-ini RUN go build contrib/environment-to-ini/environment-to-ini.go && xx-verify environment-to-ini
RUN make go-check generate-backend static-executable && xx-verify gitea RUN make RELEASE_VERSION=$RELEASE_VERSION go-check generate-backend static-executable && xx-verify gitea
# Copy local files # Copy local files
COPY docker/root /tmp/local COPY docker/root /tmp/local

View file

@ -5,7 +5,7 @@ FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.21-alpine3.19 as build
ARG GOPROXY ARG GOPROXY
ENV GOPROXY ${GOPROXY:-direct} ENV GOPROXY ${GOPROXY:-direct}
ARG GITEA_VERSION ARG RELEASE_VERSION
ARG TAGS="sqlite sqlite_unlock_notify" ARG TAGS="sqlite sqlite_unlock_notify"
ENV TAGS "bindata timetzdata $TAGS" ENV TAGS "bindata timetzdata $TAGS"
ARG CGO_EXTRA_CFLAGS ARG CGO_EXTRA_CFLAGS
@ -33,10 +33,10 @@ RUN apk --no-cache add build-base git nodejs npm
COPY . ${GOPATH}/src/code.gitea.io/gitea COPY . ${GOPATH}/src/code.gitea.io/gitea
WORKDIR ${GOPATH}/src/code.gitea.io/gitea WORKDIR ${GOPATH}/src/code.gitea.io/gitea
RUN make clean-all RUN make clean
RUN make frontend RUN make frontend
RUN go build contrib/environment-to-ini/environment-to-ini.go && xx-verify environment-to-ini RUN go build contrib/environment-to-ini/environment-to-ini.go && xx-verify environment-to-ini
RUN make go-check generate-backend static-executable && xx-verify gitea RUN make RELEASE_VERSION=$RELEASE_VERSION go-check generate-backend static-executable && xx-verify gitea
# Copy local files # Copy local files
COPY docker/rootless /tmp/local COPY docker/rootless /tmp/local

View file

@ -29,10 +29,10 @@ XGO_VERSION := go-1.21.x
AIR_PACKAGE ?= github.com/cosmtrek/air@v1.49.0 AIR_PACKAGE ?= github.com/cosmtrek/air@v1.49.0
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2 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 GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11
MISSPELL_PACKAGE ?= github.com/client9/misspell/cmd/misspell@v0.3.4 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 XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.0.3 GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.0.3
@ -85,19 +85,18 @@ endif
STORED_VERSION_FILE := VERSION STORED_VERSION_FILE := VERSION
HUGO_VERSION ?= 0.111.3 HUGO_VERSION ?= 0.111.3
GITEA_COMPATIBILITY ?= gitea-1.22.0
STORED_VERSION=$(shell cat $(STORED_VERSION_FILE) 2>/dev/null) STORED_VERSION=$(shell cat $(STORED_VERSION_FILE) 2>/dev/null)
ifneq ($(STORED_VERSION),) ifneq ($(STORED_VERSION),)
GITEA_VERSION ?= $(STORED_VERSION) FORGEJO_VERSION ?= $(STORED_VERSION)
else else
GITEA_VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//') FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//')+${GITEA_COMPATIBILITY}
endif endif
VERSION = ${GITEA_VERSION} RELEASE_VERSION ?= ${FORGEJO_VERSION}
VERSION ?= ${RELEASE_VERSION}
# SemVer 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)"
FORGEJO_VERSION := 7.0.0+0-gitea-1.22.0
LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" -X "main.ForgejoVersion=$(FORGEJO_VERSION)"
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64 LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
@ -107,7 +106,7 @@ GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/m
FOMANTIC_WORK_DIR := web_src/fomantic FOMANTIC_WORK_DIR := web_src/fomantic
WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f) 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 := 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 WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts public/assets/img/webpack
@ -134,6 +133,8 @@ TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMAN
GO_DIRS := build cmd models modules routers services tests GO_DIRS := build cmd models modules routers services tests
WEB_DIRS := web_src/js web_src/css WEB_DIRS := web_src/js web_src/css
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github
GO_SOURCES := $(wildcard *.go) GO_SOURCES := $(wildcard *.go)
GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/options/bindata.go ! -path modules/public/bindata.go ! -path modules/templates/bindata.go) GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/options/bindata.go ! -path modules/public/bindata.go ! -path modules/templates/bindata.go)
GO_SOURCES += $(GENERATED_GO_DEST) GO_SOURCES += $(GENERATED_GO_DEST)
@ -154,8 +155,8 @@ endif
FORGEJO_API_SPEC := public/assets/forgejo/api.v1.yml FORGEJO_API_SPEC := public/assets/forgejo/api.v1.yml
SWAGGER_SPEC := templates/swagger/v1_json.tmpl 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_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape}}/api/v1"|g
SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|"basePath": "/api/v1"|g SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape}}/api/v1"|"basePath": "/api/v1"|g
SWAGGER_EXCLUDE := code.gitea.io/sdk SWAGGER_EXCLUDE := code.gitea.io/sdk
SWAGGER_NEWLINE_COMMAND := -e '$$a\' SWAGGER_NEWLINE_COMMAND := -e '$$a\'
SWAGGER_SPEC_BRANDING := s|Gitea API|Forgejo API|g SWAGGER_SPEC_BRANDING := s|Gitea API|Forgejo API|g
@ -212,6 +213,8 @@ help:
@echo " - lint-swagger lint swagger files" @echo " - lint-swagger lint swagger files"
@echo " - lint-templates lint template files" @echo " - lint-templates lint template files"
@echo " - lint-yaml lint yaml files" @echo " - lint-yaml lint yaml files"
@echo " - lint-spell lint spelling"
@echo " - lint-spell-fix lint spelling and fix issues"
@echo " - checks run various consistency checks" @echo " - checks run various consistency checks"
@echo " - checks-frontend check frontend files" @echo " - checks-frontend check frontend files"
@echo " - checks-backend check backend files" @echo " - checks-backend check backend files"
@ -303,10 +306,6 @@ fmt-check: fmt
exit 1; \ exit 1; \
fi fi
.PHONY: misspell-check
misspell-check:
go run $(MISSPELL_PACKAGE) -error $(GO_DIRS) $(WEB_DIRS)
.PHONY: $(TAGS_EVIDENCE) .PHONY: $(TAGS_EVIDENCE)
$(TAGS_EVIDENCE): $(TAGS_EVIDENCE):
@mkdir -p $(MAKE_EVIDENCE_DIR) @mkdir -p $(MAKE_EVIDENCE_DIR)
@ -368,13 +367,13 @@ checks: checks-frontend checks-backend
checks-frontend: lockfile-check svg-check checks-frontend: lockfile-check svg-check
.PHONY: checks-backend .PHONY: checks-backend
checks-backend: tidy-check swagger-check fmt-check misspell-check forgejo-api-validate swagger-validate security-check checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check
.PHONY: lint .PHONY: lint
lint: lint-frontend lint-backend lint: lint-frontend lint-backend lint-spell
.PHONY: lint-fix .PHONY: lint-fix
lint-fix: lint-frontend-fix lint-backend-fix lint-fix: lint-frontend-fix lint-backend-fix lint-spell-fix
.PHONY: lint-frontend .PHONY: lint-frontend
lint-frontend: lint-js lint-css lint-frontend: lint-js lint-css
@ -412,6 +411,14 @@ lint-swagger: node_modules
lint-md: node_modules lint-md: node_modules
npx markdownlint docs *.md npx markdownlint docs *.md
.PHONY: lint-spell
lint-spell:
@go run $(MISSPELL_PACKAGE) -error $(SPELLCHECK_FILES)
.PHONY: lint-spell-fix
lint-spell-fix:
@go run $(MISSPELL_PACKAGE) -w $(SPELLCHECK_FILES)
.PHONY: lint-go .PHONY: lint-go
lint-go: lint-go:
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS) $(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS)
@ -617,8 +624,7 @@ test-mssql\#%: integrations.mssql.test generate-ini-mssql
test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test
.PHONY: playwright .PHONY: playwright
playwright: $(PLAYWRIGHT_DIR) playwright: deps-frontend
npm install --no-save @playwright/test
npx playwright install $(PLAYWRIGHT_FLAGS) npx playwright install $(PLAYWRIGHT_FLAGS)
.PHONY: test-e2e% .PHONY: test-e2e%
@ -631,7 +637,7 @@ test-e2e: test-e2e-sqlite
.PHONY: test-e2e-sqlite .PHONY: test-e2e-sqlite
test-e2e-sqlite: playwright e2e.sqlite.test generate-ini-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\#% .PHONY: test-e2e-sqlite\#%
test-e2e-sqlite\#%: playwright e2e.sqlite.test generate-ini-sqlite test-e2e-sqlite\#%: playwright e2e.sqlite.test generate-ini-sqlite
@ -639,7 +645,7 @@ test-e2e-sqlite\#%: playwright e2e.sqlite.test generate-ini-sqlite
.PHONY: test-e2e-mysql .PHONY: test-e2e-mysql
test-e2e-mysql: playwright e2e.mysql.test generate-ini-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\#% .PHONY: test-e2e-mysql\#%
test-e2e-mysql\#%: playwright e2e.mysql.test generate-ini-mysql test-e2e-mysql\#%: playwright e2e.mysql.test generate-ini-mysql
@ -647,7 +653,7 @@ test-e2e-mysql\#%: playwright e2e.mysql.test generate-ini-mysql
.PHONY: test-e2e-pgsql .PHONY: test-e2e-pgsql
test-e2e-pgsql: playwright e2e.pgsql.test generate-ini-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\#% .PHONY: test-e2e-pgsql\#%
test-e2e-pgsql\#%: playwright e2e.pgsql.test generate-ini-pgsql test-e2e-pgsql\#%: playwright e2e.pgsql.test generate-ini-pgsql
@ -655,12 +661,17 @@ test-e2e-pgsql\#%: playwright e2e.pgsql.test generate-ini-pgsql
.PHONY: test-e2e-mssql .PHONY: test-e2e-mssql
test-e2e-mssql: playwright e2e.mssql.test generate-ini-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\#% .PHONY: test-e2e-mssql\#%
test-e2e-mssql\#%: playwright e2e.mssql.test generate-ini-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/$* 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 .PHONY: bench-sqlite
bench-sqlite: integrations.sqlite.test generate-ini-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 . GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
@ -997,7 +1008,7 @@ generate-gitignore:
.PHONY: generate-images .PHONY: generate-images
generate-images: | node_modules generate-images: | node_modules
npm install --no-save --no-package-lock fabric@5 imagemin-zopfli@7 npm install --no-save fabric@6.0.0-beta19 imagemin-zopfli@7
node build/generate-images.js $(TAGS) node build/generate-images.js $(TAGS)
.PHONY: generate-manpage .PHONY: generate-manpage
@ -1015,3 +1026,8 @@ docker:
# This endif closes the if at the top of the file # This endif closes the if at the top of the file
endif 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:

View file

@ -4,6 +4,54 @@ 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). 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.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 ## 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: 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:

File diff suppressed because one or more lines are too long

View file

@ -1,20 +1,13 @@
#!/usr/bin/env node #!/usr/bin/env node
import imageminZopfli from 'imagemin-zopfli'; import imageminZopfli from 'imagemin-zopfli';
import {optimize} from 'svgo'; import {optimize} from 'svgo';
import {fabric} from 'fabric'; import {loadSVGFromString, Canvas, Rect, util} from 'fabric/node';
import {readFile, writeFile} from 'node:fs/promises'; import {readFile, writeFile} from 'node:fs/promises';
import {argv, exit} from 'node:process';
function exit(err) { function doExit(err) {
if (err) console.error(err); if (err) console.error(err);
process.exit(err ? 1 : 0); exit(err ? 1 : 0);
}
function loadSvg(svg) {
return new Promise((resolve) => {
fabric.loadSVGFromString(svg, (objects, options) => {
resolve({objects, options});
});
});
} }
async function generate(svg, path, {size, bg}) { async function generate(svg, path, {size, bg}) {
@ -35,14 +28,14 @@ async function generate(svg, path, {size, bg}) {
return; return;
} }
const {objects, options} = await loadSvg(svg); const {objects, options} = await loadSVGFromString(svg);
const canvas = new fabric.Canvas(); const canvas = new Canvas();
canvas.setDimensions({width: size, height: size}); canvas.setDimensions({width: size, height: size});
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
ctx.scale(options.width ? (size / options.width) : 1, options.height ? (size / options.height) : 1); ctx.scale(options.width ? (size / options.width) : 1, options.height ? (size / options.height) : 1);
if (bg) { if (bg) {
canvas.add(new fabric.Rect({ canvas.add(new Rect({
left: 0, left: 0,
top: 0, top: 0,
height: size * (1 / (size / options.height)), height: size * (1 / (size / options.height)),
@ -51,7 +44,7 @@ async function generate(svg, path, {size, bg}) {
})); }));
} }
canvas.add(fabric.util.groupSVGElements(objects, options)); canvas.add(util.groupSVGElements(objects, options));
canvas.renderAll(); canvas.renderAll();
let png = Buffer.from([]); let png = Buffer.from([]);
@ -64,7 +57,7 @@ async function generate(svg, path, {size, bg}) {
} }
async function main() { async function main() {
const gitea = process.argv.slice(2).includes('gitea'); const gitea = argv.slice(2).includes('gitea');
const logoSvg = await readFile(new URL('../assets/logo.svg', import.meta.url), 'utf8'); const logoSvg = await readFile(new URL('../assets/logo.svg', import.meta.url), 'utf8');
const faviconSvg = await readFile(new URL('../assets/favicon.svg', import.meta.url), 'utf8'); const faviconSvg = await readFile(new URL('../assets/favicon.svg', import.meta.url), 'utf8');
@ -79,4 +72,8 @@ async function main() {
]); ]);
} }
main().then(exit).catch(exit); try {
doExit(await main());
} catch (err) {
doExit(err);
}

View file

@ -4,15 +4,16 @@ import {optimize} from 'svgo';
import {parse} from 'node:path'; import {parse} from 'node:path';
import {readFile, writeFile, mkdir} from 'node:fs/promises'; import {readFile, writeFile, mkdir} from 'node:fs/promises';
import {fileURLToPath} from 'node:url'; import {fileURLToPath} from 'node:url';
import {exit} from 'node:process';
const glob = (pattern) => fastGlob.sync(pattern, { const glob = (pattern) => fastGlob.sync(pattern, {
cwd: fileURLToPath(new URL('..', import.meta.url)), cwd: fileURLToPath(new URL('..', import.meta.url)),
absolute: true, absolute: true,
}); });
function exit(err) { function doExit(err) {
if (err) console.error(err); if (err) console.error(err);
process.exit(err ? 1 : 0); exit(err ? 1 : 0);
} }
async function processFile(file, {prefix, fullName} = {}) { async function processFile(file, {prefix, fullName} = {}) {
@ -59,8 +60,11 @@ async function main() {
await Promise.all([ await Promise.all([
...processFiles('node_modules/@primer/octicons/build/svg/*-16.svg', {prefix: 'octicon'}), ...processFiles('node_modules/@primer/octicons/build/svg/*-16.svg', {prefix: 'octicon'}),
...processFiles('web_src/svg/*.svg'), ...processFiles('web_src/svg/*.svg'),
...processFiles('public/assets/img/gitea.svg', {fullName: 'gitea-gitea'}),
]); ]);
} }
main().then(exit).catch(exit); try {
doExit(await main());
} catch (err) {
doExit(err);
}

View file

@ -10,8 +10,8 @@ import (
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
pwd "code.gitea.io/gitea/modules/auth/password" 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/setting"
"code.gitea.io/gitea/modules/util"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -123,10 +123,10 @@ func runCreateUser(c *cli.Context) error {
changePassword = c.Bool("must-change-password") changePassword = c.Bool("must-change-password")
} }
restricted := util.OptionalBoolNone restricted := optional.None[bool]()
if c.IsSet("restricted") { if c.IsSet("restricted") {
restricted = util.OptionalBoolOf(c.Bool("restricted")) restricted = optional.Some(c.Bool("restricted"))
} }
// default user visibility in app.ini // default user visibility in app.ini
@ -142,7 +142,7 @@ func runCreateUser(c *cli.Context) error {
} }
overwriteDefault := &user_model.CreateUserOverwriteOptions{ overwriteDefault := &user_model.CreateUserOverwriteOptions{
IsActive: util.OptionalBoolTrue, IsActive: optional.Some(true),
IsRestricted: restricted, IsRestricted: restricted,
} }

View file

@ -35,7 +35,8 @@ func SubcmdActionsGenerateRunnerToken(ctx context.Context) *cli.Command {
return &cli.Command{ return &cli.Command{
Name: "generate-runner-token", Name: "generate-runner-token",
Usage: "Generate a new token for a runner to use to register with the server", 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{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "scope", Name: "scope",
@ -59,7 +60,8 @@ func SubcmdActionsRegister(ctx context.Context) *cli.Command {
return &cli.Command{ return &cli.Command{
Name: "register", Name: "register",
Usage: "Idempotent registration of a runner using a shared secret", 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{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "secret", Name: "secret",
@ -219,25 +221,3 @@ func RunGenerateActionsRunnerToken(ctx context.Context, cliCtx *cli.Context) err
} }
return nil 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)
}
}

View file

@ -145,3 +145,25 @@ func handleCliResponseExtra(ctx context.Context, extra private.ResponseExtra) er
} }
return cli.Exit(extra.Error, 1) 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
}
}

View file

@ -71,7 +71,7 @@ func runKeys(c *cli.Context) error {
ctx, cancel := installSignals() ctx, cancel := installSignals()
defer cancel() defer cancel()
setup(ctx, false) setup(ctx, c.Bool("debug"))
authorizedString, extra := private.AuthorizedPublicKeyByContent(ctx, content) authorizedString, extra := private.AuthorizedPublicKeyByContent(ctx, content)
// do not use handleCliResponseExtra or cli.NewExitError, if it exists immediately, it breaks some tests like Test_CmdKeys // do not use handleCliResponseExtra or cli.NewExitError, if it exists immediately, it breaks some tests like Test_CmdKeys

View file

@ -63,21 +63,10 @@ func setup(ctx context.Context, debug bool) {
setupConsoleLogger(log.FATAL, false, os.Stderr) setupConsoleLogger(log.FATAL, false, os.Stderr)
} }
setting.MustInstalled() 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 _, err := os.Stat(setting.RepoRootPath); err != nil {
if os.IsNotExist(err) { _ = fail(ctx, "Unable to access repository path", "Unable to access repository path %q, err: %v", setting.RepoRootPath, 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)
}
return return
} }
if err := git.InitSimple(context.Background()); err != nil { if err := git.InitSimple(context.Background()); err != nil {
_ = fail(ctx, "Failed to init git", "Failed to init git, err: %v", err) _ = fail(ctx, "Failed to init git", "Failed to init git, err: %v", err)
} }
@ -216,16 +205,18 @@ func runServ(c *cli.Context) error {
} }
} }
// LowerCase and trim the repoPath as that's how they are stored.
repoPath = strings.ToLower(strings.TrimSpace(repoPath))
rr := strings.SplitN(repoPath, "/", 2) rr := strings.SplitN(repoPath, "/", 2)
if len(rr) != 2 { if len(rr) != 2 {
return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath) return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath)
} }
username := strings.ToLower(rr[0]) username := rr[0]
reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git")) reponame := strings.TrimSuffix(rr[1], ".git")
// LowerCase and trim the repoPath as that's how they are stored.
// This should be done after splitting the repoPath into username and reponame
// so that username and reponame are not affected.
repoPath = strings.ToLower(strings.TrimSpace(repoPath))
if alphaDashDotPattern.MatchString(reponame) { if alphaDashDotPattern.MatchString(reponame) {
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame) return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)

View file

@ -991,6 +991,9 @@ LEVEL = Info
;; Disable stars feature. ;; Disable stars feature.
;DISABLE_STARS = false ;DISABLE_STARS = false
;; ;;
;; Disable repository forking.
;DISABLE_FORKS = false
;;
;; The default branch name of new repositories ;; The default branch name of new repositories
;DEFAULT_BRANCH = main ;DEFAULT_BRANCH = main
;; ;;
@ -1061,7 +1064,7 @@ LEVEL = Info
;; List of keywords used in Pull Request comments to automatically reopen a related issue ;; List of keywords used in Pull Request comments to automatically reopen a related issue
;REOPEN_KEYWORDS = reopen,reopens,reopened ;REOPEN_KEYWORDS = reopen,reopens,reopened
;; ;;
;; Set default merge style for repository creating, valid options: merge, rebase, rebase-merge, squash ;; Set default merge style for repository creating, valid options: merge, rebase, rebase-merge, squash, fast-forward-only
;DEFAULT_MERGE_STYLE = merge ;DEFAULT_MERGE_STYLE = merge
;; ;;
;; In the default merge message for squash commits include at most this many commits ;; In the default merge message for squash commits include at most this many commits
@ -1489,6 +1492,9 @@ LEVEL = Info
;DEFAULT_EMAIL_NOTIFICATIONS = enabled ;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 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 ;SEND_NOTIFICATION_EMAIL_ON_NEW_USER = false
;; Disabled features for users, could be "deletion", more features can be disabled in future
;; - deletion: a user cannot delete their own account
;USER_DISABLED_FEATURES =
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -126,7 +126,7 @@ In addition, there is _`StaticRootPath`_ which can be set as a built-in at build
keywords used in Pull Request comments to automatically close a related issue keywords used in Pull Request comments to automatically close a related issue
- `REOPEN_KEYWORDS`: **reopen**, **reopens**, **reopened**: List of keywords used in Pull Request comments to automatically reopen - `REOPEN_KEYWORDS`: **reopen**, **reopens**, **reopened**: List of keywords used in Pull Request comments to automatically reopen
a related issue a related issue
- `DEFAULT_MERGE_STYLE`: **merge**: Set default merge style for repository creating, valid options: `merge`, `rebase`, `rebase-merge`, `squash` - `DEFAULT_MERGE_STYLE`: **merge**: Set default merge style for repository creating, valid options: `merge`, `rebase`, `rebase-merge`, `squash`, `fast-forward-only`
- `DEFAULT_MERGE_MESSAGE_COMMITS_LIMIT`: **50**: In the default merge message for squash commits include at most this many commits. Set to `-1` to include all commits - `DEFAULT_MERGE_MESSAGE_COMMITS_LIMIT`: **50**: In the default merge message for squash commits include at most this many commits. Set to `-1` to include all commits
- `DEFAULT_MERGE_MESSAGE_SIZE`: **5120**: In the default merge message for squash commits limit the size of the commit messages. Set to `-1` to have no limit. Only used if `POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES` is `true`. - `DEFAULT_MERGE_MESSAGE_SIZE`: **5120**: In the default merge message for squash commits limit the size of the commit messages. Set to `-1` to have no limit. Only used if `POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES` is `true`.
- `DEFAULT_MERGE_MESSAGE_ALL_AUTHORS`: **false**: In the default merge message for squash commits walk all commits to include all authors in the Co-authored-by otherwise just use those in the limited list - `DEFAULT_MERGE_MESSAGE_ALL_AUTHORS`: **false**: In the default merge message for squash commits walk all commits to include all authors in the Co-authored-by otherwise just use those in the limited list

View file

@ -29,7 +29,7 @@ menu:
[ini](https://github.com/go-ini/ini/#recursive-values) 这里的说明。 [ini](https://github.com/go-ini/ini/#recursive-values) 这里的说明。
标注了 :exclamation: 的配置项表明除非你真的理解这个配置项的意义,否则最好使用默认值。 标注了 :exclamation: 的配置项表明除非你真的理解这个配置项的意义,否则最好使用默认值。
在下面的默认值中,`$XYZ`代表环境变量`XYZ`的值(详见:`enviroment-to-ini`)。 _`XxYyZz`_是指默认配置的一部分列出的值。这些在 app.ini 文件中不起作用,仅在此处列出作为文档说明。 在下面的默认值中,`$XYZ`代表环境变量`XYZ`的值(详见:`environment-to-ini`)。 _`XxYyZz`_是指默认配置的一部分列出的值。这些在 app.ini 文件中不起作用,仅在此处列出作为文档说明。
包含`#`或者`;`的变量必须使用引号(`` ` ``或者`""""`)包裹,否则会被解析为注释。 包含`#`或者`;`的变量必须使用引号(`` ` ``或者`""""`)包裹,否则会被解析为注释。
@ -125,7 +125,7 @@ menu:
- `CLOSE_KEYWORDS`: **close**, **closes**, **closed**, **fix**, **fixes**, **fixed**, **resolve**, **resolves**, **resolved**: 在拉取请求评论中用于自动关闭相关问题的关键词列表。 - `CLOSE_KEYWORDS`: **close**, **closes**, **closed**, **fix**, **fixes**, **fixed**, **resolve**, **resolves**, **resolved**: 在拉取请求评论中用于自动关闭相关问题的关键词列表。
- `REOPEN_KEYWORDS`: **reopen**, **reopens**, **reopened**: 在拉取请求评论中用于自动重新打开相关问题的 - `REOPEN_KEYWORDS`: **reopen**, **reopens**, **reopened**: 在拉取请求评论中用于自动重新打开相关问题的
关键词列表。 关键词列表。
- `DEFAULT_MERGE_STYLE`: **merge**: 设置创建仓库的默认合并方式,可选: `merge`, `rebase`, `rebase-merge`, `squash` - `DEFAULT_MERGE_STYLE`: **merge**: 设置创建仓库的默认合并方式,可选: `merge`, `rebase`, `rebase-merge`, `squash`, `fast-forward-only`
- `DEFAULT_MERGE_MESSAGE_COMMITS_LIMIT`: **50**: 在默认合并消息中,对于`squash`提交,最多包括此数量的提交。设置为 -1 以包括所有提交。 - `DEFAULT_MERGE_MESSAGE_COMMITS_LIMIT`: **50**: 在默认合并消息中,对于`squash`提交,最多包括此数量的提交。设置为 -1 以包括所有提交。
- `DEFAULT_MERGE_MESSAGE_SIZE`: **5120**: 在默认的合并消息中,对于`squash`提交,限制提交消息的大小。设置为 `-1`以取消限制。仅在`POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES`为`true`时使用。 - `DEFAULT_MERGE_MESSAGE_SIZE`: **5120**: 在默认的合并消息中,对于`squash`提交,限制提交消息的大小。设置为 `-1`以取消限制。仅在`POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES`为`true`时使用。
- `DEFAULT_MERGE_MESSAGE_ALL_AUTHORS`: **false**: 在默认合并消息中,对于`squash`提交,遍历所有提交以包括所有作者的`Co-authored-by`,否则仅使用限定列表中的作者。 - `DEFAULT_MERGE_MESSAGE_ALL_AUTHORS`: **false**: 在默认合并消息中,对于`squash`提交,遍历所有提交以包括所有作者的`Co-authored-by`,否则仅使用限定列表中的作者。

View file

@ -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. 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"` 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 ### Important Gotchas
- Never write `x.Update(exemplar)` without an explicit `WHERE` clause: - Never write `x.Update(exemplar)` without an explicit `WHERE` clause:

View file

@ -243,10 +243,10 @@ documentation using:
make generate-swagger make generate-swagger
``` ```
You should validate your generated Swagger file and spell-check it with: You should validate your generated Swagger file:
```bash ```bash
make swagger-validate misspell-check make swagger-validate
``` ```
You should commit the changed swagger JSON file. The continuous integration You should commit the changed swagger JSON file. The continuous integration

View file

@ -228,10 +228,10 @@ Gitea Logo的 PNG 和 SVG 版本是使用 `TAGS="gitea" make generate-images`
make generate-swagger make generate-swagger
``` ```
您应该验证生成的 Swagger 文件并使用以下命令对其进行拼写检查 您应该验证生成的 Swagger 文件:
```bash ```bash
make swagger-validate misspell-check make swagger-validate
``` ```
您应该提交更改后的 swagger JSON 文件。持续集成服务器将使用以下方法检查是否已完成: 您应该提交更改后的 swagger JSON 文件。持续集成服务器将使用以下方法检查是否已完成:

View file

@ -27,13 +27,7 @@ Next, [install Node.js with npm](https://nodejs.org/en/download/) which is
required to build the JavaScript and CSS files. The minimum supported Node.js required to build the JavaScript and CSS files. The minimum supported Node.js
version is @minNodeVersion@ and the latest LTS version is recommended. version is @minNodeVersion@ and the latest LTS version is recommended.
**Note**: When executing make tasks that require external tools, like **Note**: Go version @minGoVersion@ or higher is required. However, it is recommended to
`make misspell-check`, Gitea will automatically download and build these as
necessary. To be able to use these, you must have the `"$GOPATH/bin"` directory
on the executable path. If you don't add the go bin directory to the
executable path, you will have to manage this yourself.
**Note 2**: Go version @minGoVersion@ or higher is required. However, it is recommended to
obtain the same version as our continuous integration, see the advice given in obtain the same version as our continuous integration, see the advice given in
[Hacking on Gitea](development/hacking-on-gitea.md) [Hacking on Gitea](development/hacking-on-gitea.md)

View file

@ -21,9 +21,7 @@ menu:
接下来,[安装 Node.js 和 npm](https://nodejs.org/zh-cn/download/) 这是构建 JavaScript 和 CSS 文件所需的。最低支持的 Node.js 版本是 @minNodeVersion@,建议使用最新的 LTS 版本。 接下来,[安装 Node.js 和 npm](https://nodejs.org/zh-cn/download/) 这是构建 JavaScript 和 CSS 文件所需的。最低支持的 Node.js 版本是 @minNodeVersion@,建议使用最新的 LTS 版本。
**注意**:当执行需要外部工具的 make 任务(如`make misspell-check`Gitea 将根据需要自动下载和构建这些工具。为了能够实现这个目的,你必须将`"$GOPATH/bin"`目录添加到可执行路径中。如果没有将 Go 的二进制目录添加到可执行路径中,你需要自行解决产生的问题。 **注意**:需要 Go 版本 @minGoVersion@ 或更高版本。不过建议获取与我们的持续集成continuous integration, CI相同的版本请参阅在 [Hacking on Gitea](development/hacking-on-gitea.md) 中给出的建议。
**注意2**:需要 Go 版本 @minGoVersion@ 或更高版本。不过建议获取与我们的持续集成continuous integration, CI相同的版本请参阅在 [Hacking on Gitea](development/hacking-on-gitea.md) 中给出的建议。
## 下载 ## 下载

View file

@ -42,7 +42,7 @@ The following package managers are currently supported:
| [PyPI](usage/packages/pypi.md) | Python | `pip`, `twine` | | [PyPI](usage/packages/pypi.md) | Python | `pip`, `twine` |
| [RPM](usage/packages/rpm.md) | - | `yum`, `dnf`, `zypper` | | [RPM](usage/packages/rpm.md) | - | `yum`, `dnf`, `zypper` |
| [RubyGems](usage/packages/rubygems.md) | Ruby | `gem`, `Bundler` | | [RubyGems](usage/packages/rubygems.md) | Ruby | `gem`, `Bundler` |
| [Swift](usage/packages/rubygems.md) | Swift | `swift` | | [Swift](usage/packages/swift.md) | Swift | `swift` |
| [Vagrant](usage/packages/vagrant.md) | - | `vagrant` | | [Vagrant](usage/packages/vagrant.md) | - | `vagrant` |
**The following paragraphs only apply if Packages are not globally disabled!** **The following paragraphs only apply if Packages are not globally disabled!**

View file

@ -26,7 +26,8 @@ To work with the Swift package registry, you need to use [swift](https://www.swi
To register the package registry and provide credentials, execute: To register the package registry and provide credentials, execute:
```shell ```shell
swift package-registry set https://gitea.example.com/api/packages/{owner}/swift -login {username} -password {password} swift package-registry set https://gitea.example.com/api/packages/{owner}/swift
swift package-registry login https://gitea.example.com/api/packages/{owner}/swift --username {username} --password {password}
``` ```
| Placeholder | Description | | Placeholder | Description |

10
go.mod
View file

@ -3,10 +3,11 @@ module code.gitea.io/gitea
go 1.21 go 1.21
require ( 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/gitea-vet v0.2.3
code.gitea.io/sdk/gitea v0.17.1 code.gitea.io/sdk/gitea v0.17.1
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 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/binding v0.0.0-20230415142243-04b515c6d669
gitea.com/go-chi/cache v0.2.0 gitea.com/go-chi/cache v0.2.0
gitea.com/go-chi/captcha v0.0.0-20230415143339-2c0754df4384 gitea.com/go-chi/captcha v0.0.0-20230415143339-2c0754df4384
@ -19,7 +20,6 @@ require (
github.com/alecthomas/chroma/v2 v2.12.0 github.com/alecthomas/chroma/v2 v2.12.0
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
github.com/blevesearch/bleve/v2 v2.3.10 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/buildkite/terminal-to-html/v3 v3.10.1
github.com/caddyserver/certmagic v0.20.0 github.com/caddyserver/certmagic v0.20.0
github.com/chi-middleware/proxy v1.1.1 github.com/chi-middleware/proxy v1.1.1
@ -102,11 +102,11 @@ require (
github.com/yuin/goldmark v1.6.0 github.com/yuin/goldmark v1.6.0
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0 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/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/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/text v0.14.0
golang.org/x/tools v0.17.0 golang.org/x/tools v0.17.0
google.golang.org/grpc v1.60.1 google.golang.org/grpc v1.60.1

24
go.sum
View file

@ -35,14 +35,16 @@ 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.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.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 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.4.0 h1:OsPBPhodXuQnsspG1sQ4eRE1PeoZyofd7+i73zCwnsU=
code.gitea.io/actions-proto-go v0.3.1/go.mod h1:00ys5QDo1iHN1tHNvvddAcy2W/g+425hQya1cCSvq9A= 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 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI=
code.gitea.io/gitea-vet v0.2.3/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= 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 h1:3jCPOG2ojbl8AcfaUCRYLT5MUcBMFwS0OSK2mA5Zok8=
code.gitea.io/sdk/gitea v0.17.1/go.mod h1:aCnBqhHpoEWA180gMbaCtdX9Pl6BWBAuuP2miadoTNM= 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 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY=
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM= 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 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 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= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
@ -173,8 +175,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/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 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= 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 h1:znT9eD26LQ59dDJJEpMCwkP4wEptEAPi74hsTBuHdEo=
github.com/buildkite/terminal-to-html/v3 v3.10.1/go.mod h1:qtuRyYs6/Sw3FS9jUyVEaANHgHGqZsGqMknPLyau5cQ= 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= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
@ -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.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 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.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= 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-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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -981,8 +981,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.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= 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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= 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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1061,8 +1061,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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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-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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -1073,8 +1073,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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 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.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= 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.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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

11
main.go
View file

@ -29,6 +29,8 @@ var (
Version = "development" // program version for this build Version = "development" // program version for this build
Tags = "" // the Golang build tags Tags = "" // the Golang build tags
MakeVersion = "" // "make" program version if built with make MakeVersion = "" // "make" program version if built with make
ReleaseVersion = ""
) )
var ForgejoVersion = "1.0.0" var ForgejoVersion = "1.0.0"
@ -54,11 +56,18 @@ func main() {
log.GetManager().Close() log.GetManager().Close()
os.Exit(code) os.Exit(code)
} }
app := cmd.NewMainApp(Version, formatBuiltWith()) app := cmd.NewMainApp(Version, formatReleaseVersion()+formatBuiltWith())
_ = cmd.RunMainApp(app, os.Args...) // all errors should have been handled by the RunMainApp _ = cmd.RunMainApp(app, os.Args...) // all errors should have been handled by the RunMainApp
log.GetManager().Close() log.GetManager().Close()
} }
func formatReleaseVersion() string {
if len(ReleaseVersion) > 0 {
return " (release name " + ReleaseVersion + ")"
}
return ""
}
func formatBuiltWith() string { func formatBuiltWith() string {
version := runtime.Version() version := runtime.Version()
if len(MakeVersion) > 0 { if len(MakeVersion) > 0 {

View file

@ -26,6 +26,8 @@ const (
ArtifactStatusUploadConfirmed // 2 ArtifactStatusUploadConfirmed is the status of an artifact upload that is confirmed 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 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 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() { 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) 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 // SetArtifactExpired sets an artifact to expired
func SetArtifactExpired(ctx context.Context, artifactID int64) error { 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)}) _, err := db.GetEngine(ctx).Where("id=? AND status = ?", artifactID, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusExpired)})
return err 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
}

View file

@ -97,7 +97,7 @@ func (r *ActionRunner) StatusName() string {
} }
func (r *ActionRunner) StatusLocaleName(lang translation.Locale) string { func (r *ActionRunner) StatusLocaleName(lang translation.Locale) string {
return lang.Tr("actions.runners.status." + r.StatusName()) return lang.TrString("actions.runners.status." + r.StatusName())
} }
func (r *ActionRunner) IsOnline() bool { func (r *ActionRunner) IsOnline() bool {

View file

@ -41,7 +41,7 @@ func (s Status) String() string {
// LocaleString returns the locale string name of the Status // LocaleString returns the locale string name of the Status
func (s Status) LocaleString(lang translation.Locale) string { func (s Status) LocaleString(lang translation.Locale) string {
return lang.Tr("actions.status." + s.String()) return lang.TrString("actions.status." + s.String())
} }
// IsDone returns whether the Status is final // IsDone returns whether the Status is final

View file

@ -7,6 +7,7 @@ package db
import ( import (
"context" "context"
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"io" "io"
"reflect" "reflect"
@ -145,6 +146,7 @@ func InitEngine(ctx context.Context) error {
xormEngine.SetMaxOpenConns(setting.Database.MaxOpenConns) xormEngine.SetMaxOpenConns(setting.Database.MaxOpenConns)
xormEngine.SetMaxIdleConns(setting.Database.MaxIdleConns) xormEngine.SetMaxIdleConns(setting.Database.MaxIdleConns)
xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime) xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
xormEngine.SetConnMaxIdleTime(setting.Database.ConnMaxIdleTime)
xormEngine.SetDefaultContext(ctx) xormEngine.SetDefaultContext(ctx)
if setting.Database.SlowQueryThreshold > 0 { if setting.Database.SlowQueryThreshold > 0 {
@ -342,7 +344,7 @@ func (ErrorQueryHook) BeforeProcess(c *contexts.ContextHook) (context.Context, e
} }
func (h *ErrorQueryHook) AfterProcess(c *contexts.ContextHook) error { func (h *ErrorQueryHook) AfterProcess(c *contexts.ContextHook) error {
if c.Err != nil { if c.Err != nil && !errors.Is(c.Err, context.Canceled) {
h.Logger.Log(8, log.ERROR, "[Error SQL Query] %s %v - %v", c.SQL, c.Args, c.Err) h.Logger.Log(8, log.ERROR, "[Error SQL Query] %s %v - %v", c.SQL, c.Args, c.Err)
} }
return nil return nil

View file

@ -493,6 +493,23 @@ func (err ErrMergeUnrelatedHistories) Error() string {
return fmt.Sprintf("Merge UnrelatedHistories Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut) return fmt.Sprintf("Merge UnrelatedHistories Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
} }
// ErrMergeDivergingFastForwardOnly represents an error if a fast-forward-only merge fails because the branches diverge
type ErrMergeDivergingFastForwardOnly struct {
StdOut string
StdErr string
Err error
}
// IsErrMergeDivergingFastForwardOnly checks if an error is a ErrMergeDivergingFastForwardOnly.
func IsErrMergeDivergingFastForwardOnly(err error) bool {
_, ok := err.(ErrMergeDivergingFastForwardOnly)
return ok
}
func (err ErrMergeDivergingFastForwardOnly) Error() string {
return fmt.Sprintf("Merge DivergingFastForwardOnly Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
}
// ErrRebaseConflicts represents an error if rebase fails with a conflict // ErrRebaseConflicts represents an error if rebase fails with a conflict
type ErrRebaseConflicts struct { type ErrRebaseConflicts struct {
Style repo_model.MergeStyle Style repo_model.MergeStyle

View file

@ -135,3 +135,27 @@
user_id: 31 user_id: 31
repo_id: 28 repo_id: 28
mode: 4 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

View file

@ -45,3 +45,9 @@
repo_id: 22 repo_id: 22
user_id: 18 user_id: 18
mode: 2 # write mode: 2 # write
-
id: 9
repo_id: 60
user_id: 38
mode: 2 # write

View file

@ -293,3 +293,27 @@
lower_email: user37@example.com lower_email: user37@example.com
is_activated: true is_activated: true
is_primary: 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

View file

@ -338,3 +338,37 @@
created_unix: 978307210 created_unix: 978307210
updated_unix: 978307210 updated_unix: 978307210
is_locked: false 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

View file

@ -99,3 +99,21 @@
uid: 5 uid: 5
org_id: 36 org_id: 36
is_public: true 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

View file

@ -9,6 +9,7 @@
head_branch: branch1 head_branch: branch1
base_branch: master base_branch: master
merge_base: 4a357436d925b5c974181ff12a994538ddc5a269 merge_base: 4a357436d925b5c974181ff12a994538ddc5a269
merged_commit_id: 1a8823cd1a9549fde083f992f6b9b87a7ab74fb3
has_merged: true has_merged: true
merger_id: 2 merger_id: 2
@ -98,3 +99,21 @@
index: 1 index: 1
head_repo_id: 23 head_repo_id: 23
base_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

View file

@ -708,3 +708,45 @@
type: 1 type: 1
config: "{}" config: "{}"
created_unix: 946684810 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

View file

@ -1720,3 +1720,65 @@
is_private: true is_private: true
status: 0 status: 0
num_issues: 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

View file

@ -217,3 +217,25 @@
num_members: 1 num_members: 1
includes_all_repositories: false includes_all_repositories: false
can_create_org_repo: true 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

View file

@ -63,3 +63,15 @@
org_id: 17 org_id: 17
team_id: 9 team_id: 9
repo_id: 24 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

View file

@ -286,3 +286,39 @@
team_id: 2 team_id: 2
type: 8 type: 8
access_mode: 2 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

View file

@ -129,3 +129,21 @@
org_id: 17 org_id: 17
team_id: 9 team_id: 9
uid: 15 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

View file

@ -1369,3 +1369,151 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false 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

View file

@ -9,7 +9,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/optional"
"xorm.io/builder" "xorm.io/builder"
) )
@ -67,7 +67,7 @@ type FindBranchOptions struct {
db.ListOptions db.ListOptions
RepoID int64 RepoID int64
ExcludeBranchNames []string ExcludeBranchNames []string
IsDeletedBranch util.OptionalBool IsDeletedBranch optional.Option[bool]
OrderBy string OrderBy string
Keyword string Keyword string
} }
@ -81,8 +81,8 @@ func (opts FindBranchOptions) ToConds() builder.Cond {
if len(opts.ExcludeBranchNames) > 0 { if len(opts.ExcludeBranchNames) > 0 {
cond = cond.And(builder.NotIn("name", opts.ExcludeBranchNames)) cond = cond.And(builder.NotIn("name", opts.ExcludeBranchNames))
} }
if !opts.IsDeletedBranch.IsNone() { if opts.IsDeletedBranch.Has() {
cond = cond.And(builder.Eq{"is_deleted": opts.IsDeletedBranch.IsTrue()}) cond = cond.And(builder.Eq{"is_deleted": opts.IsDeletedBranch.Value()})
} }
if opts.Keyword != "" { if opts.Keyword != "" {
cond = cond.And(builder.Like{"name", opts.Keyword}) cond = cond.And(builder.Like{"name", opts.Keyword})
@ -92,7 +92,7 @@ func (opts FindBranchOptions) ToConds() builder.Cond {
func (opts FindBranchOptions) ToOrders() string { func (opts FindBranchOptions) ToOrders() string {
orderBy := opts.OrderBy orderBy := opts.OrderBy
if !opts.IsDeletedBranch.IsFalse() { // if deleted branch included, put them at the end if opts.IsDeletedBranch.ValueOrDefault(true) { // if deleted branch included, put them at the end
if orderBy != "" { if orderBy != "" {
orderBy += ", " orderBy += ", "
} }

View file

@ -12,7 +12,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/optional"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -49,7 +49,7 @@ func TestGetDeletedBranches(t *testing.T) {
branches, err := db.Find[git_model.Branch](db.DefaultContext, git_model.FindBranchOptions{ branches, err := db.Find[git_model.Branch](db.DefaultContext, git_model.FindBranchOptions{
ListOptions: db.ListOptionsAll, ListOptions: db.ListOptionsAll,
RepoID: repo.ID, RepoID: repo.ID,
IsDeletedBranch: util.OptionalBoolTrue, IsDeletedBranch: optional.Some(true),
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, branches, 2) assert.Len(t, branches, 2)

View file

@ -194,7 +194,7 @@ func (status *CommitStatus) APIURL(ctx context.Context) string {
// LocaleString returns the locale string name of the Status // LocaleString returns the locale string name of the Status
func (status *CommitStatus) LocaleString(lang translation.Locale) string { func (status *CommitStatus) LocaleString(lang translation.Locale) string {
return lang.Tr("repo.commitstatus." + status.State.String()) return lang.TrString("repo.commitstatus." + status.State.String())
} }
// CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc // CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc

View file

@ -8,7 +8,7 @@ import (
"sort" "sort"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/optional"
"github.com/gobwas/glob" "github.com/gobwas/glob"
) )
@ -56,7 +56,7 @@ func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string)
Page: page, Page: page,
}, },
RepoID: repoID, RepoID: repoID,
IsDeletedBranch: util.OptionalBoolFalse, IsDeletedBranch: optional.Some(false),
}) })
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -210,12 +210,12 @@ const (
// LocaleString returns the locale string name of the role // LocaleString returns the locale string name of the role
func (r RoleInRepo) LocaleString(lang translation.Locale) string { func (r RoleInRepo) LocaleString(lang translation.Locale) string {
return lang.Tr("repo.issues.role." + string(r)) return lang.TrString("repo.issues.role." + string(r))
} }
// LocaleHelper returns the locale tooltip of the role // LocaleHelper returns the locale tooltip of the role
func (r RoleInRepo) LocaleHelper(lang translation.Locale) string { func (r RoleInRepo) LocaleHelper(lang translation.Locale) string {
return lang.Tr("repo.issues.role." + string(r) + "_helper") return lang.TrString("repo.issues.role." + string(r) + "_helper")
} }
// Comment represents a comment in commit and issue page. // Comment represents a comment in commit and issue page.
@ -695,8 +695,15 @@ func (c *Comment) LoadReactions(ctx context.Context, repo *repo_model.Repository
} }
func (c *Comment) loadReview(ctx context.Context) (err error) { func (c *Comment) loadReview(ctx context.Context) (err error) {
if c.ReviewID == 0 {
return nil
}
if c.Review == nil { if c.Review == nil {
if c.Review, err = GetReviewByID(ctx, c.ReviewID); err != nil { if c.Review, err = GetReviewByID(ctx, c.ReviewID); err != nil {
// review request which has been replaced by actual reviews doesn't exist in database anymore, so ignorem them.
if c.Type == CommentTypeReviewRequest {
return nil
}
return err return err
} }
} }
@ -856,6 +863,9 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
// Check comment type. // Check comment type.
switch opts.Type { switch opts.Type {
case CommentTypeCode: case CommentTypeCode:
if err = updateAttachments(ctx, opts, comment); err != nil {
return err
}
if comment.ReviewID != 0 { if comment.ReviewID != 0 {
if comment.Review == nil { if comment.Review == nil {
if err := comment.loadReview(ctx); err != nil { if err := comment.loadReview(ctx); err != nil {
@ -873,12 +883,23 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
} }
fallthrough fallthrough
case CommentTypeReview: case CommentTypeReview:
// Check attachments if err = updateAttachments(ctx, opts, comment); err != nil {
return err
}
case CommentTypeReopen, CommentTypeClose:
if err = repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.Issue.IsPull, true); err != nil {
return err
}
}
// update the issue's updated_unix column
return UpdateIssueCols(ctx, opts.Issue, "updated_unix")
}
func updateAttachments(ctx context.Context, opts *CreateCommentOptions, comment *Comment) error {
attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, opts.Attachments) attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, opts.Attachments)
if err != nil { if err != nil {
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", opts.Attachments, err) return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", opts.Attachments, err)
} }
for i := range attachments { for i := range attachments {
attachments[i].IssueID = opts.Issue.ID attachments[i].IssueID = opts.Issue.ID
attachments[i].CommentID = comment.ID attachments[i].CommentID = comment.ID
@ -887,15 +908,8 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
return fmt.Errorf("update attachment [%d]: %w", attachments[i].ID, err) return fmt.Errorf("update attachment [%d]: %w", attachments[i].ID, err)
} }
} }
comment.Attachments = attachments comment.Attachments = attachments
case CommentTypeReopen, CommentTypeClose: return nil
if err = repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.Issue.IsPull, true); err != nil {
return err
}
}
// update the issue's updated_unix column
return UpdateIssueCols(ctx, opts.Issue, "updated_unix")
} }
func createDeadlineComment(ctx context.Context, doer *user_model.User, issue *Issue, newDeadlineUnix timeutil.TimeStamp) (*Comment, error) { func createDeadlineComment(ctx context.Context, doer *user_model.User, issue *Issue, newDeadlineUnix timeutil.TimeStamp) (*Comment, error) {

View file

@ -14,15 +14,58 @@ import (
"xorm.io/builder" "xorm.io/builder"
) )
// CodeConversation contains the comment of a given review
type CodeConversation []*Comment
// CodeConversationsAtLine contains the conversations for a given line
type CodeConversationsAtLine map[int64][]CodeConversation
// CodeConversationsAtLineAndTreePath contains the conversations for a given TreePath and line
type CodeConversationsAtLineAndTreePath map[string]CodeConversationsAtLine
func newCodeConversationsAtLineAndTreePath(comments []*Comment) CodeConversationsAtLineAndTreePath {
tree := make(CodeConversationsAtLineAndTreePath)
for _, comment := range comments {
tree.insertComment(comment)
}
return tree
}
func (tree CodeConversationsAtLineAndTreePath) insertComment(comment *Comment) {
// attempt to append comment to existing conversations (i.e. list of comments belonging to the same review)
for i, conversation := range tree[comment.TreePath][comment.Line] {
if conversation[0].ReviewID == comment.ReviewID {
tree[comment.TreePath][comment.Line][i] = append(conversation, comment)
return
}
}
// no previous conversation was found at this line, create it
if tree[comment.TreePath] == nil {
tree[comment.TreePath] = make(map[int64][]CodeConversation)
}
tree[comment.TreePath][comment.Line] = append(tree[comment.TreePath][comment.Line], CodeConversation{comment})
}
// FetchCodeConversations will return a 2d-map: ["Path"]["Line"] = List of CodeConversation (one per review) for this line
func FetchCodeConversations(ctx context.Context, issue *Issue, doer *user_model.User, showOutdatedComments bool) (CodeConversationsAtLineAndTreePath, error) {
opts := FindCommentsOptions{
Type: CommentTypeCode,
IssueID: issue.ID,
}
comments, err := findCodeComments(ctx, opts, issue, doer, nil, showOutdatedComments)
if err != nil {
return nil, err
}
return newCodeConversationsAtLineAndTreePath(comments), nil
}
// CodeComments represents comments on code by using this structure: FILENAME -> LINE (+ == proposed; - == previous) -> COMMENTS // CodeComments represents comments on code by using this structure: FILENAME -> LINE (+ == proposed; - == previous) -> COMMENTS
type CodeComments map[string]map[int64][]*Comment type CodeComments map[string]map[int64][]*Comment
// FetchCodeComments will return a 2d-map: ["Path"]["Line"] = Comments at line func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, doer *user_model.User, review *Review, showOutdatedComments bool) (CodeComments, error) {
func FetchCodeComments(ctx context.Context, issue *Issue, currentUser *user_model.User, showOutdatedComments bool) (CodeComments, error) {
return fetchCodeCommentsByReview(ctx, issue, currentUser, nil, showOutdatedComments)
}
func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *user_model.User, review *Review, showOutdatedComments bool) (CodeComments, error) {
pathToLineToComment := make(CodeComments) pathToLineToComment := make(CodeComments)
if review == nil { if review == nil {
review = &Review{ID: 0} review = &Review{ID: 0}
@ -33,7 +76,7 @@ func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *u
ReviewID: review.ID, ReviewID: review.ID,
} }
comments, err := findCodeComments(ctx, opts, issue, currentUser, review, showOutdatedComments) comments, err := findCodeComments(ctx, opts, issue, doer, review, showOutdatedComments)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -47,7 +90,7 @@ func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *u
return pathToLineToComment, nil return pathToLineToComment, nil
} }
func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issue, currentUser *user_model.User, review *Review, showOutdatedComments bool) ([]*Comment, error) { func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issue, doer *user_model.User, review *Review, showOutdatedComments bool) ([]*Comment, error) {
var comments CommentList var comments CommentList
if review == nil { if review == nil {
review = &Review{ID: 0} review = &Review{ID: 0}
@ -91,7 +134,7 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
if re, ok := reviews[comment.ReviewID]; ok && re != nil { if re, ok := reviews[comment.ReviewID]; ok && re != nil {
// If the review is pending only the author can see the comments (except if the review is set) // If the review is pending only the author can see the comments (except if the review is set)
if review.ID == 0 && re.Type == ReviewTypePending && if review.ID == 0 && re.Type == ReviewTypePending &&
(currentUser == nil || currentUser.ID != re.ReviewerID) { (doer == nil || doer.ID != re.ReviewerID) {
continue continue
} }
comment.Review = re comment.Review = re
@ -121,13 +164,14 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
return comments[:n], nil return comments[:n], nil
} }
// FetchCodeCommentsByLine fetches the code comments for a given treePath and line number // FetchCodeConversation fetches the code conversation of a given comment (same review, treePath and line number)
func FetchCodeCommentsByLine(ctx context.Context, issue *Issue, currentUser *user_model.User, treePath string, line int64, showOutdatedComments bool) ([]*Comment, error) { func FetchCodeConversation(ctx context.Context, comment *Comment, doer *user_model.User) ([]*Comment, error) {
opts := FindCommentsOptions{ opts := FindCommentsOptions{
Type: CommentTypeCode, Type: CommentTypeCode,
IssueID: issue.ID, IssueID: comment.IssueID,
TreePath: treePath, ReviewID: comment.ReviewID,
Line: line, TreePath: comment.TreePath,
Line: comment.Line,
} }
return findCodeComments(ctx, opts, issue, currentUser, nil, showOutdatedComments) return findCodeComments(ctx, opts, comment.Issue, doer, nil, true)
} }

View file

@ -225,6 +225,10 @@ func (comments CommentList) loadAssignees(ctx context.Context) error {
for _, comment := range comments { for _, comment := range comments {
comment.Assignee = assignees[comment.AssigneeID] comment.Assignee = assignees[comment.AssigneeID]
if comment.Assignee == nil {
comment.AssigneeID = user_model.GhostUserID
comment.Assignee = user_model.NewGhostUser()
}
} }
return nil return nil
} }
@ -430,7 +434,8 @@ func (comments CommentList) loadReviews(ctx context.Context) error {
for _, comment := range comments { for _, comment := range comments {
comment.Review = reviews[comment.ReviewID] comment.Review = reviews[comment.ReviewID]
if comment.Review == nil { if comment.Review == nil {
if comment.ReviewID > 0 { // review request which has been replaced by actual reviews doesn't exist in database anymore, so don't log errors for them.
if comment.ReviewID > 0 && comment.Type != CommentTypeReviewRequest {
log.Error("comment with review id [%d] but has no review record", comment.ReviewID) log.Error("comment with review id [%d] but has no review record", comment.ReviewID)
} }
continue continue

View file

@ -46,20 +46,20 @@ func TestCreateComment(t *testing.T) {
unittest.AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix)) unittest.AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix))
} }
func TestFetchCodeComments(t *testing.T) { func TestFetchCodeConversations(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
res, err := issues_model.FetchCodeComments(db.DefaultContext, issue, user, false) res, err := issues_model.FetchCodeConversations(db.DefaultContext, issue, user, false)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, res, "README.md") assert.Contains(t, res, "README.md")
assert.Contains(t, res["README.md"], int64(4)) assert.Contains(t, res["README.md"], int64(4))
assert.Len(t, res["README.md"][4], 1) assert.Len(t, res["README.md"][4], 1)
assert.Equal(t, int64(4), res["README.md"][4][0].ID) assert.Equal(t, int64(4), res["README.md"][4][0][0].ID)
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
res, err = issues_model.FetchCodeComments(db.DefaultContext, issue, user2, false) res, err = issues_model.FetchCodeConversations(db.DefaultContext, issue, user2, false)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, res, 1) assert.Len(t, res, 1)
} }

View file

@ -161,22 +161,18 @@ func FetchIssueContentHistoryList(dbCtx context.Context, issueID, commentID int6
} }
for _, item := range res { for _, item := range res {
if item.UserID > 0 {
item.UserAvatarLink = avatars.GenerateUserAvatarFastLink(item.UserName, 0) item.UserAvatarLink = avatars.GenerateUserAvatarFastLink(item.UserName, 0)
} else {
item.UserAvatarLink = avatars.DefaultAvatarLink()
}
} }
return res, nil return res, nil
} }
// HasIssueContentHistory check if a ContentHistory entry exists // HasIssueContentHistory check if a ContentHistory entry exists
func HasIssueContentHistory(dbCtx context.Context, issueID, commentID int64) (bool, error) { func HasIssueContentHistory(dbCtx context.Context, issueID, commentID int64) (bool, error) {
exists, err := db.GetEngine(dbCtx).Cols("id").Exist(&ContentHistory{ return db.GetEngine(dbCtx).Where("issue_id = ? AND comment_id = ?", issueID, commentID).Exist(new(ContentHistory))
IssueID: issueID,
CommentID: commentID,
})
if err != nil {
log.Error("can not fetch issue content history. err=%v", err)
return false, err
}
return exists, err
} }
// SoftDeleteIssueContentHistory soft delete // SoftDeleteIssueContentHistory soft delete

View file

@ -78,3 +78,16 @@ func TestContentHistory(t *testing.T) {
assert.EqualValues(t, 7, list2[1].HistoryID) assert.EqualValues(t, 7, list2[1].HistoryID)
assert.EqualValues(t, 4, list2[2].HistoryID) assert.EqualValues(t, 4, list2[2].HistoryID)
} }
func TestHasIssueContentHistory(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
// Ensures that comment_id is into taken account even if it's zero.
_ = issues_model.SaveIssueContentHistory(db.DefaultContext, 1, 11, 100, timeutil.TimeStampNow(), "c-a", true)
_ = issues_model.SaveIssueContentHistory(db.DefaultContext, 1, 11, 100, timeutil.TimeStampNow().Add(5), "c-b", false)
hasHistory1, _ := issues_model.HasIssueContentHistory(db.DefaultContext, 11, 0)
assert.False(t, hasHistory1)
hasHistory2, _ := issues_model.HasIssueContentHistory(db.DefaultContext, 11, 100)
assert.True(t, hasHistory2)
}

View file

@ -381,7 +381,7 @@ func TestCountIssues(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
count, err := issues_model.CountIssues(db.DefaultContext, &issues_model.IssuesOptions{}) count, err := issues_model.CountIssues(db.DefaultContext, &issues_model.IssuesOptions{})
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 20, count) assert.EqualValues(t, 22, count)
} }
func TestIssueLoadAttributes(t *testing.T) { func TestIssueLoadAttributes(t *testing.T) {

View file

@ -46,10 +46,10 @@ func neuterCrossReferences(ctx context.Context, issueID, commentID int64) error
for i, c := range active { for i, c := range active {
ids[i] = c.ID ids[i] = c.ID
} }
return neuterCrossReferencesIds(ctx, nil, ids) return neuterCrossReferencesIDs(ctx, nil, ids)
} }
func neuterCrossReferencesIds(stdCtx context.Context, ctx *crossReferencesContext, ids []int64) error { func neuterCrossReferencesIDs(stdCtx context.Context, ctx *crossReferencesContext, ids []int64) error {
sess := db.GetEngine(stdCtx).In("id", ids).Cols("`ref_action`") sess := db.GetEngine(stdCtx).In("id", ids).Cols("`ref_action`")
if ctx != nil && ctx.OrigIssue.NoAutoTime { if ctx != nil && ctx.OrigIssue.NoAutoTime {
sess.SetExpr("updated_unix", ctx.OrigIssue.UpdatedUnix).NoAutoTime() sess.SetExpr("updated_unix", ctx.OrigIssue.UpdatedUnix).NoAutoTime()
@ -104,7 +104,7 @@ func (issue *Issue) createCrossReferences(stdCtx context.Context, ctx *crossRefe
} }
} }
if len(ids) > 0 { if len(ids) > 0 {
if err = neuterCrossReferencesIds(stdCtx, ctx, ids); err != nil { if err = neuterCrossReferencesIDs(stdCtx, ctx, ids); err != nil {
return err return err
} }
} }

View file

@ -652,6 +652,35 @@ func GetPullRequestByIssueID(ctx context.Context, issueID int64) (*PullRequest,
return pr, pr.LoadAttributes(ctx) return pr, pr.LoadAttributes(ctx)
} }
// GetPullRequestsByBaseHeadInfo returns the pull request by given base and head
func GetPullRequestByBaseHeadInfo(ctx context.Context, baseID, headID int64, base, head string) (*PullRequest, error) {
pr := &PullRequest{}
sess := db.GetEngine(ctx).
Join("INNER", "issue", "issue.id = pull_request.issue_id").
Where("base_repo_id = ? AND base_branch = ? AND head_repo_id = ? AND head_branch = ?", baseID, base, headID, head)
has, err := sess.Get(pr)
if err != nil {
return nil, err
}
if !has {
return nil, ErrPullRequestNotExist{
HeadRepoID: headID,
BaseRepoID: baseID,
HeadBranch: head,
BaseBranch: base,
}
}
if err = pr.LoadAttributes(ctx); err != nil {
return nil, err
}
if err = pr.LoadIssue(ctx); err != nil {
return nil, err
}
return pr, nil
}
// GetAllUnmergedAgitPullRequestByPoster get all unmerged agit flow pull request // GetAllUnmergedAgitPullRequestByPoster get all unmerged agit flow pull request
// By poster id. // By poster id.
func GetAllUnmergedAgitPullRequestByPoster(ctx context.Context, uid int64) ([]*PullRequest, error) { func GetAllUnmergedAgitPullRequestByPoster(ctx context.Context, uid int64) ([]*PullRequest, error) {
@ -893,7 +922,14 @@ func PullRequestCodeOwnersReview(ctx context.Context, pull *Issue, pr *PullReque
} }
rules, _ := GetCodeOwnersFromContent(ctx, data) rules, _ := GetCodeOwnersFromContent(ctx, data)
changedFiles, err := repo.GetFilesChangedBetween(git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName())
prInfo, err := repo.GetCompareInfo(repo.Path, git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName(), false, false)
if err != nil {
return err
}
// Use the merge base as the base instead of the main branch to avoid problems
// if the pull request is out of date with the base branch.
changedFiles, err := repo.GetFilesChangedBetween(prInfo.MergeBase, pr.HeadCommitID)
if err != nil { if err != nil {
return err return err
} }
@ -1093,3 +1129,23 @@ func InsertPullRequests(ctx context.Context, prs ...*PullRequest) error {
} }
return committer.Commit() return committer.Commit()
} }
// GetPullRequestByMergedCommit returns a merged pull request by the given commit
func GetPullRequestByMergedCommit(ctx context.Context, repoID int64, sha string) (*PullRequest, error) {
pr := new(PullRequest)
has, err := db.GetEngine(ctx).Where("base_repo_id = ? AND merged_commit_id = ?", repoID, sha).Get(pr)
if err != nil {
return nil, err
} else if !has {
return nil, ErrPullRequestNotExist{0, 0, 0, repoID, "", ""}
}
if err = pr.LoadAttributes(ctx); err != nil {
return nil, err
}
if err = pr.LoadIssue(ctx); err != nil {
return nil, err
}
return pr, nil
}

View file

@ -425,6 +425,18 @@ func TestGetApprovers(t *testing.T) {
assert.EqualValues(t, expected, approvers) assert.EqualValues(t, expected, approvers)
} }
func TestGetPullRequestByMergedCommit(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
pr, err := issues_model.GetPullRequestByMergedCommit(db.DefaultContext, 1, "1a8823cd1a9549fde083f992f6b9b87a7ab74fb3")
assert.NoError(t, err)
assert.EqualValues(t, 1, pr.ID)
_, err = issues_model.GetPullRequestByMergedCommit(db.DefaultContext, 0, "1a8823cd1a9549fde083f992f6b9b87a7ab74fb3")
assert.ErrorAs(t, err, &issues_model.ErrPullRequestNotExist{})
_, err = issues_model.GetPullRequestByMergedCommit(db.DefaultContext, 1, "")
assert.ErrorAs(t, err, &issues_model.ErrPullRequestNotExist{})
}
func TestMigrate_InsertPullRequests(t *testing.T) { func TestMigrate_InsertPullRequests(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
reponame := "repo1" reponame := "repo1"

View file

@ -159,6 +159,14 @@ func (r *Review) LoadReviewer(ctx context.Context) (err error) {
return err return err
} }
r.Reviewer, err = user_model.GetPossibleUserByID(ctx, r.ReviewerID) r.Reviewer, err = user_model.GetPossibleUserByID(ctx, r.ReviewerID)
if err != nil {
if !user_model.IsErrUserNotExist(err) {
return fmt.Errorf("GetPossibleUserByID [%d]: %w", r.ReviewerID, err)
}
r.ReviewerID = user_model.GhostUserID
r.Reviewer = user_model.NewGhostUser()
return nil
}
return err return err
} }
@ -284,8 +292,14 @@ func IsOfficialReviewerTeam(ctx context.Context, issue *Issue, team *organizatio
// CreateReview creates a new review based on opts // CreateReview creates a new review based on opts
func CreateReview(ctx context.Context, opts CreateReviewOptions) (*Review, error) { func CreateReview(ctx context.Context, opts CreateReviewOptions) (*Review, error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return nil, err
}
defer committer.Close()
sess := db.GetEngine(ctx)
review := &Review{ review := &Review{
Type: opts.Type,
Issue: opts.Issue, Issue: opts.Issue,
IssueID: opts.Issue.ID, IssueID: opts.Issue.ID,
Reviewer: opts.Reviewer, Reviewer: opts.Reviewer,
@ -295,15 +309,39 @@ func CreateReview(ctx context.Context, opts CreateReviewOptions) (*Review, error
CommitID: opts.CommitID, CommitID: opts.CommitID,
Stale: opts.Stale, Stale: opts.Stale,
} }
if opts.Reviewer != nil { if opts.Reviewer != nil {
review.Type = opts.Type
review.ReviewerID = opts.Reviewer.ID review.ReviewerID = opts.Reviewer.ID
} else {
if review.Type != ReviewTypeRequest { reviewCond := builder.Eq{"reviewer_id": opts.Reviewer.ID, "issue_id": opts.Issue.ID}
// make sure user review requests are cleared
if opts.Type != ReviewTypePending {
if _, err := sess.Where(reviewCond.And(builder.Eq{"type": ReviewTypeRequest})).Delete(new(Review)); err != nil {
return nil, err
}
}
// make sure if the created review gets dismissed no old review surface
// other types can be ignored, as they don't affect branch protection
if opts.Type == ReviewTypeApprove || opts.Type == ReviewTypeReject {
if _, err := sess.Where(reviewCond.And(builder.In("type", ReviewTypeApprove, ReviewTypeReject))).
Cols("dismissed").Update(&Review{Dismissed: true}); err != nil {
return nil, err
}
}
} else if opts.ReviewerTeam != nil {
review.Type = ReviewTypeRequest review.Type = ReviewTypeRequest
}
review.ReviewerTeamID = opts.ReviewerTeam.ID review.ReviewerTeamID = opts.ReviewerTeam.ID
} else {
return nil, fmt.Errorf("provide either reviewer or reviewer team")
} }
return review, db.Insert(ctx, review)
if _, err := sess.Insert(review); err != nil {
return nil, err
}
return review, committer.Commit()
} }
// GetCurrentReview returns the current pending review of reviewer for given issue // GetCurrentReview returns the current pending review of reviewer for given issue
@ -621,6 +659,9 @@ func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_mo
return nil, err return nil, err
} }
// func caller use the created comment to retrieve created review too.
comment.Review = review
return comment, committer.Commit() return comment, committer.Commit()
} }

View file

@ -18,11 +18,11 @@ type ReviewList []*Review
// LoadReviewers loads reviewers // LoadReviewers loads reviewers
func (reviews ReviewList) LoadReviewers(ctx context.Context) error { func (reviews ReviewList) LoadReviewers(ctx context.Context) error {
reviewerIds := make([]int64, len(reviews)) reviewerIDs := make([]int64, len(reviews))
for i := 0; i < len(reviews); i++ { for i := 0; i < len(reviews); i++ {
reviewerIds[i] = reviews[i].ReviewerID reviewerIDs[i] = reviews[i].ReviewerID
} }
reviewers, err := user_model.GetPossibleUserByIDs(ctx, reviewerIds) reviewers, err := user_model.GetPossibleUserByIDs(ctx, reviewerIDs)
if err != nil { if err != nil {
return err return err
} }
@ -38,12 +38,12 @@ func (reviews ReviewList) LoadReviewers(ctx context.Context) error {
} }
func (reviews ReviewList) LoadIssues(ctx context.Context) error { func (reviews ReviewList) LoadIssues(ctx context.Context) error {
issueIds := container.Set[int64]{} issueIDs := container.Set[int64]{}
for i := 0; i < len(reviews); i++ { for i := 0; i < len(reviews); i++ {
issueIds.Add(reviews[i].IssueID) issueIDs.Add(reviews[i].IssueID)
} }
issues, err := GetIssuesByIDs(ctx, issueIds.Values()) issues, err := GetIssuesByIDs(ctx, issueIDs.Values())
if err != nil { if err != nil {
return err return err
} }

View file

@ -595,8 +595,6 @@ func GetOrgByID(ctx context.Context, id int64) (*Organization, error) {
} else if !has { } else if !has {
return nil, user_model.ErrUserNotExist{ return nil, user_model.ErrUserNotExist{
UID: id, UID: id,
Name: "",
KeyID: 0,
} }
} }
return u, nil return u, nil

View file

@ -356,7 +356,6 @@ func HasAccessUnit(ctx context.Context, user *user_model.User, repo *repo_model.
// CanBeAssigned return true if user can be assigned to issue or pull requests in repo // CanBeAssigned return true if user can be assigned to issue or pull requests in repo
// Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface. // Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface.
// FIXME: user could send PullRequest also could be assigned???
func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.Repository, _ bool) (bool, error) { func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.Repository, _ bool) (bool, error) {
if user.IsOrganization() { if user.IsOrganization() {
return false, fmt.Errorf("Organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID) return false, fmt.Errorf("Organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID)
@ -365,7 +364,8 @@ func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.
if err != nil { if err != nil {
return false, err return false, err
} }
return perm.CanAccessAny(perm_model.AccessModeWrite, unit.TypeCode, unit.TypeIssues, unit.TypePullRequests), nil return perm.CanAccessAny(perm_model.AccessModeWrite, unit.AllRepoUnitTypes...) ||
perm.CanAccessAny(perm_model.AccessModeRead, unit.TypePullRequests), nil
} }
// HasAccess returns true if user has access to repo // HasAccess returns true if user has access to repo

View file

@ -21,6 +21,8 @@ const (
MergeStyleRebaseMerge MergeStyle = "rebase-merge" MergeStyleRebaseMerge MergeStyle = "rebase-merge"
// MergeStyleSquash squash commits into single commit before merging // MergeStyleSquash squash commits into single commit before merging
MergeStyleSquash MergeStyle = "squash" MergeStyleSquash MergeStyle = "squash"
// MergeStyleFastForwardOnly fast-forward merge if possible, otherwise fail
MergeStyleFastForwardOnly MergeStyle = "fast-forward-only"
// MergeStyleManuallyMerged pr has been merged manually, just mark it as merged directly // MergeStyleManuallyMerged pr has been merged manually, just mark it as merged directly
MergeStyleManuallyMerged MergeStyle = "manually-merged" MergeStyleManuallyMerged MergeStyle = "manually-merged"
// MergeStyleRebaseUpdate not a merge style, used to update pull head by rebase // MergeStyleRebaseUpdate not a merge style, used to update pull head by rebase

View file

@ -449,6 +449,31 @@ func (repo *Repository) GetUnit(ctx context.Context, tp unit.Type) (*RepoUnit, e
return nil, ErrUnitTypeNotExist{tp} return nil, ErrUnitTypeNotExist{tp}
} }
// AllUnitsEnabled returns true if all units are enabled for the repo.
func (repo *Repository) AllUnitsEnabled(ctx context.Context) bool {
hasAnyUnitEnabled := func(unitGroup []unit.Type) bool {
// Loop over the group of units
for _, unit := range unitGroup {
// If *any* of them is enabled, return true.
if repo.UnitEnabled(ctx, unit) {
return true
}
}
// If none are enabled, return false.
return false
}
for _, unitGroup := range unit.AllowedRepoUnitGroups {
// If any disabled unit is found, return false immediately.
if !hasAnyUnitEnabled(unitGroup) {
return false
}
}
return true
}
// LoadOwner loads owner user // LoadOwner loads owner user
func (repo *Repository) LoadOwner(ctx context.Context) (err error) { func (repo *Repository) LoadOwner(ctx context.Context) (err error) {
if repo.Owner != nil { if repo.Owner != nil {

View file

@ -138,12 +138,12 @@ func getTestCases() []struct {
{ {
name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative", name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse},
count: 32, count: 34,
}, },
{ {
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative", name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: util.OptionalBoolFalse},
count: 37, count: 39,
}, },
{ {
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName", name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
@ -158,7 +158,7 @@ func getTestCases() []struct {
{ {
name: "AllPublic/PublicRepositoriesOfOrganization", name: "AllPublic/PublicRepositoriesOfOrganization",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse, Template: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse, Template: util.OptionalBoolFalse},
count: 32, count: 34,
}, },
{ {
name: "AllTemplates", name: "AllTemplates",

View file

@ -153,6 +153,7 @@ type PullRequestsConfig struct {
AllowRebase bool AllowRebase bool
AllowRebaseMerge bool AllowRebaseMerge bool
AllowSquash bool AllowSquash bool
AllowFastForwardOnly bool
AllowManualMerge bool AllowManualMerge bool
AutodetectManualMerge bool AutodetectManualMerge bool
AllowRebaseUpdate bool AllowRebaseUpdate bool
@ -179,6 +180,7 @@ func (cfg *PullRequestsConfig) IsMergeStyleAllowed(mergeStyle MergeStyle) bool {
mergeStyle == MergeStyleRebase && cfg.AllowRebase || mergeStyle == MergeStyleRebase && cfg.AllowRebase ||
mergeStyle == MergeStyleRebaseMerge && cfg.AllowRebaseMerge || mergeStyle == MergeStyleRebaseMerge && cfg.AllowRebaseMerge ||
mergeStyle == MergeStyleSquash && cfg.AllowSquash || mergeStyle == MergeStyleSquash && cfg.AllowSquash ||
mergeStyle == MergeStyleFastForwardOnly && cfg.AllowFastForwardOnly ||
mergeStyle == MergeStyleManuallyMerged && cfg.AllowManualMerge mergeStyle == MergeStyleManuallyMerged && cfg.AllowManualMerge
} }

View file

@ -8,6 +8,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
@ -78,7 +79,8 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
if err = e.Table("team_user"). if err = e.Table("team_user").
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id"). Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id"). Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
Where("`team_repo`.repo_id = ? AND `team_unit`.access_mode >= ?", repo.ID, perm.AccessModeWrite). Where("`team_repo`.repo_id = ? AND (`team_unit`.access_mode >= ? OR (`team_unit`.access_mode = ? AND `team_unit`.`type` = ?))",
repo.ID, perm.AccessModeWrite, perm.AccessModeRead, unit.TypePullRequests).
Distinct("`team_user`.uid"). Distinct("`team_user`.uid").
Select("`team_user`.uid"). Select("`team_user`.uid").
Find(&additionalUserIDs); err != nil { Find(&additionalUserIDs); err != nil {

View file

@ -17,13 +17,13 @@ const (
func (o OwnerType) LocaleString(locale translation.Locale) string { func (o OwnerType) LocaleString(locale translation.Locale) string {
switch o { switch o {
case OwnerTypeSystemGlobal: case OwnerTypeSystemGlobal:
return locale.Tr("concept_system_global") return locale.TrString("concept_system_global")
case OwnerTypeIndividual: case OwnerTypeIndividual:
return locale.Tr("concept_user_individual") return locale.TrString("concept_user_individual")
case OwnerTypeRepository: case OwnerTypeRepository:
return locale.Tr("concept_code_repository") return locale.TrString("concept_code_repository")
case OwnerTypeOrganization: case OwnerTypeOrganization:
return locale.Tr("concept_user_organization") return locale.TrString("concept_user_organization")
} }
return locale.Tr("unknown") return locale.TrString("unknown")
} }

View file

@ -108,6 +108,10 @@ var (
// DisabledRepoUnits contains the units that have been globally disabled // DisabledRepoUnits contains the units that have been globally disabled
DisabledRepoUnits = []Type{} DisabledRepoUnits = []Type{}
// AllowedRepoUnitGroups contains the units that have been globally enabled,
// with mutually exclusive units grouped together.
AllowedRepoUnitGroups = [][]Type{}
) )
// Get valid set of default repository units from settings // Get valid set of default repository units from settings
@ -162,6 +166,45 @@ func LoadUnitConfig() error {
if len(DefaultForkRepoUnits) == 0 { if len(DefaultForkRepoUnits) == 0 {
return errors.New("no default fork repository units found") return errors.New("no default fork repository units found")
} }
// Collect the allowed repo unit groups. Mutually exclusive units are
// grouped together.
AllowedRepoUnitGroups = [][]Type{}
for _, unit := range []Type{
TypeCode,
TypePullRequests,
TypeProjects,
TypePackages,
TypeActions,
} {
// If unit is globally disabled, ignore it.
if unit.UnitGlobalDisabled() {
continue
}
// If it is allowed, add it to the group list.
AllowedRepoUnitGroups = append(AllowedRepoUnitGroups, []Type{unit})
}
addMutuallyExclusiveGroup := func(unit1, unit2 Type) {
var list []Type
if !unit1.UnitGlobalDisabled() {
list = append(list, unit1)
}
if !unit2.UnitGlobalDisabled() {
list = append(list, unit2)
}
if len(list) > 0 {
AllowedRepoUnitGroups = append(AllowedRepoUnitGroups, list)
}
}
addMutuallyExclusiveGroup(TypeIssues, TypeExternalTracker)
addMutuallyExclusiveGroup(TypeWiki, TypeExternalWiki)
return nil return nil
} }

View file

@ -44,12 +44,12 @@ func fatalTestError(fmtStr string, args ...any) {
} }
// InitSettings initializes config provider and load common settings for tests // InitSettings initializes config provider and load common settings for tests
func InitSettings(extraConfigs ...string) { func InitSettings() {
if setting.CustomConf == "" { if setting.CustomConf == "" {
setting.CustomConf = filepath.Join(setting.CustomPath, "conf/app-unittest-tmp.ini") setting.CustomConf = filepath.Join(setting.CustomPath, "conf/app-unittest-tmp.ini")
_ = os.Remove(setting.CustomConf) _ = os.Remove(setting.CustomConf)
} }
setting.InitCfgProvider(setting.CustomConf, strings.Join(extraConfigs, "\n")) setting.InitCfgProvider(setting.CustomConf)
setting.LoadCommonSettings() setting.LoadCommonSettings()
if err := setting.PrepareAppDataPath(); err != nil { if err := setting.PrepareAppDataPath(); err != nil {

View file

@ -131,8 +131,8 @@ func AssertSuccessfulInsert(t assert.TestingT, beans ...any) {
} }
// AssertCount assert the count of a bean // AssertCount assert the count of a bean
func AssertCount(t assert.TestingT, bean, expected any) { func AssertCount(t assert.TestingT, bean, expected any) bool {
assert.EqualValues(t, expected, GetCount(t, bean)) return assert.EqualValues(t, expected, GetCount(t, bean))
} }
// AssertInt64InRange assert value is in range [low, high] // AssertInt64InRange assert value is in range [low, high]
@ -150,7 +150,7 @@ func GetCountByCond(t assert.TestingT, tableName string, cond builder.Cond) int6
} }
// AssertCountByCond test the count of database entries matching bean // AssertCountByCond test the count of database entries matching bean
func AssertCountByCond(t assert.TestingT, tableName string, cond builder.Cond, expected int) { func AssertCountByCond(t assert.TestingT, tableName string, cond builder.Cond, expected int) bool {
assert.EqualValues(t, expected, GetCountByCond(t, tableName, cond), return assert.EqualValues(t, expected, GetCountByCond(t, tableName, cond),
"Failed consistency test, the counted bean (of table %s) was %+v", tableName, cond) "Failed consistency test, the counted bean (of table %s) was %+v", tableName, cond)
} }

View file

@ -33,7 +33,6 @@ func (err ErrUserAlreadyExist) Unwrap() error {
type ErrUserNotExist struct { type ErrUserNotExist struct {
UID int64 UID int64
Name string Name string
KeyID int64
} }
// IsErrUserNotExist checks if an error is a ErrUserNotExist. // IsErrUserNotExist checks if an error is a ErrUserNotExist.
@ -43,7 +42,7 @@ func IsErrUserNotExist(err error) bool {
} }
func (err ErrUserNotExist) Error() string { func (err ErrUserNotExist) Error() string {
return fmt.Sprintf("user does not exist [uid: %d, name: %s, keyid: %d]", err.UID, err.Name, err.KeyID) return fmt.Sprintf("user does not exist [uid: %d, name: %s]", err.UID, err.Name)
} }
// Unwrap unwraps this error as a ErrNotExist error // Unwrap unwraps this error as a ErrNotExist error

View file

@ -26,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@ -442,14 +443,14 @@ func (u *User) GetDisplayName() string {
} }
// GetCompleteName returns the the full name and username in the form of // GetCompleteName returns the the full name and username in the form of
// "Full Name (@username)" if full name is not empty, otherwise it returns // "Full Name (username)" if full name is not empty, otherwise it returns
// "@username". // "username".
func (u *User) GetCompleteName() string { func (u *User) GetCompleteName() string {
trimmedFullName := strings.TrimSpace(u.FullName) trimmedFullName := strings.TrimSpace(u.FullName)
if len(trimmedFullName) > 0 { if len(trimmedFullName) > 0 {
return fmt.Sprintf("%s (@%s)", trimmedFullName, u.Name) return fmt.Sprintf("%s (%s)", trimmedFullName, u.Name)
} }
return fmt.Sprintf("@%s", u.Name) return u.Name
} }
func gitSafeName(name string) string { func gitSafeName(name string) string {
@ -591,14 +592,14 @@ func IsUsableUsername(name string) error {
// CreateUserOverwriteOptions are an optional options who overwrite system defaults on user creation // CreateUserOverwriteOptions are an optional options who overwrite system defaults on user creation
type CreateUserOverwriteOptions struct { type CreateUserOverwriteOptions struct {
KeepEmailPrivate util.OptionalBool KeepEmailPrivate optional.Option[bool]
Visibility *structs.VisibleType Visibility *structs.VisibleType
AllowCreateOrganization util.OptionalBool AllowCreateOrganization optional.Option[bool]
EmailNotificationsPreference *string EmailNotificationsPreference *string
MaxRepoCreation *int MaxRepoCreation *int
Theme *string Theme *string
IsRestricted util.OptionalBool IsRestricted optional.Option[bool]
IsActive util.OptionalBool IsActive optional.Option[bool]
} }
// CreateUser creates record of a new user. // CreateUser creates record of a new user.
@ -625,14 +626,14 @@ func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOve
// overwrite defaults if set // overwrite defaults if set
if len(overwriteDefault) != 0 && overwriteDefault[0] != nil { if len(overwriteDefault) != 0 && overwriteDefault[0] != nil {
overwrite := overwriteDefault[0] overwrite := overwriteDefault[0]
if !overwrite.KeepEmailPrivate.IsNone() { if overwrite.KeepEmailPrivate.Has() {
u.KeepEmailPrivate = overwrite.KeepEmailPrivate.IsTrue() u.KeepEmailPrivate = overwrite.KeepEmailPrivate.Value()
} }
if overwrite.Visibility != nil { if overwrite.Visibility != nil {
u.Visibility = *overwrite.Visibility u.Visibility = *overwrite.Visibility
} }
if !overwrite.AllowCreateOrganization.IsNone() { if overwrite.AllowCreateOrganization.Has() {
u.AllowCreateOrganization = overwrite.AllowCreateOrganization.IsTrue() u.AllowCreateOrganization = overwrite.AllowCreateOrganization.Value()
} }
if overwrite.EmailNotificationsPreference != nil { if overwrite.EmailNotificationsPreference != nil {
u.EmailNotificationsPreference = *overwrite.EmailNotificationsPreference u.EmailNotificationsPreference = *overwrite.EmailNotificationsPreference
@ -643,11 +644,11 @@ func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOve
if overwrite.Theme != nil { if overwrite.Theme != nil {
u.Theme = *overwrite.Theme u.Theme = *overwrite.Theme
} }
if !overwrite.IsRestricted.IsNone() { if overwrite.IsRestricted.Has() {
u.IsRestricted = overwrite.IsRestricted.IsTrue() u.IsRestricted = overwrite.IsRestricted.Value()
} }
if !overwrite.IsActive.IsNone() { if overwrite.IsActive.Has() {
u.IsActive = overwrite.IsActive.IsTrue() u.IsActive = overwrite.IsActive.Value()
} }
} }
@ -864,7 +865,7 @@ func GetUserByID(ctx context.Context, id int64) (*User, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} else if !has { } else if !has {
return nil, ErrUserNotExist{id, "", 0} return nil, ErrUserNotExist{UID: id}
} }
return u, nil return u, nil
} }
@ -914,14 +915,14 @@ func GetPossibleUserByIDs(ctx context.Context, ids []int64) ([]*User, error) {
// GetUserByNameCtx returns user by given name. // GetUserByNameCtx returns user by given name.
func GetUserByName(ctx context.Context, name string) (*User, error) { func GetUserByName(ctx context.Context, name string) (*User, error) {
if len(name) == 0 { if len(name) == 0 {
return nil, ErrUserNotExist{0, name, 0} return nil, ErrUserNotExist{Name: name}
} }
u := &User{LowerName: strings.ToLower(name), Type: UserTypeIndividual} u := &User{LowerName: strings.ToLower(name), Type: UserTypeIndividual}
has, err := db.GetEngine(ctx).Get(u) has, err := db.GetEngine(ctx).Get(u)
if err != nil { if err != nil {
return nil, err return nil, err
} else if !has { } else if !has {
return nil, ErrUserNotExist{0, name, 0} return nil, ErrUserNotExist{Name: name}
} }
return u, nil return u, nil
} }
@ -1062,7 +1063,7 @@ func ValidateCommitsWithEmails(ctx context.Context, oldCommits []*git.Commit) []
// GetUserByEmail returns the user object by given e-mail if exists. // GetUserByEmail returns the user object by given e-mail if exists.
func GetUserByEmail(ctx context.Context, email string) (*User, error) { func GetUserByEmail(ctx context.Context, email string) (*User, error) {
if len(email) == 0 { if len(email) == 0 {
return nil, ErrUserNotExist{0, email, 0} return nil, ErrUserNotExist{Name: email}
} }
email = strings.ToLower(email) email = strings.ToLower(email)
@ -1089,7 +1090,7 @@ func GetUserByEmail(ctx context.Context, email string) (*User, error) {
} }
} }
return nil, ErrUserNotExist{0, email, 0} return nil, ErrUserNotExist{Name: email}
} }
// GetUser checks if a user already exists // GetUser checks if a user already exists
@ -1100,7 +1101,7 @@ func GetUser(ctx context.Context, user *User) (bool, error) {
// GetUserByOpenID returns the user object by given OpenID if exists. // GetUserByOpenID returns the user object by given OpenID if exists.
func GetUserByOpenID(ctx context.Context, uri string) (*User, error) { func GetUserByOpenID(ctx context.Context, uri string) (*User, error) {
if len(uri) == 0 { if len(uri) == 0 {
return nil, ErrUserNotExist{0, uri, 0} return nil, ErrUserNotExist{Name: uri}
} }
uri, err := openid.Normalize(uri) uri, err := openid.Normalize(uri)
@ -1120,7 +1121,7 @@ func GetUserByOpenID(ctx context.Context, uri string) (*User, error) {
return GetUserByID(ctx, oid.UID) return GetUserByID(ctx, oid.UID)
} }
return nil, ErrUserNotExist{0, uri, 0} return nil, ErrUserNotExist{Name: uri}
} }
// GetAdminUser returns the first administrator // GetAdminUser returns the first administrator

View file

@ -98,7 +98,7 @@ func TestSearchUsers(t *testing.T) {
[]int64{19, 25}) []int64{19, 25})
testOrgSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 4, PageSize: 2}}, testOrgSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 4, PageSize: 2}},
[]int64{26}) []int64{26, 41})
testOrgSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 5, PageSize: 2}}, testOrgSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 5, PageSize: 2}},
[]int64{}) []int64{})
@ -110,13 +110,13 @@ func TestSearchUsers(t *testing.T) {
} }
testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}}, testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}},
[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37}) []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40})
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolFalse}, testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolFalse},
[]int64{9}) []int64{9})
testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue},
[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37}) []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40})
testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue},
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18}) []int64{1, 10, 11, 12, 13, 14, 15, 16, 18})

View file

@ -25,6 +25,45 @@ const (
GithubEventSchedule = "schedule" GithubEventSchedule = "schedule"
) )
// IsDefaultBranchWorkflow returns true if the event only triggers workflows on the default branch
func IsDefaultBranchWorkflow(triggedEvent webhook_module.HookEventType) bool {
switch triggedEvent {
case webhook_module.HookEventDelete:
// GitHub "delete" event
// https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#delete
return true
case webhook_module.HookEventFork:
// GitHub "fork" event
// https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#fork
return true
case webhook_module.HookEventIssueComment:
// GitHub "issue_comment" event
// https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issue_comment
return true
case webhook_module.HookEventPullRequestComment:
// GitHub "pull_request_comment" event
// https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_comment-use-issue_comment
return true
case webhook_module.HookEventWiki:
// GitHub "gollum" event
// https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#gollum
return true
case webhook_module.HookEventSchedule:
// GitHub "schedule" event
// https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
return true
case webhook_module.HookEventIssues,
webhook_module.HookEventIssueAssign,
webhook_module.HookEventIssueLabel,
webhook_module.HookEventIssueMilestone:
// Github "issues" event
// https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issues
return true
}
return false
}
// canGithubEventMatch check if the input Github event can match any Gitea event. // canGithubEventMatch check if the input Github event can match any Gitea event.
func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEventType) bool { func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEventType) bool {
switch eventName { switch eventName {
@ -55,7 +94,9 @@ func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEvent
case webhook_module.HookEventPullRequest, case webhook_module.HookEventPullRequest,
webhook_module.HookEventPullRequestSync, webhook_module.HookEventPullRequestSync,
webhook_module.HookEventPullRequestAssign, webhook_module.HookEventPullRequestAssign,
webhook_module.HookEventPullRequestLabel: webhook_module.HookEventPullRequestLabel,
webhook_module.HookEventPullRequestReviewRequest,
webhook_module.HookEventPullRequestMilestone:
return true return true
default: default:
@ -73,6 +114,11 @@ func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEvent
return false return false
} }
case GithubEventIssueComment:
// https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_comment-use-issue_comment
return triggedEvent == webhook_module.HookEventIssueComment ||
triggedEvent == webhook_module.HookEventPullRequestComment
default: default:
return eventName == string(triggedEvent) return eventName == string(triggedEvent)
} }

View file

@ -103,6 +103,12 @@ func TestCanGithubEventMatch(t *testing.T) {
webhook_module.HookEventCreate, webhook_module.HookEventCreate,
true, true,
}, },
{
"create pull request comment",
GithubEventIssueComment,
webhook_module.HookEventPullRequestComment,
true,
},
} }
for _, tc := range testCases { for _, tc := range testCases {

View file

@ -186,7 +186,9 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web
webhook_module.HookEventPullRequest, webhook_module.HookEventPullRequest,
webhook_module.HookEventPullRequestSync, webhook_module.HookEventPullRequestSync,
webhook_module.HookEventPullRequestAssign, webhook_module.HookEventPullRequestAssign,
webhook_module.HookEventPullRequestLabel: webhook_module.HookEventPullRequestLabel,
webhook_module.HookEventPullRequestReviewRequest,
webhook_module.HookEventPullRequestMilestone:
return matchPullRequestEvent(gitRepo, commit, payload.(*api.PullRequestPayload), evt) return matchPullRequestEvent(gitRepo, commit, payload.(*api.PullRequestPayload), evt)
case // pull_request_review case // pull_request_review
@ -362,13 +364,13 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
} else { } else {
// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request // See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request
// Actions with the same name: // Actions with the same name:
// opened, edited, closed, reopened, assigned, unassigned // opened, edited, closed, reopened, assigned, unassigned, review_requested, review_request_removed, milestoned, demilestoned
// Actions need to be converted: // Actions need to be converted:
// synchronized -> synchronize // synchronized -> synchronize
// label_updated -> labeled // label_updated -> labeled
// label_cleared -> unlabeled // label_cleared -> unlabeled
// Unsupported activity types: // Unsupported activity types:
// converted_to_draft, ready_for_review, locked, unlocked, review_requested, review_request_removed, auto_merge_enabled, auto_merge_disabled // converted_to_draft, ready_for_review, locked, unlocked, auto_merge_enabled, auto_merge_disabled, enqueued, dequeued
action := prPayload.Action action := prPayload.Action
switch action { switch action {

View file

@ -8,6 +8,7 @@ import (
"context" "context"
"crypto/rand" "crypto/rand"
"errors" "errors"
"html/template"
"math/big" "math/big"
"strings" "strings"
"sync" "sync"
@ -121,15 +122,15 @@ func Generate(n int) (string, error) {
} }
// BuildComplexityError builds the error message when password complexity checks fail // BuildComplexityError builds the error message when password complexity checks fail
func BuildComplexityError(locale translation.Locale) string { func BuildComplexityError(locale translation.Locale) template.HTML {
var buffer bytes.Buffer var buffer bytes.Buffer
buffer.WriteString(locale.Tr("form.password_complexity")) buffer.WriteString(locale.TrString("form.password_complexity"))
buffer.WriteString("<ul>") buffer.WriteString("<ul>")
for _, c := range requiredList { for _, c := range requiredList {
buffer.WriteString("<li>") buffer.WriteString("<li>")
buffer.WriteString(locale.Tr(c.TrNameOne)) buffer.WriteString(locale.TrString(c.TrNameOne))
buffer.WriteString("</li>") buffer.WriteString("</li>")
} }
buffer.WriteString("</ul>") buffer.WriteString("</ul>")
return buffer.String() return template.HTML(buffer.String())
} }

View file

@ -5,11 +5,13 @@ package base
import ( import (
"math/big" "math/big"
"strings"
"unicode/utf8" "unicode/utf8"
) )
// NaturalSortLess compares two strings so that they could be sorted in natural order // NaturalSortLess compares two strings so that they could be sorted in natural order
func NaturalSortLess(s1, s2 string) bool { func NaturalSortLess(s1, s2 string) bool {
s1, s2 = strings.ToLower(s1), strings.ToLower(s2)
var i1, i2 int var i1, i2 int
for { for {
rune1, j1, end1 := getNextRune(s1, i1) rune1, j1, end1 := getNextRune(s1, i1)

View file

@ -20,4 +20,10 @@ func TestNaturalSortLess(t *testing.T) {
test("a-1-a", "a-1-b", true) test("a-1-a", "a-1-b", true)
test("2", "12", true) test("2", "12", true)
test("a", "ab", true) test("a", "ab", true)
// Test for case insensitive.
test("A", "ab", true)
test("B", "ab", false)
test("a", "AB", true)
test("b", "AB", false)
} }

View file

@ -115,7 +115,7 @@ func CreateTimeLimitCode(data string, minutes int, startInf any) string {
// create sha1 encode string // create sha1 encode string
sh := sha1.New() sh := sha1.New()
_, _ = sh.Write([]byte(fmt.Sprintf("%s%s%s%s%d", data, setting.SecretKey, startStr, endStr, minutes))) _, _ = sh.Write([]byte(fmt.Sprintf("%s%s%s%s%d", data, hex.EncodeToString(setting.GetGeneralTokenSigningSecret()), startStr, endStr, minutes)))
encoded := hex.EncodeToString(sh.Sum(nil)) encoded := hex.EncodeToString(sh.Sum(nil))
code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded) code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
@ -174,7 +174,7 @@ func Int64sToStrings(ints []int64) []string {
func EntryIcon(entry *git.TreeEntry) string { func EntryIcon(entry *git.TreeEntry) string {
switch { switch {
case entry.IsLink(): case entry.IsLink():
te, err := entry.FollowLink() te, _, err := entry.FollowLink()
if err != nil { if err != nil {
log.Debug(err.Error()) log.Debug(err.Error())
return "file-symlink-file" return "file-symlink-file"

View file

@ -10,6 +10,7 @@ package charset
import ( import (
"html/template" "html/template"
"io" "io"
"slices"
"strings" "strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -20,16 +21,29 @@ import (
// RuneNBSP is the codepoint for NBSP // RuneNBSP is the codepoint for NBSP
const RuneNBSP = 0xa0 const RuneNBSP = 0xa0
type escapeContext string
// Keep this consistent with the documentation of [ui].SKIP_ESCAPE_CONTEXTS
// Defines the different contexts that could be used to escape in.
const (
// Wiki pages.
WikiContext escapeContext = "wiki"
// Rendered content (except markup), source code and blames.
FileviewContext escapeContext = "file-view"
// Commits or pull requet's diff.
DiffContext escapeContext = "diff"
)
// EscapeControlHTML escapes the unicode control sequences in a provided html document // EscapeControlHTML escapes the unicode control sequences in a provided html document
func EscapeControlHTML(html template.HTML, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output template.HTML) { func EscapeControlHTML(html template.HTML, locale translation.Locale, context escapeContext, allowed ...rune) (escaped *EscapeStatus, output template.HTML) {
sb := &strings.Builder{} sb := &strings.Builder{}
escaped, _ = EscapeControlReader(strings.NewReader(string(html)), sb, locale, allowed...) // err has been handled in EscapeControlReader escaped, _ = EscapeControlReader(strings.NewReader(string(html)), sb, locale, context, allowed...) // err has been handled in EscapeControlReader
return escaped, template.HTML(sb.String()) return escaped, template.HTML(sb.String())
} }
// EscapeControlReader escapes the unicode control sequences in a provided reader of HTML content and writer in a locale and returns the findings as an EscapeStatus // EscapeControlReader escapes the unicode control sequences in a provided reader of HTML content and writer in a locale and returns the findings as an EscapeStatus
func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, err error) { func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.Locale, context escapeContext, allowed ...rune) (escaped *EscapeStatus, err error) {
if !setting.UI.AmbiguousUnicodeDetection { if !setting.UI.AmbiguousUnicodeDetection || slices.Contains(setting.UI.SkipEscapeContexts, string(context)) {
_, err = io.Copy(writer, reader) _, err = io.Copy(writer, reader)
return &EscapeStatus{}, err return &EscapeStatus{}, err
} }

View file

@ -173,7 +173,7 @@ func (e *escapeStreamer) ambiguousRune(r, c rune) error {
Val: "ambiguous-code-point", Val: "ambiguous-code-point",
}, html.Attribute{ }, html.Attribute{
Key: "data-tooltip-content", Key: "data-tooltip-content",
Val: e.locale.Tr("repo.ambiguous_character", r, c), Val: e.locale.TrString("repo.ambiguous_character", r, c),
}); err != nil { }); err != nil {
return err return err
} }

View file

@ -4,6 +4,7 @@
package charset package charset
import ( import (
"html/template"
"strings" "strings"
"testing" "testing"
@ -14,6 +15,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
var testContext = escapeContext("test")
type escapeControlTest struct { type escapeControlTest struct {
name string name string
text string text string
@ -159,7 +162,7 @@ func TestEscapeControlReader(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
output := &strings.Builder{} output := &strings.Builder{}
status, err := EscapeControlReader(strings.NewReader(tt.text), output, &translation.MockLocale{}) status, err := EscapeControlReader(strings.NewReader(tt.text), output, &translation.MockLocale{}, testContext)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, tt.status, *status) assert.Equal(t, tt.status, *status)
assert.Equal(t, tt.result, output.String()) assert.Equal(t, tt.result, output.String())
@ -169,9 +172,22 @@ func TestEscapeControlReader(t *testing.T) {
func TestSettingAmbiguousUnicodeDetection(t *testing.T) { func TestSettingAmbiguousUnicodeDetection(t *testing.T) {
defer test.MockVariableValue(&setting.UI.AmbiguousUnicodeDetection, true)() defer test.MockVariableValue(&setting.UI.AmbiguousUnicodeDetection, true)()
_, out := EscapeControlHTML("a test", &translation.MockLocale{})
_, out := EscapeControlHTML("a test", &translation.MockLocale{}, testContext)
assert.EqualValues(t, `a<span class="escaped-code-point" data-escaped="[U+00A0]"><span class="char"> </span></span>test`, out) assert.EqualValues(t, `a<span class="escaped-code-point" data-escaped="[U+00A0]"><span class="char"> </span></span>test`, out)
setting.UI.AmbiguousUnicodeDetection = false setting.UI.AmbiguousUnicodeDetection = false
_, out = EscapeControlHTML("a test", &translation.MockLocale{}) _, out = EscapeControlHTML("a test", &translation.MockLocale{}, testContext)
assert.EqualValues(t, `a test`, out) assert.EqualValues(t, `a test`, out)
} }
func TestAmbiguousUnicodeDetectionContext(t *testing.T) {
defer test.MockVariableValue(&setting.UI.SkipEscapeContexts, []string{"test"})()
input := template.HTML("a test")
_, out := EscapeControlHTML(input, &translation.MockLocale{}, escapeContext("not-test"))
assert.EqualValues(t, `a<span class="escaped-code-point" data-escaped="[U+00A0]"><span class="char"> </span></span>test`, out)
_, out = EscapeControlHTML(input, &translation.MockLocale{}, testContext)
assert.EqualValues(t, input, out)
}

View file

@ -247,7 +247,7 @@ func APIContexter() func(http.Handler) http.Handler {
// NotFound handles 404s for APIContext // NotFound handles 404s for APIContext
// String will replace message, errors will be added to a slice // String will replace message, errors will be added to a slice
func (ctx *APIContext) NotFound(objs ...any) { func (ctx *APIContext) NotFound(objs ...any) {
message := ctx.Tr("error.not_found") message := ctx.Locale.TrString("error.not_found")
var errors []string var errors []string
for _, obj := range objs { for _, obj := range objs {
// Ignore nil // Ignore nil
@ -309,12 +309,6 @@ func RepoRefForAPI(next http.Handler) http.Handler {
return return
} }
objectFormat, err := ctx.Repo.GitRepo.GetObjectFormat()
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
return
}
if ref := ctx.FormTrim("ref"); len(ref) > 0 { if ref := ctx.FormTrim("ref"); len(ref) > 0 {
commit, err := ctx.Repo.GitRepo.GetCommit(ref) commit, err := ctx.Repo.GitRepo.GetCommit(ref)
if err != nil { if err != nil {
@ -333,6 +327,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
} }
refName := getRefName(ctx.Base, ctx.Repo, RepoRefAny) refName := getRefName(ctx.Base, ctx.Repo, RepoRefAny)
var err error
if ctx.Repo.GitRepo.IsBranchExist(refName) { if ctx.Repo.GitRepo.IsBranchExist(refName) {
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName) ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
@ -348,7 +343,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
return return
} }
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
} else if len(refName) == objectFormat.FullLength() { } else if len(refName) == ctx.Repo.GetObjectFormat().FullLength() {
ctx.Repo.CommitID = refName ctx.Repo.CommitID = refName
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName) ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
if err != nil { if err != nil {

View file

@ -6,6 +6,7 @@ package context
import ( import (
"context" "context"
"fmt" "fmt"
"html/template"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
@ -286,11 +287,11 @@ func (b *Base) cleanUp() {
} }
} }
func (b *Base) Tr(msg string, args ...any) string { func (b *Base) Tr(msg string, args ...any) template.HTML {
return b.Locale.Tr(msg, args...) return b.Locale.Tr(msg, args...)
} }
func (b *Base) TrN(cnt any, key1, keyN string, args ...any) string { func (b *Base) TrN(cnt any, key1, keyN string, args ...any) template.HTML {
return b.Locale.TrN(cnt, key1, keyN, args...) return b.Locale.TrN(cnt, key1, keyN, args...)
} }

View file

@ -6,7 +6,8 @@ package context
import ( import (
"context" "context"
"html" "encoding/hex"
"fmt"
"html/template" "html/template"
"io" "io"
"net/http" "net/http"
@ -71,16 +72,6 @@ func init() {
}) })
} }
// TrHTMLEscapeArgs runs ".Locale.Tr()" but pre-escapes all arguments with html.EscapeString.
// This is useful if the locale message is intended to only produce HTML content.
func (ctx *Context) TrHTMLEscapeArgs(msg string, args ...string) string {
trArgs := make([]any, len(args))
for i, arg := range args {
trArgs[i] = html.EscapeString(arg)
}
return ctx.Locale.Tr(msg, trArgs...)
}
type webContextKeyType struct{} type webContextKeyType struct{}
var WebContextKey = webContextKeyType{} var WebContextKey = webContextKeyType{}
@ -134,7 +125,7 @@ func NewWebContext(base *Base, render Render, session session.Store) *Context {
func Contexter() func(next http.Handler) http.Handler { func Contexter() func(next http.Handler) http.Handler {
rnd := templates.HTMLRenderer() rnd := templates.HTMLRenderer()
csrfOpts := CsrfOptions{ csrfOpts := CsrfOptions{
Secret: setting.SecretKey, Secret: hex.EncodeToString(setting.GetGeneralTokenSigningSecret()),
Cookie: setting.CSRFCookieName, Cookie: setting.CSRFCookieName,
SetCookie: true, SetCookie: true,
Secure: setting.SessionConfig.Secure, Secure: setting.SessionConfig.Secure,
@ -207,6 +198,7 @@ func Contexter() func(next http.Handler) http.Handler {
// FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these // FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
ctx.Data["DisableStars"] = setting.Repository.DisableStars ctx.Data["DisableStars"] = setting.Repository.DisableStars
ctx.Data["DisableForks"] = setting.Repository.DisableForks
ctx.Data["EnableActions"] = setting.Actions.Enabled ctx.Data["EnableActions"] = setting.Actions.Enabled
ctx.Data["ManifestData"] = setting.ManifestData ctx.Data["ManifestData"] = setting.ManifestData
@ -253,6 +245,13 @@ func (ctx *Context) JSONOK() {
ctx.JSON(http.StatusOK, map[string]any{"ok": true}) // this is only a dummy response, frontend seldom uses it ctx.JSON(http.StatusOK, map[string]any{"ok": true}) // this is only a dummy response, frontend seldom uses it
} }
func (ctx *Context) JSONError(msg string) { func (ctx *Context) JSONError(msg any) {
ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": msg}) switch v := msg.(type) {
case string:
ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "text"})
case template.HTML:
ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "html"})
default:
panic(fmt.Sprintf("unsupported type: %T", msg))
}
} }

View file

@ -90,6 +90,20 @@ func (ctx *Context) HTML(status int, name base.TplName) {
} }
} }
// JSONTemplate renders the template as JSON response
// keep in mind that the template is processed in HTML context, so JSON-things should be handled carefully, eg: by JSEscape
func (ctx *Context) JSONTemplate(tmpl base.TplName) {
t, err := ctx.Render.TemplateLookup(string(tmpl), nil)
if err != nil {
ctx.ServerError("unable to find template", err)
return
}
ctx.Resp.Header().Set("Content-Type", "application/json")
if err = t.Execute(ctx.Resp, ctx.Data); err != nil {
ctx.ServerError("unable to execute template", err)
}
}
// RenderToString renders the template content to a string // RenderToString renders the template content to a string
func (ctx *Context) RenderToString(name base.TplName, data map[string]any) (string, error) { func (ctx *Context) RenderToString(name base.TplName, data map[string]any) (string, error) {
var buf strings.Builder var buf strings.Builder
@ -98,12 +112,11 @@ func (ctx *Context) RenderToString(name base.TplName, data map[string]any) (stri
} }
// RenderWithErr used for page has form validation but need to prompt error to users. // RenderWithErr used for page has form validation but need to prompt error to users.
func (ctx *Context) RenderWithErr(msg string, tpl base.TplName, form any) { func (ctx *Context) RenderWithErr(msg any, tpl base.TplName, form any) {
if form != nil { if form != nil {
middleware.AssignForm(form, ctx.Data) middleware.AssignForm(form, ctx.Data)
} }
ctx.Flash.ErrorMsg = msg ctx.Flash.Error(msg, true)
ctx.Data["Flash"] = ctx.Flash
ctx.HTML(http.StatusOK, tpl) ctx.HTML(http.StatusOK, tpl)
} }

View file

@ -5,10 +5,7 @@ package context
import ( import (
"context" "context"
"errors"
"time" "time"
"code.gitea.io/gitea/modules/log"
) )
var _ context.Context = TemplateContext(nil) var _ context.Context = TemplateContext(nil)
@ -36,14 +33,3 @@ func (c TemplateContext) Err() error {
func (c TemplateContext) Value(key any) any { func (c TemplateContext) Value(key any) any {
return c.parentContext().Value(key) return c.parentContext().Value(key)
} }
// DataRaceCheck checks whether the template context function "ctx()" returns the consistent context
// as the current template's rendering context (request context), to help to find data race issues as early as possible.
// When the code is proven to be correct and stable, this function should be removed.
func (c TemplateContext) DataRaceCheck(dataCtx context.Context) (string, error) {
if c.parentContext() != dataCtx {
log.Error("TemplateContext.DataRaceCheck: parent context mismatch\n%s", log.Stack(2))
return "", errors.New("parent context mismatch")
}
return "", nil
}

View file

@ -11,6 +11,8 @@ import (
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
) )
@ -255,6 +257,19 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
ctx.Data["CanReadProjects"] = ctx.Org.CanReadUnit(ctx, unit.TypeProjects) ctx.Data["CanReadProjects"] = ctx.Org.CanReadUnit(ctx, unit.TypeProjects)
ctx.Data["CanReadPackages"] = ctx.Org.CanReadUnit(ctx, unit.TypePackages) ctx.Data["CanReadPackages"] = ctx.Org.CanReadUnit(ctx, unit.TypePackages)
ctx.Data["CanReadCode"] = ctx.Org.CanReadUnit(ctx, unit.TypeCode) ctx.Data["CanReadCode"] = ctx.Org.CanReadUnit(ctx, unit.TypeCode)
ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
if len(ctx.ContextUser.Description) != 0 {
content, err := markdown.RenderString(&markup.RenderContext{
Metas: map[string]string{"mode": "document"},
Ctx: ctx,
}, ctx.ContextUser.Description)
if err != nil {
ctx.ServerError("RenderString", err)
return
}
ctx.Data["RenderedDescription"] = content
}
} }
// OrgAssignment returns a middleware to handle organization assignment // OrgAssignment returns a middleware to handle organization assignment

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