mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-24 08:57:03 -05:00
Merge branch 'forgejo' into forgejo-federated-star
This commit is contained in:
commit
70ae102597
419 changed files with 9562 additions and 3898 deletions
|
@ -2,9 +2,10 @@ root = "."
|
||||||
tmp_dir = ".air"
|
tmp_dir = ".air"
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
|
pre_cmd = ["killall -9 gitea 2>/dev/null || true"] # kill off potential zombie processes from previous runs
|
||||||
cmd = "make --no-print-directory backend"
|
cmd = "make --no-print-directory backend"
|
||||||
bin = "gitea"
|
bin = "gitea"
|
||||||
delay = 1000
|
delay = 2000
|
||||||
include_ext = ["go", "tmpl"]
|
include_ext = ["go", "tmpl"]
|
||||||
include_file = ["main.go"]
|
include_file = ["main.go"]
|
||||||
include_dir = ["cmd", "models", "modules", "options", "routers", "services"]
|
include_dir = ["cmd", "models", "modules", "options", "routers", "services"]
|
||||||
|
@ -15,8 +16,11 @@ exclude_dir = [
|
||||||
"modules/avatar/testdata",
|
"modules/avatar/testdata",
|
||||||
"modules/git/tests",
|
"modules/git/tests",
|
||||||
"modules/migration/file_format_testdata",
|
"modules/migration/file_format_testdata",
|
||||||
|
"modules/markup/tests/repo/repo1_filepreview",
|
||||||
"routers/private/tests",
|
"routers/private/tests",
|
||||||
"services/gitdiff/testdata",
|
"services/gitdiff/testdata",
|
||||||
|
"services/migrations/testdata",
|
||||||
|
"services/webhook/sourcehut/testdata",
|
||||||
]
|
]
|
||||||
exclude_regex = ["_test.go$", "_gen.go$"]
|
exclude_regex = ["_test.go$", "_gen.go$"]
|
||||||
stop_on_error = true
|
stop_on_error = true
|
||||||
|
|
|
@ -73,6 +73,7 @@ package "code.gitea.io/gitea/models/migrations/base"
|
||||||
func MainTest
|
func MainTest
|
||||||
|
|
||||||
package "code.gitea.io/gitea/models/organization"
|
package "code.gitea.io/gitea/models/organization"
|
||||||
|
func GetTeamNamesByID
|
||||||
func UpdateTeamUnits
|
func UpdateTeamUnits
|
||||||
func (SearchMembersOptions).ToConds
|
func (SearchMembersOptions).ToConds
|
||||||
func UsersInTeamsCount
|
func UsersInTeamsCount
|
||||||
|
@ -139,6 +140,7 @@ package "code.gitea.io/gitea/models/user"
|
||||||
func GetUserAllSettings
|
func GetUserAllSettings
|
||||||
func DeleteUserSetting
|
func DeleteUserSetting
|
||||||
func GetUserEmailsByNames
|
func GetUserEmailsByNames
|
||||||
|
func GetUserNamesByIDs
|
||||||
|
|
||||||
package "code.gitea.io/gitea/modules/assetfs"
|
package "code.gitea.io/gitea/modules/assetfs"
|
||||||
func Bindata
|
func Bindata
|
||||||
|
|
|
@ -94,6 +94,9 @@ cpu.out
|
||||||
/.air
|
/.air
|
||||||
/.go-licenses
|
/.go-licenses
|
||||||
|
|
||||||
|
# Files and folders that were previously generated
|
||||||
|
/public/assets/img/webpack
|
||||||
|
|
||||||
# Snapcraft
|
# Snapcraft
|
||||||
snap/.snapcraft/
|
snap/.snapcraft/
|
||||||
parts/
|
parts/
|
||||||
|
|
|
@ -4,6 +4,7 @@ reportUnusedDisableDirectives: true
|
||||||
ignorePatterns:
|
ignorePatterns:
|
||||||
- /web_src/js/vendor
|
- /web_src/js/vendor
|
||||||
- /web_src/fomantic
|
- /web_src/fomantic
|
||||||
|
- /public/assets/js
|
||||||
|
|
||||||
parserOptions:
|
parserOptions:
|
||||||
sourceType: module
|
sourceType: module
|
||||||
|
|
2
.forgejo/testdata/build-release/Dockerfile
vendored
2
.forgejo/testdata/build-release/Dockerfile
vendored
|
@ -1,4 +1,6 @@
|
||||||
FROM code.forgejo.org/oci/alpine:3.19
|
FROM code.forgejo.org/oci/alpine:3.19
|
||||||
ARG RELEASE_VERSION=unkown
|
ARG RELEASE_VERSION=unkown
|
||||||
|
LABEL maintainer="contact@forgejo.org" \
|
||||||
|
org.opencontainers.image.version="${RELEASE_VERSION}"
|
||||||
RUN mkdir -p /app/gitea
|
RUN mkdir -p /app/gitea
|
||||||
RUN ( echo '#!/bin/sh' ; echo "echo forgejo v$RELEASE_VERSION" ) > /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
|
||||||
|
|
|
@ -45,39 +45,13 @@ jobs:
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
${{ toJSON(github) }}
|
${{ toJSON(github) }}
|
||||||
EOF
|
EOF
|
||||||
- name: Fetch labels
|
- uses: https://code.forgejo.org/actions/git-backporting@v4.8.0
|
||||||
id: fetch-labels
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -x
|
|
||||||
echo "Labels retrieved below"
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
|
||||||
apt-get update -qq
|
|
||||||
apt-get -q install -qq -y jq
|
|
||||||
filtered_labels=$(echo "$LABELS" | jq -c 'map(select(.name | startswith("backport/v")))')
|
|
||||||
echo "FILTERED_LABELS=${filtered_labels}" >> $GITHUB_ENV
|
|
||||||
env:
|
|
||||||
LABELS: ${{ toJSON(github.event.pull_request.labels) }}
|
|
||||||
- name: Extract targets
|
|
||||||
id: extract-targets
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -x
|
|
||||||
targets="$(echo $FILTERED_LABELS | jq -c '[.[] | .name | sub("backport/"; "")]')"
|
|
||||||
echo "targets=$(echo $targets)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Printing info
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo "targets: ${{ steps.extract-targets.outputs.targets }}"
|
|
||||||
echo "target-branch: ${{ fromJSON(steps.extract-targets.outputs.targets)[0] }}"
|
|
||||||
echo "pull-request: ${{ github.event.pull_request.url }}"
|
|
||||||
|
|
||||||
- uses: https://code.forgejo.org/forgejo/git-backporting@b2554a678d5ea2814f0df3abad2ac4fcdee2d81f
|
|
||||||
with:
|
with:
|
||||||
target-branch: ${{ fromJSON(steps.extract-targets.outputs.targets)[0] }}/forgejo
|
target-branch-pattern: "^backport/(?<target>(v.*))$"
|
||||||
strategy: ort
|
strategy: ort
|
||||||
strategy-option: find-renames
|
strategy-option: find-renames
|
||||||
cherry-pick-options: -x
|
cherry-pick-options: -x
|
||||||
auth: ${{ secrets.BACKPORT_TOKEN }}
|
auth: ${{ secrets.BACKPORT_TOKEN }}
|
||||||
pull-request: ${{ github.event.pull_request.url }}
|
pull-request: ${{ github.event.pull_request.url }}
|
||||||
|
auto-no-squash: true
|
||||||
|
enable-err-notification: true
|
||||||
|
|
|
@ -159,7 +159,7 @@ 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@v3
|
uses: https://code.forgejo.org/forgejo/forgejo-build-publish/build@v5.1.1
|
||||||
with:
|
with:
|
||||||
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
||||||
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
||||||
|
@ -173,11 +173,12 @@ jobs:
|
||||||
binary-name: forgejo
|
binary-name: forgejo
|
||||||
binary-path: /app/gitea/gitea
|
binary-path: /app/gitea/gitea
|
||||||
override: "${{ steps.release-info.outputs.override }}"
|
override: "${{ steps.release-info.outputs.override }}"
|
||||||
|
verify-labels: "maintainer=contact@forgejo.org,org.opencontainers.image.version=${{ steps.release-info.outputs.version }}"
|
||||||
verbose: ${{ vars.VERBOSE || secrets.VERBOSE || 'false' }}
|
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@v3
|
uses: https://code.forgejo.org/forgejo/forgejo-build-publish/build@v5.1.1
|
||||||
with:
|
with:
|
||||||
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
||||||
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
||||||
|
@ -190,6 +191,7 @@ jobs:
|
||||||
suffix: -rootless
|
suffix: -rootless
|
||||||
dockerfile: Dockerfile.rootless
|
dockerfile: Dockerfile.rootless
|
||||||
override: "${{ steps.release-info.outputs.override }}"
|
override: "${{ steps.release-info.outputs.override }}"
|
||||||
|
verify-labels: "maintainer=contact@forgejo.org,org.opencontainers.image.version=${{ steps.release-info.outputs.version }}"
|
||||||
verbose: ${{ vars.VERBOSE || secrets.VERBOSE || 'false' }}
|
verbose: ${{ vars.VERBOSE || secrets.VERBOSE || 'false' }}
|
||||||
|
|
||||||
- name: end-to-end tests
|
- name: end-to-end tests
|
||||||
|
|
|
@ -42,7 +42,7 @@ 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@v4
|
uses: https://code.forgejo.org/forgejo/forgejo-build-publish/publish@v5
|
||||||
with:
|
with:
|
||||||
from-forgejo: ${{ vars.FORGEJO }}
|
from-forgejo: ${{ vars.FORGEJO }}
|
||||||
to-forgejo: ${{ vars.FORGEJO }}
|
to-forgejo: ${{ vars.FORGEJO }}
|
||||||
|
|
|
@ -22,10 +22,11 @@ jobs:
|
||||||
|
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/visualon/renovate:37.280.0
|
image: ghcr.io/visualon/renovate:37.323.3
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: https://code.forgejo.org/actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
- name: Load renovate repo cache
|
||||||
|
uses: https://code.forgejo.org/actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
.tmp/cache/renovate/repository
|
.tmp/cache/renovate/repository
|
||||||
|
@ -33,7 +34,8 @@ jobs:
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
repo-cache-
|
repo-cache-
|
||||||
|
|
||||||
- run: renovate
|
- name: Run renovate
|
||||||
|
run: renovate
|
||||||
env:
|
env:
|
||||||
GITHUB_COM_TOKEN: ${{ secrets.RENOVATE_GITHUB_COM_TOKEN }}
|
GITHUB_COM_TOKEN: ${{ secrets.RENOVATE_GITHUB_COM_TOKEN }}
|
||||||
LOG_LEVEL: debug
|
LOG_LEVEL: debug
|
||||||
|
|
|
@ -140,6 +140,8 @@ jobs:
|
||||||
env:
|
env:
|
||||||
MINIO_ROOT_USER: 123456
|
MINIO_ROOT_USER: 123456
|
||||||
MINIO_ROOT_PASSWORD: 12345678
|
MINIO_ROOT_PASSWORD: 12345678
|
||||||
|
ldap:
|
||||||
|
image: docker.io/gitea/test-openldap:latest
|
||||||
pgsql:
|
pgsql:
|
||||||
image: 'docker.io/postgres:15'
|
image: 'docker.io/postgres:15'
|
||||||
env:
|
env:
|
||||||
|
@ -176,6 +178,7 @@ jobs:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
RACE_ENABLED: true
|
RACE_ENABLED: true
|
||||||
USE_REPO_TEST_DIR: 1
|
USE_REPO_TEST_DIR: 1
|
||||||
|
TEST_LDAP: 1
|
||||||
test-sqlite:
|
test-sqlite:
|
||||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
|
|
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1,5 +1,6 @@
|
||||||
* text=auto eol=lf
|
* text=auto eol=lf
|
||||||
*.tmpl linguist-language=go-html-template
|
*.tmpl linguist-language=go-html-template
|
||||||
|
*.pb.go linguist-generated
|
||||||
/assets/*.json linguist-generated
|
/assets/*.json linguist-generated
|
||||||
/public/assets/img/svg/*.svg linguist-generated
|
/public/assets/img/svg/*.svg linguist-generated
|
||||||
/templates/swagger/v1_json.tmpl linguist-generated
|
/templates/swagger/v1_json.tmpl linguist-generated
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -101,6 +101,9 @@ cpu.out
|
||||||
/.go-licenses
|
/.go-licenses
|
||||||
/.cur-deadcode-out
|
/.cur-deadcode-out
|
||||||
|
|
||||||
|
# Files and folders that were previously generated
|
||||||
|
/public/assets/img/webpack
|
||||||
|
|
||||||
# Snapcraft
|
# Snapcraft
|
||||||
/gitea_a*.txt
|
/gitea_a*.txt
|
||||||
snap/.snapcraft/
|
snap/.snapcraft/
|
||||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,3 +0,0 @@
|
||||||
[submodule "manual-testing"]
|
|
||||||
path = manual-testing
|
|
||||||
url = https://codeberg.org/forgejo/forgejo-manual-testing
|
|
|
@ -4,21 +4,4 @@ The Forgejo project is run by a community of people who are expected to follow t
|
||||||
|
|
||||||
Sensitive security-related issues should be reported to [security@forgejo.org](mailto:security@forgejo.org) using [encryption](https://keyoxide.org/security@forgejo.org).
|
Sensitive security-related issues should be reported to [security@forgejo.org](mailto:security@forgejo.org) using [encryption](https://keyoxide.org/security@forgejo.org).
|
||||||
|
|
||||||
## For everyone involved
|
You can find links to the different aspects of Developer documentation on this page: [Forgejo developer guide](https://forgejo.org/docs/next/developer/).
|
||||||
|
|
||||||
- [Documentation](https://forgejo.org/docs/next/)
|
|
||||||
- [Code of Conduct](https://forgejo.org/docs/latest/developer/coc/)
|
|
||||||
- [Bugs, features, security and others discussions](https://forgejo.org/docs/latest/developer/discussions/)
|
|
||||||
- [Governance](https://forgejo.org/docs/latest/developer/governance/)
|
|
||||||
- [Sustainability and funding](https://codeberg.org/forgejo/sustainability/src/branch/main/README.md)
|
|
||||||
|
|
||||||
## For contributors
|
|
||||||
|
|
||||||
- [Developer Certificate of Origin (DCO)](https://forgejo.org/docs/latest/developer/dco/)
|
|
||||||
- [Development workflow](https://forgejo.org/docs/latest/developer/workflow/)
|
|
||||||
- [Compiling from source](https://forgejo.org/docs/latest/developer/from-source/)
|
|
||||||
|
|
||||||
## For maintainers
|
|
||||||
|
|
||||||
- [Release management](https://forgejo.org/docs/latest/developer/release/)
|
|
||||||
- [Secrets](https://forgejo.org/docs/latest/developer/secrets/)
|
|
||||||
|
|
12
Dockerfile
12
Dockerfile
|
@ -52,7 +52,17 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
|
||||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
||||||
|
|
||||||
FROM docker.io/library/alpine:3.19
|
FROM docker.io/library/alpine:3.19
|
||||||
LABEL maintainer="contact@forgejo.org"
|
ARG RELEASE_VERSION
|
||||||
|
LABEL maintainer="contact@forgejo.org" \
|
||||||
|
org.opencontainers.image.authors="Forgejo" \
|
||||||
|
org.opencontainers.image.url="https://forgejo.org" \
|
||||||
|
org.opencontainers.image.documentation="https://forgejo.org/download/#container-image" \
|
||||||
|
org.opencontainers.image.source="https://codeberg.org/forgejo/forgejo" \
|
||||||
|
org.opencontainers.image.version="${RELEASE_VERSION}" \
|
||||||
|
org.opencontainers.image.vendor="Forgejo" \
|
||||||
|
org.opencontainers.image.licenses="MIT" \
|
||||||
|
org.opencontainers.image.title="Forgejo. Beyond coding. We forge." \
|
||||||
|
org.opencontainers.image.description="Forgejo is a self-hosted lightweight software forge. Easy to install and low maintenance, it just does the job."
|
||||||
|
|
||||||
EXPOSE 22 3000
|
EXPOSE 22 3000
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,16 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
|
||||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
||||||
|
|
||||||
FROM docker.io/library/alpine:3.19
|
FROM docker.io/library/alpine:3.19
|
||||||
LABEL maintainer="contact@forgejo.org"
|
LABEL maintainer="contact@forgejo.org" \
|
||||||
|
org.opencontainers.image.authors="Forgejo" \
|
||||||
|
org.opencontainers.image.url="https://forgejo.org" \
|
||||||
|
org.opencontainers.image.documentation="https://forgejo.org/download/#container-image" \
|
||||||
|
org.opencontainers.image.source="https://codeberg.org/forgejo/forgejo" \
|
||||||
|
org.opencontainers.image.version="${RELEASE_VERSION}" \
|
||||||
|
org.opencontainers.image.vendor="Forgejo" \
|
||||||
|
org.opencontainers.image.licenses="MIT" \
|
||||||
|
org.opencontainers.image.title="Forgejo. Beyond coding. We forge." \
|
||||||
|
org.opencontainers.image.description="Forgejo is a self-hosted lightweight software forge. Easy to install and low maintenance, it just does the job."
|
||||||
|
|
||||||
EXPOSE 2222 3000
|
EXPOSE 2222 3000
|
||||||
|
|
||||||
|
|
45
Makefile
45
Makefile
|
@ -26,18 +26,18 @@ DIFF ?= diff --unified
|
||||||
|
|
||||||
XGO_VERSION := go-1.21.x
|
XGO_VERSION := go-1.21.x
|
||||||
|
|
||||||
AIR_PACKAGE ?= github.com/cosmtrek/air@v1.49.0
|
AIR_PACKAGE ?= github.com/cosmtrek/air@v1.49.0 # renovate: datasource=go
|
||||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0
|
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v2/cmd/editorconfig-checker@2.8.0 # renovate: datasource=go
|
||||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0
|
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0 # renovate: datasource=go
|
||||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.56.1
|
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.57.2 # renovate: datasource=go
|
||||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11
|
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 # renovate: datasource=go
|
||||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.4.1
|
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.4.1 # renovate: datasource=go
|
||||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.6-0.20240201115257-bcc7c78b7786
|
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.6-0.20240201115257-bcc7c78b7786 # renovate: datasource=go
|
||||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest # renovate: datasource=go
|
||||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0
|
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasource=go
|
||||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.0.3
|
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go
|
||||||
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.6.26
|
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.6.27 # renovate: datasource=go
|
||||||
DEADCODE_PACKAGE ?= golang.org/x/tools/internal/cmd/deadcode@v0.14.0
|
DEADCODE_PACKAGE ?= golang.org/x/tools/internal/cmd/deadcode@v0.14.0 # renovate: datasource=go
|
||||||
|
|
||||||
DOCKER_IMAGE ?= gitea/gitea
|
DOCKER_IMAGE ?= gitea/gitea
|
||||||
DOCKER_TAG ?= latest
|
DOCKER_TAG ?= latest
|
||||||
|
@ -121,7 +121,6 @@ LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeV
|
||||||
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
|
||||||
|
|
||||||
ifeq ($(HAS_GO), yes)
|
ifeq ($(HAS_GO), yes)
|
||||||
GO_PACKAGES ?= $(filter-out code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
|
||||||
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) $(shell $(GO) list code.gitea.io/gitea/models/forgejo_migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) $(shell $(GO) list code.gitea.io/gitea/models/forgejo_migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
@ -155,9 +154,9 @@ 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
|
||||||
|
|
||||||
ESLINT_FILES := web_src/js tools *.config.js tests/e2e
|
ESLINT_FILES := web_src/js tools *.js tests/e2e
|
||||||
STYLELINT_FILES := web_src/css web_src/js/components/*.vue
|
STYLELINT_FILES := web_src/css web_src/js/components/*.vue
|
||||||
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github
|
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github $(wildcard *.go *.js *.md *.yml *.yaml *.toml)
|
||||||
|
|
||||||
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)
|
||||||
|
@ -245,7 +244,7 @@ help:
|
||||||
@echo " - show-version-major show major release number only"
|
@echo " - show-version-major show major release number only"
|
||||||
@echo " - test-frontend test frontend files"
|
@echo " - test-frontend test frontend files"
|
||||||
@echo " - test-backend test backend files"
|
@echo " - test-backend test backend files"
|
||||||
@echo " - test-e2e[\#TestSpecificName] test end to end using playwright"
|
@echo " - test-e2e-sqlite[\#name.test.e2e] test end to end using playwright and sqlite"
|
||||||
@echo " - update update js and py dependencies"
|
@echo " - update update js and py dependencies"
|
||||||
@echo " - update-js update js dependencies"
|
@echo " - update-js update js dependencies"
|
||||||
@echo " - update-py update py dependencies"
|
@echo " - update-py update py dependencies"
|
||||||
|
@ -312,7 +311,7 @@ clean:
|
||||||
|
|
||||||
.PHONY: fmt
|
.PHONY: fmt
|
||||||
fmt:
|
fmt:
|
||||||
GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}'
|
@GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}'
|
||||||
$(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl'))
|
$(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl'))
|
||||||
@# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only
|
@# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only
|
||||||
@# whitespace before it
|
@# whitespace before it
|
||||||
|
@ -335,8 +334,8 @@ ifneq "$(TAGS)" "$(shell cat $(TAGS_EVIDENCE) 2>/dev/null)"
|
||||||
TAGS_PREREQ := $(TAGS_EVIDENCE)
|
TAGS_PREREQ := $(TAGS_EVIDENCE)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
OAPI_CODEGEN_PACKAGE ?= github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.12.4
|
OAPI_CODEGEN_PACKAGE ?= github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.12.4 # renovate: datasource=go
|
||||||
KIN_OPENAPI_CODEGEN_PACKAGE ?= github.com/getkin/kin-openapi/cmd/validate@v0.114.0
|
KIN_OPENAPI_CODEGEN_PACKAGE ?= github.com/getkin/kin-openapi/cmd/validate@v0.114.0 # renovate: datasource=go
|
||||||
FORGEJO_API_SERVER = routers/api/forgejo/v1/generated.go
|
FORGEJO_API_SERVER = routers/api/forgejo/v1/generated.go
|
||||||
|
|
||||||
.PHONY: generate-forgejo-api
|
.PHONY: generate-forgejo-api
|
||||||
|
@ -453,7 +452,7 @@ lint-go-windows:
|
||||||
.PHONY: lint-go-vet
|
.PHONY: lint-go-vet
|
||||||
lint-go-vet:
|
lint-go-vet:
|
||||||
@echo "Running go vet..."
|
@echo "Running go vet..."
|
||||||
@$(GO) vet $(GO_PACKAGES)
|
@$(GO) vet ./...
|
||||||
|
|
||||||
.PHONY: lint-editorconfig
|
.PHONY: lint-editorconfig
|
||||||
lint-editorconfig:
|
lint-editorconfig:
|
||||||
|
@ -628,6 +627,10 @@ test-e2e-sqlite: playwright e2e.sqlite.test generate-ini-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 -test.run TestE2e/$*
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./e2e.sqlite.test -test.run TestE2e/$*
|
||||||
|
|
||||||
|
.PHONY: test-e2e-sqlite-firefox\#%
|
||||||
|
test-e2e-sqlite-firefox\#%: playwright e2e.sqlite.test generate-ini-sqlite
|
||||||
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini PLAYWRIGHT_PROJECT=firefox ./e2e.sqlite.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
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./e2e.mysql.test -test.run TestE2e
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./e2e.mysql.test -test.run TestE2e
|
||||||
|
@ -764,7 +767,7 @@ generate-backend: $(TAGS_PREREQ) generate-go
|
||||||
.PHONY: generate-go
|
.PHONY: generate-go
|
||||||
generate-go: $(TAGS_PREREQ)
|
generate-go: $(TAGS_PREREQ)
|
||||||
@echo "Running go generate..."
|
@echo "Running go generate..."
|
||||||
@CC= GOOS= GOARCH= $(GO) generate -tags '$(TAGS)' $(GO_PACKAGES)
|
@CC= GOOS= GOARCH= $(GO) generate -tags '$(TAGS)' ./...
|
||||||
|
|
||||||
.PHONY: merge-locales
|
.PHONY: merge-locales
|
||||||
merge-locales:
|
merge-locales:
|
||||||
|
|
515
RELEASE-NOTES.md
515
RELEASE-NOTES.md
|
@ -1,8 +1,515 @@
|
||||||
# Release Notes
|
# Release Notes
|
||||||
|
|
||||||
A Forgejo release is published shortly after a Gitea release is published and they have [matching release numbers](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/CONTRIBUTING/RELEASE.md#release-numbering). Additional Forgejo releases may be published to address urgent security issues or bug fixes.
|
A minor or major Forgejo release is published every [three months](https://forgejo.org/docs/latest/user/versions/), with more patch releases in between depending on the severity of the bug and security fixes it contains.
|
||||||
|
|
||||||
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).
|
A [patch or minor release](https://semver.org/spec/v2.0.0.html) (e.g. upgrading from v7.0.0 to v7.0.1 or v7.1.0) does not require manual intervention. But [major releases](https://semver.org/spec/v2.0.0.html#spec-item-8) where the first version number changes (e.g. upgrading from v1.21 to v7.0) contain breaking changes and the release notes explain how to deal with them.
|
||||||
|
|
||||||
|
## Upcoming releases (not available yet)
|
||||||
|
|
||||||
|
- [8.0.0](/release-notes/8.0.0/)
|
||||||
|
|
||||||
|
## 7.0.0
|
||||||
|
|
||||||
|
The [complete list of commits](https://codeberg.org/forgejo/forgejo/commits/branch/v7.0/forgejo) included in the `Forgejo v7.0.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 origin/v1.21/forgejo..origin/v7.0/forgejo
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Regressions and workarounds:**
|
||||||
|
* Running the [`forgejo doctor check --fix`](https://forgejo.org/docs/v7.0/admin/command-line/#doctor-check) CLI command or setting [`[cron.gc_lfs].ENABLED=true`](https://forgejo.org/docs/v7.0/admin/config-cheat-sheet/#cron---garbage-collect-lfs-pointers-in-repositories-crongc_lfs) (the default is `false`) will corrupt the LFS storage. The workaround is to not run the doctor CLI command and disable the `cron.gc_lfs`. This regression will be [fixed in 7.0.1](https://codeberg.org/forgejo/forgejo/issues/3438).
|
||||||
|
* The [`fogejo admin user create`](https://forgejo.org/docs/v7.0/admin/command-line/#admin-user-create) CLI command [requires a password](https://codeberg.org/forgejo/forgejo/commit/b122c6ef8b9254120432aed373cbe075331132ac) change by default when creating the first user and the `--admin` flag is not specified. The `--must-change-password=false` argument must be given to not require a password change. This regression will be [fixed in 7.0.1](https://codeberg.org/forgejo/forgejo/issues/3399).
|
||||||
|
* **Breaking changes requiring manual intervention:**
|
||||||
|
* [MySQL 8.0 or PostgreSQL 12](https://codeberg.org/forgejo/forgejo/commit/e94f9fcafdcf284561e7fb33f60156a69c4ad6a5) are the minimum supported versions. The database must be migrated before upgrading. The requirements regarding SQLite did not change.
|
||||||
|
* The `per_page` parameter is [no longer a synonym for `limit`](https://codeberg.org/forgejo/forgejo/commit/0aab2d38a7d91bc8caff332e452364468ce52d9a) in the [/repos/{owner}/{repo}/releases](https://code.forgejo.org/api/swagger/#/repository/repoListReleases) API endpoint.
|
||||||
|
* The date format of the `created` and `last_update` fields of the [`/repos/{owner}/{repo}/push_mirrors`](https://code.forgejo.org/api/swagger/#/repository/repoListPushMirrors) and [/repos/{owner}/{repo}/push_mirrors](https://code.forgejo.org/api/swagger/#/repository/repoAddPushMirror) API endpoint changed [to be timestamps instead of numbers](https://codeberg.org/forgejo/forgejo/commit/0ee7cbf725f45650136be45f8e0f74d395f73b5c).
|
||||||
|
* Labels used [by pprof endpoint](https://forgejo.org/docs/v7.0/admin/config-cheat-sheet/#server-server) have been changed:
|
||||||
|
* `graceful-lifecycle` to `gracefulLifecycle`
|
||||||
|
* `process-type` to `processType`
|
||||||
|
* `process-description` to `processDescription`
|
||||||
|
This allows for those endpoints to be scraped by services requiring prometheus style labels such as [grafana-agent](https://grafana.com/docs/agent/latest/).
|
||||||
|
* The repository description [imposes additional restrictions on what it contains](https://codeberg.org/forgejo/forgejo/commit/1075ff74b5050f671c5f9824ae39390230b3c85d) to prevent abuse. You may use [the v7.0 test instance](https://v7.next.forgejo.org/) to check how it will be modified.
|
||||||
|
* The [Gitea themes were renamed](https://codeberg.org/forgejo/forgejo/commit/023e937141dd891bce3370c869d4db2c60f971ed) and the `[ui].THEMES` setting must be changed as follows:
|
||||||
|
* `gitea` is replaced by `gitea-light`
|
||||||
|
* `arc-green` is replaced by `gitea-dark`
|
||||||
|
* `auto` is replaced by `gitea-auto`
|
||||||
|
* **Breaking changes in the user interface:**
|
||||||
|
Note that the modifications related to CSS, templates or assets (images, fonts, etc.) are not documented here.
|
||||||
|
Although they can be extracted and modified, Forgejo does not provide any guarantee that such changes
|
||||||
|
will be portable from one version to another (even a patch version). See also
|
||||||
|
[the developer documentation about interface customization](https://forgejo.org/docs/v1.21/developer/customization/).
|
||||||
|
* [Update checker setting might change](https://codeberg.org/forgejo/forgejo/pulls/2925). The documentation was listing it as enabled by default, however, for a while it was disabled unless it was explicitly specified in the config or on the installation page. Instances migrated from Gitea also had it disabled due to different default value. Since then Forgejo got a privacy-friendly DNS-based update checking mechanism which is now being enabled by default unless explicitly specified [in the config](https://forgejo.org/docs/v7.0/admin/config-cheat-sheet/#cron---check-for-new-forgejo-versions-cronupdate_checker).
|
||||||
|
* Language statistics for repositories that use `linguist` attributes in `.gitattributes` *may* show different statistics than previously, because Forgejo recognizes more [linguist attributes](https://forgejo.org/docs/v7.0/user/language-detection/) now.
|
||||||
|
* It is [no longer possible to replace the default web editor](https://codeberg.org/forgejo/forgejo/pulls/2916) used to write comments or issues and pull requests with the EasyMDE editor. It is however still available as an alternative to edit releases and wiki pages.
|
||||||
|
* [The list of all repositories and the `New Issue` button are no longer available in the user dashboard](https://codeberg.org/forgejo/forgejo/commit/beb71f5ef6e8074dc744ac995c15f7b5947a3f2e) for issues and pull requests.
|
||||||
|
* **Migration warning**
|
||||||
|
* If the logs show a line like the following, [run doctor convert](https://forgejo.org/docs/v7.0/admin/command-line/#doctor-convert) to fix it.
|
||||||
|
```
|
||||||
|
[W] Current database is using a case-insensitive collation "utf8mb4_general_ci"
|
||||||
|
```
|
||||||
|
* Large instances may experience slow migrations when the database is upgraded to support SHA-256 git repositories. For instance, here are the logs from a test migration of the https://codeberg.org production database:
|
||||||
|
```
|
||||||
|
[I] Migration[286]: Add support for SHA256 git repositories
|
||||||
|
[W] [Slow SQL Query] ALTER TABLE `commit_status` MODIFY COLUMN `context_hash` VARCHAR(64) [] - 3m41.647738396s
|
||||||
|
[W] [Slow SQL Query] ALTER TABLE `comment` MODIFY COLUMN `commit_sha` VARCHAR(64) [] - 1m5.500234133s
|
||||||
|
[W] [Slow SQL Query] ALTER TABLE `release` MODIFY COLUMN `sha1` VARCHAR(64) [] - 22.06241145s
|
||||||
|
```
|
||||||
|
* **Features and enhancements**
|
||||||
|
* Repository settings have been refactored, lifting out the repository unit-related settings to their own page. ([#2221](https://codeberg.org/forgejo/forgejo/pulls/2221))
|
||||||
|
- When additional units can be enabled, an "Add more..." link will be displayed for repository admins. This can be turned off. ([#2533](https://codeberg.org/forgejo/forgejo/pulls/2533))
|
||||||
|
* Repository administrators can [allow anyone to edit the wiki](https://forgejo.org/docs/v7.0/user/wiki/#activation-and-permissions) in the repository Settings. ([#2001](https://codeberg.org/forgejo/forgejo/pulls/2001))
|
||||||
|
* Instance administrators can enable [repository badges](https://forgejo.org/docs/v7.0/user/readme-badges/) in the [configuration file](https://forgejo.org/docs/v7.0/admin/config-cheat-sheet/#badges-badges). This feature depends on a shield generator service such as shields.io, and is disabled by default. ([#2070](https://codeberg.org/forgejo/forgejo/pulls/2070))
|
||||||
|
* Instance administrators can configure the additional clone methods displayed on the repository home view. ([gitea#29320](https://github.com/go-gitea/gitea/pull/29320))
|
||||||
|
* Instance administrators can [assign custom flags to repositories](https://codeberg.org/forgejo/forgejo/pulls/2079). This is disabled by default, and currently requires custom templates to do anything useful with the flags. ([#2079](https://codeberg.org/forgejo/forgejo/pulls/2079) & [#2097](https://codeberg.org/forgejo/forgejo/pulls/2097))
|
||||||
|
* Fallback for [basic repo search using git-grep](https://forgejo.org/docs/v7.0/user/code-search/) when code indexer is disabled ([gitea#29998](https://github.com/go-gitea/gitea/pull/29998))
|
||||||
|
* Repository administrators can disable forking instance-wide by setting the new `[repository].DISABLE_FORKS` setting. ([#2445](https://codeberg.org/forgejo/forgejo/pulls/2445))
|
||||||
|
* Render permalinks to files with a line range by an inline preview in all places where markup is allowed ([#2669](https://codeberg.org/forgejo/forgejo/pulls/2669))
|
||||||
|
* A user can now optionally set their preferred pronouns ([#1518](https://codeberg.org/forgejo/forgejo/pulls/1518)).
|
||||||
|
* [Always enable caches](https://codeberg.org/forgejo/forgejo/commit/e7cb8da2a8310ac167b6f613b283caa3316a7154).
|
||||||
|
* Forgejo now recognizes more [linguist attributes](https://forgejo.org/docs/v7.0/user/language-detection/), making it possible to include documentation in the repository language statistics, for example. ([#2088](https://codeberg.org/forgejo/forgejo/pulls/2088))
|
||||||
|
* When displaying the message to open a pull request from a recently pushed branch, the recently pushed branch now links to the appropriate branch. ([#2141](https://codeberg.org/forgejo/forgejo/pulls/2141))
|
||||||
|
* Users who signed up, but have not activated their accounts yet, are now able to [change their email before activation](https://codeberg.org/forgejo/forgejo/pulls/1891). ([#1891](https://codeberg.org/forgejo/forgejo/pulls/1891))
|
||||||
|
* The "You pushed on branch ...." banner is now displayed for repositories you have a fork of with recently pushed branches too ([#2195](https://codeberg.org/forgejo/forgejo/pulls/2195)), and it will no longer consider branches that share no history with the default branch. ([#2196](https://codeberg.org/forgejo/forgejo/pulls/2196))
|
||||||
|
* Forgejo will now highlight signed tags in a similar way it highlights signed commits. ([#2534](https://codeberg.org/forgejo/forgejo/pulls/2534))
|
||||||
|
* Forgejo gained support for the more recent GitHub-style alert blocks. ([#2348](https://codeberg.org/forgejo/forgejo/pulls/2348))
|
||||||
|
- The older style remains supported too.
|
||||||
|
* [[ACTIONS] Add vars context to cron jobs](https://codeberg.org/forgejo/forgejo/pulls/3059)
|
||||||
|
* [[ACTIONS] Allow viewing the latest Action Run on the web](https://codeberg.org/forgejo/forgejo/pulls/1900)
|
||||||
|
* [[AGIT] Automatically fill in the description](https://codeberg.org/forgejo/forgejo/pulls/2344)
|
||||||
|
* [[API] Add API to get PR by base/head](https://codeberg.org/forgejo/forgejo/pulls/2481)
|
||||||
|
* [[API] commentAssignment() to verify the id belongs](https://codeberg.org/forgejo/forgejo/pulls/2126)
|
||||||
|
* [[API] DELETE /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments/{comment}](https://codeberg.org/forgejo/forgejo/pulls/2157)
|
||||||
|
* [[API] endpoint for adding comments to reviews](https://codeberg.org/forgejo/forgejo/pulls/2122)
|
||||||
|
* [[API] GET /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments/{comment}](https://codeberg.org/forgejo/forgejo/pulls/2127)
|
||||||
|
* [[API] support for repository flags](https://codeberg.org/forgejo/forgejo/pulls/2097)
|
||||||
|
* [[I18N] Clarify description in deletion modal](https://codeberg.org/forgejo/forgejo/pulls/2488)
|
||||||
|
* [[I18N] Clarify the description of SSH Keys](https://codeberg.org/forgejo/forgejo/pulls/2393)
|
||||||
|
* [[I18N] Data size unit localization](https://codeberg.org/forgejo/forgejo/pulls/2528)
|
||||||
|
* [[I18N] Improve branch select list ui in go templates (gitea#29729)](https://codeberg.org/forgejo/forgejo/pulls/2744)
|
||||||
|
* [[I18N] Improve localization of repo summary](https://codeberg.org/forgejo/forgejo/pulls/2756)
|
||||||
|
* [[I18N] Improve registration / password reset emails](https://codeberg.org/forgejo/forgejo/pulls/2529)
|
||||||
|
* [[I18N] Use correct translations for pull request](https://codeberg.org/forgejo/forgejo/pulls/2260)
|
||||||
|
* [[I18N] Improve translatability of activity heatmap](https://codeberg.org/forgejo/forgejo/pulls/2612)
|
||||||
|
* [[I18N] Improve English locale for admin settings](https://codeberg.org/forgejo/forgejo/pulls/2583)
|
||||||
|
* [I18N] Add plural support: [1](https://codeberg.org/forgejo/forgejo/pulls/2614), [2](https://codeberg.org/forgejo/forgejo/pulls/2695), [3](https://codeberg.org/forgejo/forgejo/pulls/2954), [4](https://codeberg.org/forgejo/forgejo/pulls/3031)
|
||||||
|
* [I18N] General improvements to English locale: [1](https://codeberg.org/forgejo/forgejo/pulls/2307), [2](https://codeberg.org/forgejo/forgejo/pulls/2437), [3](https://codeberg.org/forgejo/forgejo/pulls/2492), [4](https://codeberg.org/forgejo/forgejo/pulls/2610), [5](https://codeberg.org/forgejo/forgejo/pulls/2703), [6](https://codeberg.org/forgejo/forgejo/pulls/2941).
|
||||||
|
* [[I18N] Allow custom repo size format](https://codeberg.org/forgejo/forgejo/pulls/2974)
|
||||||
|
* [[PACKAGES] nuget basic manifest download](https://codeberg.org/forgejo/forgejo/pulls/2222)
|
||||||
|
* [[UI] Add label filters in organization issues dashboard](https://codeberg.org/forgejo/forgejo/pulls/2944)
|
||||||
|
* [[UI] Allow users to hide all "Add more units..." hints](https://codeberg.org/forgejo/forgejo/pulls/2533)
|
||||||
|
* [[UI] Display tag name as title for a tag with no release [gitea]](https://codeberg.org/forgejo/forgejo/pulls/2547)
|
||||||
|
* [[UI] Enable ambiguous character detection in configured contexts](https://codeberg.org/forgejo/forgejo/pulls/2427)
|
||||||
|
* [[UI] Improve display of 404/500 error pages](https://codeberg.org/forgejo/forgejo/pulls/2466)
|
||||||
|
* [[UI] Improve look of user profiles](https://codeberg.org/forgejo/forgejo/pulls/2875)
|
||||||
|
* [[UI] Include a branch link in the recently pushed banner](https://codeberg.org/forgejo/forgejo/pulls/2141)
|
||||||
|
* [[UI] Offer to remove WIP: prefix in sidebar](https://codeberg.org/forgejo/forgejo/pulls/2660)
|
||||||
|
* [[UI] Port console colors](https://codeberg.org/forgejo/forgejo/pulls/2419)
|
||||||
|
* [[UI] pulls "Edit File" button in "Files Changed" tab](https://codeberg.org/forgejo/forgejo/pulls/1992)
|
||||||
|
* [[UI] Remove add organization on dashboard switcher](https://codeberg.org/forgejo/forgejo/pulls/2895)
|
||||||
|
* [[UI] Restrict file size of blame operation](https://codeberg.org/forgejo/forgejo/pulls/2395)
|
||||||
|
* [[UI] Show follow symlink button](https://codeberg.org/forgejo/forgejo/pulls/2530)
|
||||||
|
* [[UI] split code conversations in diff tab](https://codeberg.org/forgejo/forgejo/pulls/2306)
|
||||||
|
* [[UI] Update look of repo/org tabs on homepage](https://codeberg.org/forgejo/forgejo/pulls/2593)
|
||||||
|
* [[UI] Visual separation between types of attachments](https://codeberg.org/forgejo/forgejo/pulls/2899)
|
||||||
|
* [[UI] [AGIT] Add AGit label to AGit-created PRs](https://codeberg.org/forgejo/forgejo/pulls/2444)
|
||||||
|
* [[UI] [AGIT] Add link to docs and tooltip to label](https://codeberg.org/forgejo/forgejo/pulls/2499)
|
||||||
|
* [Implement commit mail selection for other Git operations](https://codeberg.org/forgejo/forgejo/pulls/2383)
|
||||||
|
* [Improved Linguist compatibility](https://codeberg.org/forgejo/forgejo/pulls/2088)
|
||||||
|
* [improve nuget nuspec api](https://codeberg.org/forgejo/forgejo/pulls/2996)
|
||||||
|
* [Log SQL queries when the database return error](https://codeberg.org/forgejo/forgejo/pulls/2140)
|
||||||
|
* [New doctor check: fix-push-mirrors-without-git-remote](https://codeberg.org/forgejo/forgejo/pulls/1853)
|
||||||
|
* [New route to view latest run of specific workflows](https://codeberg.org/forgejo/forgejo/pulls/2304)
|
||||||
|
* [Check for Commit in opengraph](https://codeberg.org/forgejo/forgejo/pulls/2094)
|
||||||
|
* [Check if commit is already present in target branch](https://codeberg.org/forgejo/forgejo/pulls/2450)
|
||||||
|
* [Configure if protected branch rule should apply to admins](https://codeberg.org/forgejo/forgejo/pulls/2867)
|
||||||
|
* [Count downloads for tag archives](https://codeberg.org/forgejo/forgejo/pulls/2976)
|
||||||
|
* [depguard sha256-simd](https://codeberg.org/forgejo/forgejo/pulls/2234)
|
||||||
|
* [Don't consider orphan branches as recently pushed](https://codeberg.org/forgejo/forgejo/pulls/2196)
|
||||||
|
* [extend webfinger to respond to profile page URIs](https://codeberg.org/forgejo/forgejo/pulls/2883)
|
||||||
|
* [Highlight signed tags like signed commits](https://codeberg.org/forgejo/forgejo/pulls/2534)
|
||||||
|
* [Allow changing the email address before activation](https://codeberg.org/forgejo/forgejo/pulls/1891)
|
||||||
|
* [Allow changing the repo Wiki branch to main](https://codeberg.org/forgejo/forgejo/pulls/2264)
|
||||||
|
* [Allow forking without a repo ID](https://codeberg.org/forgejo/forgejo/pulls/2310)
|
||||||
|
* [Allow instance-wide disabling of forking](https://codeberg.org/forgejo/forgejo/pulls/2445)
|
||||||
|
* [Allow non-explicit push options](https://codeberg.org/forgejo/forgejo/pulls/2984)
|
||||||
|
* [Allow to exclude files in dump](https://codeberg.org/forgejo/forgejo/pulls/2876)
|
||||||
|
* [add bucket lookup type](https://codeberg.org/forgejo/forgejo/pulls/2482)
|
||||||
|
* [Add download URL for executable files](https://codeberg.org/forgejo/forgejo/pulls/1839)
|
||||||
|
* [Add gitignore template for Janet projects](https://codeberg.org/forgejo/forgejo/pulls/2557)
|
||||||
|
* [add optional storage init to doctor commands](https://codeberg.org/forgejo/forgejo/pulls/3034)
|
||||||
|
* [Add rel="nofollow" to issue filter links](https://codeberg.org/forgejo/forgejo/pulls/2367)
|
||||||
|
* [Add support for shields.io-based badges](https://codeberg.org/forgejo/forgejo/pulls/2070)
|
||||||
|
* [Add Zig gitignore](https://codeberg.org/forgejo/forgejo/pulls/2352)
|
||||||
|
* [Recognize SSH signed tags too](https://codeberg.org/forgejo/forgejo/pulls/2520)
|
||||||
|
* [Repository flags](https://codeberg.org/forgejo/forgejo/pulls/2079)
|
||||||
|
* [support `.forgejo` dir for issue and PR templates](https://codeberg.org/forgejo/forgejo/pulls/2290)
|
||||||
|
* [Support Include/Exclude Filters for Grep](https://codeberg.org/forgejo/forgejo/pulls/3058)
|
||||||
|
* [Use 'Text' instead of 'Plaintext'](https://codeberg.org/forgejo/forgejo/pulls/2833)
|
||||||
|
* [Render code tags in commit messages](https://codeberg.org/forgejo/forgejo/commit/3ccb0c2512cb551943945aaa3f2bd0b1e2abd3b8).
|
||||||
|
* [Refactor markdown attention render](https://codeberg.org/forgejo/forgejo/commit/ec2201a3da5f18e55bfc0a54114ac935804f4ef8).
|
||||||
|
* [Add default board to new projects, remove uncategorized pseudo-board](https://codeberg.org/forgejo/forgejo/commit/8ffb9c6fb1571a1221978440f108911057df25db).
|
||||||
|
* [Add more stats tables](https://codeberg.org/forgejo/forgejo/commit/926367fe1d778fe7c9f5bc6b8e8c514b619ef038).
|
||||||
|
* [Improve branch select list ui in go templates](https://codeberg.org/forgejo/forgejo/commit/729849a2fd026adbb91e3ff3259290f61bd919f0).
|
||||||
|
* [Update allowed attachment types](https://codeberg.org/forgejo/forgejo/commit/04b79bb48b490644c46e58da46af4b62a40e5e03).
|
||||||
|
* [Completely style the webkit autofill](https://codeberg.org/forgejo/forgejo/commit/9916f3ed64a715fb9a31a0fcad6452276e275615).
|
||||||
|
* [Set user's 24h preference from their current OS locale](https://codeberg.org/forgejo/forgejo/commit/427ab550a6a35e7369bc1b33a188bb3030c32ec0).
|
||||||
|
* [Make wiki default branch name changeable](https://codeberg.org/forgejo/forgejo/commit/7ea8993a0e342e7a30cb2da03216697b4819935a).
|
||||||
|
* [Make admin pages wider because of left sidebar added and some tables become too narrow](https://codeberg.org/forgejo/forgejo/commit/145bebc829c03cbb078e518d7364d27bcf60d96c).
|
||||||
|
* [Make PR form use toast to show error message](https://codeberg.org/forgejo/forgejo/commit/221a28436a080447f429fa2089d264e56f4980e2).
|
||||||
|
* [Rename Action.GetDisplayName to GetActDisplayName](https://codeberg.org/forgejo/forgejo/commit/be9189eddc84e942710b16b1c8c54c10aad01b63).
|
||||||
|
* [Unify search boxes](https://codeberg.org/forgejo/forgejo/commit/847f03b6a65ee251bf764f54f6114737346a43b6).
|
||||||
|
* [Detect broken git hooks](https://codeberg.org/forgejo/forgejo/commit/963df8290784d82385f7e8ad9f5c9abfd2fa2860).
|
||||||
|
* [Filter for default-branch selection](https://codeberg.org/forgejo/forgejo/commit/1090734255d70deb9886de2c1a8bb971096223ee).
|
||||||
|
* [Include resource state events in Gitlab downloads](https://codeberg.org/forgejo/forgejo/commit/bc7a247b9ea68643e3c59d4b4376dea097ffcc68).
|
||||||
|
* [Properly migrate target branch change GitLab comment](https://codeberg.org/forgejo/forgejo/commit/f0acc71ba13713f07602294b4a33028125343d47).
|
||||||
|
* [Recolor dark theme to blue shade](https://codeberg.org/forgejo/forgejo/commit/ff581d5a2415f7a3321fa6ba656ae0e972674d6c).
|
||||||
|
* [Unify organizations header](https://codeberg.org/forgejo/forgejo/commit/4b494d341f3142c066bc5b2b3cfd50f924d64fd3).
|
||||||
|
* [Auto-update the system status in admin dashboard](https://codeberg.org/forgejo/forgejo/commit/4f050f358a15dd51903e01b330a5419b2ac06693).
|
||||||
|
* [Show more settings for empty repositories](https://codeberg.org/forgejo/forgejo/commit/b03af9efb275f935bb265c7f031225caaafefaff).
|
||||||
|
* [Downscale pasted PNG images based on metadata](https://codeberg.org/forgejo/forgejo/commit/b3f2447bc4b6a7220da748cc6eb24bd5568bee7c).
|
||||||
|
* [Show `View at this point in history` for every commit](https://codeberg.org/forgejo/forgejo/commit/27bc2b9d9597de89d2c6b68581c6729bb16a4572).
|
||||||
|
* [Drop "@" from email sender to avoid spam filters](https://codeberg.org/forgejo/forgejo/commit/9a1d5c549cb6d32219647ea1a771b8a82d5ac89f).
|
||||||
|
* [Allow non-admin users to delete review requests](https://codeberg.org/forgejo/forgejo/commit/77c56e29ded5665bdc09d0a568159aa7127b44b1).
|
||||||
|
* [Some performance optimization on dashboard and issues page](https://codeberg.org/forgejo/forgejo/commit/d996c5d5179c99855e69156a034eca055e9329a4).
|
||||||
|
* [Improve user search display name](https://codeberg.org/forgejo/forgejo/commit/c3e462921ee31536e59b37e654ed20e92a37ffe6).
|
||||||
|
* [Fix UI Spacing Errors in mirror settings](https://codeberg.org/forgejo/forgejo/commit/64faecefe10613840709a68c1b8b708115d69d6e).
|
||||||
|
* [Include username in email headers](https://codeberg.org/forgejo/forgejo/commit/360b3fd17c3315ad9ad9c4e6ac02eda73f48d8ae).
|
||||||
|
* [Also match weakly validated ETags](https://codeberg.org/forgejo/forgejo/commit/28fe3db1fb0f89bcb55829ced33c1282f85f6e97).
|
||||||
|
* [Propagate install_if and provider_priority to APKINDEX](https://codeberg.org/forgejo/forgejo/commit/2da233ad8be107de29190720f1c30199410fe0cd).
|
||||||
|
* [Fix display latest sync time for pull mirrors on the repo page](https://codeberg.org/forgejo/forgejo/commit/4674aea25b54baf08594c54f061dee9e44190f02).
|
||||||
|
* [Remove trust model selection from repository creation on web page because it can be changed in settings later](https://codeberg.org/forgejo/forgejo/commit/c08d263a1900aa5ee92f56af8ad1c7a2697d02e1).
|
||||||
|
* [Add ability to see open and closed issues at the same time](https://codeberg.org/forgejo/forgejo/commit/2c3da59e275b69ebf984bb70954f42a7bcb0b49d).
|
||||||
|
* [Move sign in labels to be above inputs](https://codeberg.org/forgejo/forgejo/commit/4af0944b2604dd2b2e413864492135faea097298).
|
||||||
|
* [Move the captcha script loader to the template which really needs it](https://codeberg.org/forgejo/forgejo/commit/a04f8c0f81f55a8b927ce0fad8127db39396f892).
|
||||||
|
* [Display latest sync time for pull mirrors on the repo page](https://codeberg.org/forgejo/forgejo/commit/2d343f8987025015f5b61e328cc9e45082e6d3f2).
|
||||||
|
* [Show in Web UI if file is vendored and generated](https://codeberg.org/forgejo/forgejo/commit/7ed18566e10b298309dcc99d97447cb1932ae09a).
|
||||||
|
* [Add orphaned topic consistency check](https://codeberg.org/forgejo/forgejo/commit/e02095c5b6e04f70ae6562f5aee169f7ee93cf7a).
|
||||||
|
* [Convert to url auth to header auth in tests](https://codeberg.org/forgejo/forgejo/commit/838db2f8911690fa2115c6827ed73687db71bef1).
|
||||||
|
* [Add option to set language in admin user view](https://codeberg.org/forgejo/forgejo/commit/318634ef74dc0a9c285991692e72d3df90b8583c).
|
||||||
|
* [Fix incorrect run order of action jobs](https://codeberg.org/forgejo/forgejo/commit/f4561c44b1cad700bf41537eb4db487fff34f6c9).
|
||||||
|
* [Add missing exclusive in advanced label options](https://codeberg.org/forgejo/forgejo/commit/77506c6f6cbfa5c15d8373743415f47b2adb404d).
|
||||||
|
* [Add combined index for issue_user.uid and issue_id](https://codeberg.org/forgejo/forgejo/commit/e08f1a9cbd582c73918e401eeba36261627f44a7).
|
||||||
|
* [Add edit option for README.md](https://codeberg.org/forgejo/forgejo/commit/08552f0076204b99258f9135c77a962c302521dc).
|
||||||
|
* [Fix link to `Code` tab on wiki commits](https://codeberg.org/forgejo/forgejo/commit/709a376c518d0cfde10bb911b32fd0ea82c67b52).
|
||||||
|
* [Remove autofocus in search box](https://codeberg.org/forgejo/forgejo/commit/eae555ff2395cc1ad178f3a977d83742ae73e1d9).
|
||||||
|
* [Allow to set explore page default sort](https://codeberg.org/forgejo/forgejo/commit/16ba16dbe951763cfc026b7351e26009d1a25fdc).
|
||||||
|
* [Improve PR diff view on mobile](https://codeberg.org/forgejo/forgejo/commit/49dddd87b19aebe83e1c54a455e62529a19f61b4).
|
||||||
|
* [Properly migrate automatic merge GitLab comments](https://codeberg.org/forgejo/forgejo/commit/542badbb76408c17ce6692e99fff680bee69face).
|
||||||
|
* [Display issue task list on project cards](https://codeberg.org/forgejo/forgejo/commit/4776fde9e1caa7cee5671715144a668e19a0323c).
|
||||||
|
* [Add Index to pull_auto_merge.doer_id](https://codeberg.org/forgejo/forgejo/commit/c8602a8dfa05f653e7de8ed2e677c8967b8688f5).
|
||||||
|
* [Fix display member unit in the menu bar if there are no hidden members in public org](https://codeberg.org/forgejo/forgejo/commit/0e021cd33ee3eb3d8f204bd075e2597b7ec8b391).
|
||||||
|
* [List all Debian package versions in `Packages`](https://codeberg.org/forgejo/forgejo/commit/b36e2ca4195298d2e4516e3022b953543f62f470).
|
||||||
|
* [Allow pull requests Manually Merged option to be used by non-admins](https://codeberg.org/forgejo/forgejo/commit/1756e30e102d079f8425aa2061ef80fd36c2e57d).
|
||||||
|
* [Only show diff file tree when more than one file changed](https://codeberg.org/forgejo/forgejo/commit/572f0963edc71239634ee782a3c69213479f34ba).
|
||||||
|
* [Show placeholder email in privacy popup](https://codeberg.org/forgejo/forgejo/commit/31f8880bc252a25075f8752e2722b316c6e46ec7).
|
||||||
|
* [Revamp repo header](https://codeberg.org/forgejo/forgejo/commit/7d62615513b8985360de497e9a051b51ca0faaf2).
|
||||||
|
* [Add link to members and repositories at teams page](https://codeberg.org/forgejo/forgejo/commit/4f4ddcf3c593b474846d40e47b4351d3deb39202).
|
||||||
|
* [Add link for repositories README file](https://codeberg.org/forgejo/forgejo/commit/7210f23fa0f11da093b307029d7ab91ed40807fb).
|
||||||
|
* [Add `must-change-password` cli parameter](https://codeberg.org/forgejo/forgejo/commit/9bea276055edc9527e3d6d66df3bbf0d20326f8b).
|
||||||
|
* [Unify password changing and invalidate auth tokens](https://codeberg.org/forgejo/forgejo/commit/688d4a1f719d2df4d2626453f4bc042c1874a375).
|
||||||
|
* [Add slow SQL query warning](https://codeberg.org/forgejo/forgejo/commit/664192767c41b9d0759bcc3915c7bd6ccecc52ae).
|
||||||
|
* [Pre-register OAuth application for tea](https://codeberg.org/forgejo/forgejo/commit/a825cc0f3423f0a5c8157c436a0c7b489ef536c1).
|
||||||
|
* [Differentiate between `push` and `pull` `mirror sync in progress`](https://codeberg.org/forgejo/forgejo/commit/e709bc199fe33456c4ecd1cd28029bd31b529832).
|
||||||
|
* [Cargo package - Fix missing domain in cargo sparse url](https://codeberg.org/forgejo/forgejo/commit/a112cf34d391cc04770021f9ffaa29e383cb9d51).
|
||||||
|
* [Link to file from its history](https://codeberg.org/forgejo/forgejo/commit/33de64cb21505259338e393ef0d15ccb0f757475).
|
||||||
|
* [Add a shortcut to user's profile page to admin user details](https://codeberg.org/forgejo/forgejo/commit/e96e440b8bde5516ffc7bba42691e26084a96588).
|
||||||
|
* [Doctor: delete action entries without existing user](https://codeberg.org/forgejo/forgejo/commit/15fa0383fb5dd9ad1702dbc34ba7100c0cdbcc8c).
|
||||||
|
* [Add anchor to review types](https://codeberg.org/forgejo/forgejo/commit/89c9a498fdd6184df8afda8b5b488462e65b9e71).
|
||||||
|
* [Show total TrackedTime on issue/pull/milestone lists](https://codeberg.org/forgejo/forgejo/commit/adbc995c347e158a56264f2488997d7d59a4dd8b).
|
||||||
|
* [Improve commit record's ui in comment list](https://codeberg.org/forgejo/forgejo/commit/ed1798f66d30e3755f01e24f8cb4aa5e8b6628a0).
|
||||||
|
* [Don't show new pr button when page is not compare pull](https://codeberg.org/forgejo/forgejo/commit/b693611b35c5ae17cfc820bc3e731608a5251464).
|
||||||
|
* [Add `Hide/Show all checks` button to commit status check](https://codeberg.org/forgejo/forgejo/commit/dcb648ee71853073d54e8a6e107b764212ede58e).
|
||||||
|
* [Improvements of releases list and tags list](https://codeberg.org/forgejo/forgejo/commit/3fcad582c9b9bfe66f4a346652f82b1aaf18430d).
|
||||||
|
* [Support pasting URLs over markdown text](https://codeberg.org/forgejo/forgejo/commit/45112876766cb81ed7edd2b72a3ab93e6deab8bb).
|
||||||
|
* [Customizable "Open with" applications for repository clone](https://codeberg.org/forgejo/forgejo/commit/44221a3cd747a01d55093b15a12bf053b534da35).
|
||||||
|
* [Allow options to disable user deletion from the interface on app.ini](https://codeberg.org/forgejo/forgejo/commit/767e9634d3d02acab27f05e1783391c9c7f6292e).
|
||||||
|
* [Extend issue template yaml engine](https://codeberg.org/forgejo/forgejo/commit/ff8f7a7a0d1d0f57113a6ad8b499f7c1094288f5).
|
||||||
|
* [Filter Repositories by type](https://codeberg.org/forgejo/forgejo/commit/83e04328dfff3b09e5d28dd972ebee0865f96b0e).
|
||||||
|
* [Implement code frequency graph](https://codeberg.org/forgejo/forgejo/commit/f097799953c5f510b7e3314f1e3e115761f207d0).
|
||||||
|
* [Implement recent commits graph](https://codeberg.org/forgejo/forgejo/commit/428008ac19185125b7cb1e3d379254d7b1932529).
|
||||||
|
* [Show commit status for releases](https://codeberg.org/forgejo/forgejo/commit/369fe5696697cef33a188d9b985ac4b9824a4bdf).
|
||||||
|
* [Actions Artifacts v4 backend](https://codeberg.org/forgejo/forgejo/commit/66632c4958041abdffe6adafc278d34ef515c44f).
|
||||||
|
* [Add merge style `fast-forward-only`](https://codeberg.org/forgejo/forgejo/commit/83123b493f3ae25d07d81c86b1a78afe1c17db53).
|
||||||
|
* [Retarget depending pulls when the parent branch is deleted](https://codeberg.org/forgejo/forgejo/commit/49eb16867728913d1eb2ced96e0b0b0a358f6ebe).
|
||||||
|
* [Add global setting how timestamps should be rendered](https://codeberg.org/forgejo/forgejo/commit/cdc33b29a012e61b42f192d79f9486fa8e21b2ed).
|
||||||
|
* [Add skip ci functionality](https://codeberg.org/forgejo/forgejo/commit/816e46ee7ce4b2649479554a940ecbe1cc505a3d)
|
||||||
|
* [Show latest commit for file](https://codeberg.org/forgejo/forgejo/commit/885cc32b14584ee2d01009768895b7a776441504).
|
||||||
|
* [Allow to sync tags from admin dashboard](https://codeberg.org/forgejo/forgejo/commit/4567a3a1ad0490d9077102e0e7b5de35790e5803).
|
||||||
|
* [Add Profile Readme for Organisations](https://codeberg.org/forgejo/forgejo/commit/603573366a203efae06f818a0b220be964cdac21).
|
||||||
|
* [Implement contributors graph](https://codeberg.org/forgejo/forgejo/commit/e9be8b25ae57189c4b29eaa393a397cf634d21d7).
|
||||||
|
* [Artifact deletion in actions ui](https://codeberg.org/forgejo/forgejo/commit/c551d3f3ab13379b0740fc45bc4dfc8f2fb84e16).
|
||||||
|
* [Add API routes to get runner registration token](https://codeberg.org/forgejo/forgejo/commit/baf0d402d9cb47849394202fcfc7c2e23b0faac3).
|
||||||
|
* [Add support for forking single branch](https://codeberg.org/forgejo/forgejo/commit/5e02e3b7ee8294e2ec94968ece9af56bf1aa1534).
|
||||||
|
* [Add support for sha256 repositories](https://codeberg.org/forgejo/forgejo/commit/d68a613ba8fd860863a3465b5b5945b191b87b25).
|
||||||
|
* [Add admin API route for managing user's badges](https://codeberg.org/forgejo/forgejo/commit/82b7de1360870db7a8b368a3f80ede887e32e128).
|
||||||
|
* **Bug fixes:**
|
||||||
|
* The repository home view will no longer redirect to external units. ([#2064](https://codeberg.org/forgejo/forgejo/pulls/2064))
|
||||||
|
* User and Organization `.profile` repositories now search for a `README.md` file case insensitively. ([#2090](https://codeberg.org/forgejo/forgejo/pulls/2090))
|
||||||
|
* When viewing a file, the RSS feed link is only displayed when there is an RSS feed provided for the context: when viewing a file on a branch. ([#2103](https://codeberg.org/forgejo/forgejo/pulls/2103))
|
||||||
|
* Repository topic searches are now correctly paged, which should make topic management on larger instances orders of magnitudes faster. ([#2060](https://codeberg.org/forgejo/forgejo/pulls/2060))
|
||||||
|
* Mentioning a user in a comment or similar place ignores apostrophes now. ([#2485](https://codeberg.org/forgejo/forgejo/pulls/2485))
|
||||||
|
* Setting the `[repository].DISABLE_STARS` setting to `true` disables the functionality completely, rather than just hiding it from the user interface.
|
||||||
|
* Forking a repository is now available at a predictable URL, and does not require knowing the repository id. ([#2310](https://codeberg.org/forgejo/forgejo/pulls/2310))
|
||||||
|
* Issue and pull request templates can now be placed in a `.forgejo` directory, like workflows. ([#2290](https://codeberg.org/forgejo/forgejo/pulls/2290))
|
||||||
|
* [[A11Y] Fix accessibility and translatability of repo explore counters](https://codeberg.org/forgejo/forgejo/pulls/2862)
|
||||||
|
* [[A11Y] Focus styling and fix Watch/Unwatch buttons](https://codeberg.org/forgejo/forgejo/pulls/2379)
|
||||||
|
* [[A11Y] Label Stars/Forks links in repo explore](https://codeberg.org/forgejo/forgejo/pulls/2634)
|
||||||
|
* [[A11Y] Taborder in repo explore](https://codeberg.org/forgejo/forgejo/pulls/2636)
|
||||||
|
* [[ACTIONS] add proper payload to scheduled events](https://codeberg.org/forgejo/forgejo/pulls/2015)
|
||||||
|
* [[ACTIONS] Do not update PRs based on events that happened before they existed](https://codeberg.org/forgejo/forgejo/pulls/2932)
|
||||||
|
* [[ACTIONS] GetScheduledMergeByPullID may involve a system user](https://codeberg.org/forgejo/forgejo/pulls/1908)
|
||||||
|
* [[ACTIONS] Link to Workflow in View](https://codeberg.org/forgejo/forgejo/pulls/1866)
|
||||||
|
* [[ACTIONS] the ref of a scheduled action is always the default branch](https://codeberg.org/forgejo/forgejo/pulls/1941)
|
||||||
|
* [[API] Adjust name of operation](https://codeberg.org/forgejo/forgejo/pulls/2189)
|
||||||
|
* [[API] `/api/v1/{owner}/{repo}/issue_templates`](https://codeberg.org/forgejo/forgejo/pulls/2292)
|
||||||
|
* [[API] Document correct status code for creating a tag](https://codeberg.org/forgejo/forgejo/pulls/2201)
|
||||||
|
* [[API] /api/forgejo/v1/version auth check](https://codeberg.org/forgejo/forgejo/pulls/2582)
|
||||||
|
* [[API] inconsistencies](https://codeberg.org/forgejo/forgejo/pulls/2182)
|
||||||
|
* [[API] /issues/search endpoint](https://codeberg.org/forgejo/forgejo/pulls/2020)
|
||||||
|
* [[API] Make HTTPS schema default for Swagger](https://codeberg.org/forgejo/forgejo/pulls/1896)
|
||||||
|
* [[I18N] Add missing translation for more_items](https://codeberg.org/forgejo/forgejo/pulls/2828)
|
||||||
|
* [[I18N] Eliminate wrapping quotes in English locale](https://codeberg.org/forgejo/forgejo/pulls/2467)
|
||||||
|
* [[I18N] English fixes and improvements](https://codeberg.org/forgejo/forgejo/pulls/2631)
|
||||||
|
* [[I18N] Fix milestone sorting translation keys](https://codeberg.org/forgejo/forgejo/pulls/2644)
|
||||||
|
* [[I18N] Use correct translation on closed milestones](https://codeberg.org/forgejo/forgejo/pulls/2957)
|
||||||
|
* [[I18N] Use new translation key](https://codeberg.org/forgejo/forgejo/pulls/2760)
|
||||||
|
* [[PACKAGES] Delete redundant snap packaging recipe](https://codeberg.org/forgejo/forgejo/pulls/2693)
|
||||||
|
* [[PACKAGES] Fix Alpine Registry packages with noarch not being found](https://codeberg.org/forgejo/forgejo/pulls/2285)
|
||||||
|
* [[PACKAGES] Generate install if condition for Alpine](https://codeberg.org/forgejo/forgejo/pulls/2176)
|
||||||
|
* [[PACKAGES] Packagist webhook: support all events](https://codeberg.org/forgejo/forgejo/pulls/2646)
|
||||||
|
* [[PACKAGES] Fix for PyPi Registry PEP 503 Compliance](https://codeberg.org/forgejo/forgejo/pulls/3197)
|
||||||
|
* [[UI] Adjust the signed tag verification line](https://codeberg.org/forgejo/forgejo/pulls/2966)
|
||||||
|
* [[UI] Better color for labels/counters](https://codeberg.org/forgejo/forgejo/pulls/2935)
|
||||||
|
* [[UI] Better number for UserCards pagination](https://codeberg.org/forgejo/forgejo/pulls/2584)
|
||||||
|
* [[UI] Center icon and callout text](https://codeberg.org/forgejo/forgejo/pulls/3010)
|
||||||
|
* [[UI] Consistent styling for Sort filter](https://codeberg.org/forgejo/forgejo/pulls/2920)
|
||||||
|
* [[UI] Disable the RSS feed in file view for non-branches](https://codeberg.org/forgejo/forgejo/pulls/2103)
|
||||||
|
* [[UI] Disable 'View at this point in history' for wikis](https://codeberg.org/forgejo/forgejo/pulls/2999)
|
||||||
|
* [[UI] Display error message if doer is unable to fork](https://codeberg.org/forgejo/forgejo/pulls/2649)
|
||||||
|
* [[UI] Don't use `<br />` in alert block](https://codeberg.org/forgejo/forgejo/pulls/2741)
|
||||||
|
* [[UI] Fix admin layout](https://codeberg.org/forgejo/forgejo/pulls/3087)
|
||||||
|
* [[UI] Fix crash in issue forms](https://codeberg.org/forgejo/forgejo/pulls/3012)
|
||||||
|
* [[UI] Fix Ctrl+Enter on submitting review comment](https://codeberg.org/forgejo/forgejo/pulls/2370)
|
||||||
|
* [[UI] Fix diff patch operation in web UI](https://codeberg.org/forgejo/forgejo/pulls/2449)
|
||||||
|
* [[UI] Fixes for project selector in sidebar](https://codeberg.org/forgejo/forgejo/pulls/2608)
|
||||||
|
* [[UI] Fix must-change-password help dialog](https://codeberg.org/forgejo/forgejo/pulls/2676)
|
||||||
|
* [[UI] Fix relative links on orgmode](https://codeberg.org/forgejo/forgejo/pulls/2385)
|
||||||
|
* [[UI] Fix selector inner radius](https://codeberg.org/forgejo/forgejo/pulls/2860)
|
||||||
|
* [[UI] Fix tone of callout boxes for Forgejo dark](https://codeberg.org/forgejo/forgejo/pulls/3085)
|
||||||
|
* [[UI] Fix tooltip for 1000+ stars/forks](https://codeberg.org/forgejo/forgejo/pulls/3147)
|
||||||
|
* [[UI] include hostname in admin panel URL in new user emails](https://codeberg.org/forgejo/forgejo/pulls/1940)
|
||||||
|
* [[UI] Increase contrast of code block](https://codeberg.org/forgejo/forgejo/pulls/2874)
|
||||||
|
* [[UI] Limit amount of javascript errors being shown](https://codeberg.org/forgejo/forgejo/pulls/2175)
|
||||||
|
* [[UI] Make settings tab not active when on repository "Add units" tab](https://codeberg.org/forgejo/forgejo/pulls/2524)
|
||||||
|
* [[UI] Make write and preview tabs interactive](https://codeberg.org/forgejo/forgejo/pulls/2681)
|
||||||
|
* [[UI] New issue button position consistency](https://codeberg.org/forgejo/forgejo/pulls/2845)
|
||||||
|
* [[UI] Fix orgmode link resolver for text descriptions](https://codeberg.org/forgejo/forgejo/pulls/2276)
|
||||||
|
* [[UI] Preview: set font-size on preview content](https://codeberg.org/forgejo/forgejo/pulls/2349)
|
||||||
|
* [[UI] Fix primary button background inconsistency](https://codeberg.org/forgejo/forgejo/pulls/3002)
|
||||||
|
* [[UI] Fix regression of issue edit not working](https://codeberg.org/forgejo/forgejo/pulls/3043)
|
||||||
|
* [[UI] Fix relative links rendering](https://codeberg.org/forgejo/forgejo/pulls/2166)
|
||||||
|
* [[UI] Remember topic only in repo search](https://codeberg.org/forgejo/forgejo/pulls/2575)
|
||||||
|
* [[UI] Remove min-height from wiki elements](https://codeberg.org/forgejo/forgejo/pulls/2080)
|
||||||
|
* [[UI] Render emojis in labels in issue info popup](https://codeberg.org/forgejo/forgejo/pulls/2888)
|
||||||
|
* [[UI] Render correct label link](https://codeberg.org/forgejo/forgejo/pulls/3187)
|
||||||
|
* [[UI] Render inline file permalinks](https://codeberg.org/forgejo/forgejo/pulls/2669)
|
||||||
|
* [[UI] Fix repo badges when the label or text contains dashes](https://codeberg.org/forgejo/forgejo/pulls/2711)
|
||||||
|
* [[UI] Fix repo unarchivation button](https://codeberg.org/forgejo/forgejo/pulls/2550)
|
||||||
|
* [[UI] Restrict when to make link absolute in markdown](https://codeberg.org/forgejo/forgejo/pulls/2403)
|
||||||
|
* [[UI] Revert darker tone on labels](https://codeberg.org/forgejo/forgejo/pulls/2881)
|
||||||
|
* [[UI] Simplify converting struct to map in admin stats](https://codeberg.org/forgejo/forgejo/pulls/2442)
|
||||||
|
* [[UI] Fix the Fork button in repo headers](https://codeberg.org/forgejo/forgejo/pulls/2495)
|
||||||
|
* [[UI] Use correct logout URL](https://codeberg.org/forgejo/forgejo/pulls/2475)
|
||||||
|
* [[UI] Use separate keys for tabs on login screen](https://codeberg.org/forgejo/forgejo/pulls/2630)
|
||||||
|
* [[UI] "view file" button in diff compare view](https://codeberg.org/forgejo/forgejo/pulls/3046)
|
||||||
|
* [add Cache-Control header for health-check](https://codeberg.org/forgejo/forgejo/pulls/3060)
|
||||||
|
* [add max idle time setting for db connections](https://codeberg.org/forgejo/forgejo/pulls/2418)
|
||||||
|
* [Allow `'s` in mentions](https://codeberg.org/forgejo/forgejo/pulls/2485)
|
||||||
|
* [Avoid `WHERE IN` for comment migration query](https://codeberg.org/forgejo/forgejo/pulls/1961)
|
||||||
|
* [Cleanup characters forbidden on Windows from test fixture filenames](https://codeberg.org/forgejo/forgejo/pulls/2178)
|
||||||
|
* [Correct changed files for CODEOWNERS](https://codeberg.org/forgejo/forgejo/pulls/2507)
|
||||||
|
* [Correct default licenses to work as desired](https://codeberg.org/forgejo/forgejo/pulls/1888)
|
||||||
|
* [Detect protected branch on branch rename](https://codeberg.org/forgejo/forgejo/pulls/2811)
|
||||||
|
* [Disabling Stars should disable the routes too](https://codeberg.org/forgejo/forgejo/pulls/2471)
|
||||||
|
* [doctor: Don't say All done when no checks were run](https://codeberg.org/forgejo/forgejo/pulls/1907)
|
||||||
|
* [Do not allow deletion of internal references](https://codeberg.org/forgejo/forgejo/pulls/2834)
|
||||||
|
* [Don't color dot literal color names](https://codeberg.org/forgejo/forgejo/pulls/2905)
|
||||||
|
* [Don't delete inactive emails explicitly](https://codeberg.org/forgejo/forgejo/pulls/2880)
|
||||||
|
* [Don't overwrite protected branch accidentally](https://codeberg.org/forgejo/forgejo/pulls/2473)
|
||||||
|
* [Don't redirect the repo to external units](https://codeberg.org/forgejo/forgejo/pulls/2064)
|
||||||
|
* [Don't remove builtin OAuth2 applications](https://codeberg.org/forgejo/forgejo/pulls/3067)
|
||||||
|
* [Ensure `HasIssueContentHistory` takes into account `comment_id`](https://codeberg.org/forgejo/forgejo/pulls/2518)
|
||||||
|
* [Find README.md for user profiles case insensitively](https://codeberg.org/forgejo/forgejo/pulls/2090)
|
||||||
|
* [Fix header name in swagger response](https://codeberg.org/forgejo/forgejo/pulls/2526)
|
||||||
|
* [Fix pull request reopen conditions](https://codeberg.org/forgejo/forgejo/pulls/2373)
|
||||||
|
* [Fix unblock action](https://codeberg.org/forgejo/forgejo/pulls/3086)
|
||||||
|
* [Fix VSCode settings](https://codeberg.org/forgejo/forgejo/pulls/1881)
|
||||||
|
* [Gracefully handle missing branches on a repos branches page](https://codeberg.org/forgejo/forgejo/pulls/2139)
|
||||||
|
* [Initialize Git for hook regeneration](https://codeberg.org/forgejo/forgejo/pulls/2416)
|
||||||
|
* [Internal Server Error when resolving comments](https://codeberg.org/forgejo/forgejo/pulls/2282)
|
||||||
|
* [Load `AllUnitsEnabled` when necessary](https://codeberg.org/forgejo/forgejo/pulls/2420)
|
||||||
|
* [Makefile: check git diff exitCode](https://codeberg.org/forgejo/forgejo/pulls/2651)
|
||||||
|
* [Make pprof labels conformant with prometheus spec](https://codeberg.org/forgejo/forgejo/pulls/2933)
|
||||||
|
* [Make reference URL absolute](https://codeberg.org/forgejo/forgejo/pulls/2100)
|
||||||
|
* [misleading comparisons when comparing branches](https://codeberg.org/forgejo/forgejo/pulls/2194)
|
||||||
|
* [Block issue creation when blocked by repo owner](https://codeberg.org/forgejo/forgejo/pulls/2052)
|
||||||
|
* [NPE in `ToPullReviewList`](https://codeberg.org/forgejo/forgejo/pulls/2057)
|
||||||
|
* [NPE in `UsernameSubRoute`](https://codeberg.org/forgejo/forgejo/pulls/1981)
|
||||||
|
* [Only pass selected repository IDs to pagination](https://codeberg.org/forgejo/forgejo/pulls/1848)
|
||||||
|
* [panic in `canSoftDeleteContentHistory`](https://codeberg.org/forgejo/forgejo/pulls/2134)
|
||||||
|
* [prevent removing session cookie when redirect_uri query contains ://](https://codeberg.org/forgejo/forgejo/pulls/2590)
|
||||||
|
* [pull_request_template branch link](https://codeberg.org/forgejo/forgejo/pulls/2232)
|
||||||
|
* [Rate limit pre-activation email change separately](https://codeberg.org/forgejo/forgejo/pulls/2043)
|
||||||
|
* [Refactor LFS GC functions](https://codeberg.org/forgejo/forgejo/pulls/3056)
|
||||||
|
* [Reflect Cargo index state in settings](https://codeberg.org/forgejo/forgejo/pulls/2698)
|
||||||
|
* [Remember topic only in repo search](https://codeberg.org/forgejo/forgejo/pulls/2489)
|
||||||
|
* [Require Latex code to have a end sequence](https://codeberg.org/forgejo/forgejo/pulls/1822)
|
||||||
|
* [Respond with JSON Resource Descriptor Content-Type per RFC7033](https://codeberg.org/forgejo/forgejo/pulls/2882)
|
||||||
|
* [Fix session generation for database](https://codeberg.org/forgejo/forgejo/pulls/2045)
|
||||||
|
* [Sort file list case insensitively](https://codeberg.org/forgejo/forgejo/pulls/2522)
|
||||||
|
* [Fix the topic search paging](https://codeberg.org/forgejo/forgejo/pulls/2060)
|
||||||
|
* [Typo fix & clarify RegistrationToken](https://codeberg.org/forgejo/forgejo/pulls/2191)
|
||||||
|
* [Update checker setting updates](https://codeberg.org/forgejo/forgejo/pulls/2925)
|
||||||
|
* [Use correct format for attr-check error log](https://codeberg.org/forgejo/forgejo/pulls/2866)
|
||||||
|
* [Use correct head commit for CODEOWNER](https://codeberg.org/forgejo/forgejo/pulls/2658)
|
||||||
|
* [Use correct template for commitmail error](https://codeberg.org/forgejo/forgejo/pulls/2973)
|
||||||
|
* [Workaround borked Git version](https://codeberg.org/forgejo/forgejo/pulls/2335)
|
||||||
|
* [Remove scheduled action tasks if the repo is archived](https://codeberg.org/forgejo/forgejo/commit/87870ade49eb76ff57a8593ba35df10e0d617aa5).
|
||||||
|
* [Relax generic package filename restrictions](https://codeberg.org/forgejo/forgejo/commit/ea4755be6dfc8fc1f3c794eeaa2e2322b97d192e).
|
||||||
|
* [Prevent re-review and dismiss review actions on closed and merged PRs](https://codeberg.org/forgejo/forgejo/commit/23676bfea7ccbbe166a554115ea1f5f02800e379).
|
||||||
|
* [Add a warning for disallowed email domains](https://codeberg.org/forgejo/forgejo/commit/2559c80bec27a41967b355d214253a83b9ee5dad).
|
||||||
|
* [Skip email domain check when admins edit user emails](https://codeberg.org/forgejo/forgejo/commit/e7afba21ce2b02eb4230ba03752bd8b937f3e6ef).
|
||||||
|
* [Skip email domain check when admin users adds user manually](https://codeberg.org/forgejo/forgejo/commit/b6057a34db38e563473db00543a1e39fd743ca34).
|
||||||
|
* [Add support for API blob upload of release attachments](https://codeberg.org/forgejo/forgejo/commit/47a913d40d3417858f2ee51a7dbed64ca84eff60).
|
||||||
|
* [Allow options to disable user gpg keys configuration from the interface on app.ini](https://codeberg.org/forgejo/forgejo/commit/ee6ff937c0782b9cdc7ae1bc62b7eda83982d40f).
|
||||||
|
* [Allow options to disable user ssh keys configuration from the interface on app.ini](https://codeberg.org/forgejo/forgejo/commit/bb09ad2b63570c80418b4b9a10f7dbbb349448ab).
|
||||||
|
* [Fix content size does not match error when uploading lfs file](https://codeberg.org/forgejo/forgejo/commit/fb137d1e49c0436f1db093e2dc0a2350d63e1e29).
|
||||||
|
* [Add API to get merged PR of a commit](https://codeberg.org/forgejo/forgejo/commit/1608ef0ce9ce2ea1c87aef715d111cf441637d01).
|
||||||
|
* [Add API to get PR by base/head](https://codeberg.org/forgejo/forgejo/commit/feb189554e758ed27d1e309e5ec309d663e8f338).
|
||||||
|
* [Add attachment support for code review comments](https://codeberg.org/forgejo/forgejo/commit/f95fb8cc44d790e0ae71d3f879124a6ee9b07f66).
|
||||||
|
* [Add support for action artifact serve direct](https://codeberg.org/forgejo/forgejo/commit/1f8ad34e4391673a2eda434ea5e48ea084cdc814).
|
||||||
|
* [Show whether a PR is WIP inside popups](https://codeberg.org/forgejo/forgejo/commit/50f55f11c4f785b72a39e59b0fc12ae70ab8d8b5).
|
||||||
|
* [Add artifacts v4 jwt to job message and accept it](https://codeberg.org/forgejo/forgejo/commit/a9bc590d5d10b97bd8aa050ffb720e141a600064).
|
||||||
|
* [Fix some RPM registry flaws](https://codeberg.org/forgejo/forgejo/commit/461d8b53c2e51a8a6a1715ba40ac61d7e9f93971).
|
||||||
|
* [Add branch protection setting for ignoring stale approvals](https://codeberg.org/forgejo/forgejo/commit/5d3fdd121279c758f247a76e020799aa5e548feb).
|
||||||
|
* [Added instance-level variables](https://codeberg.org/forgejo/forgejo/commit/d0f24ff4cad05c1145afeca791e7d02fe146d46a).
|
||||||
|
* [Fix the wrong HTTP response status code for duplicate packages](https://codeberg.org/forgejo/forgejo/commit/5b6258a0b94737ec3db1ce418d0c933512a71f78).
|
||||||
|
* [Don't run push mirrors for archived repos](https://codeberg.org/forgejo/forgejo/commit/f3ba3e922dde7d12999a90d6cee15805a56cc7ff).
|
||||||
|
* [Support for grouping RPMs using paths](https://codeberg.org/forgejo/forgejo/commit/ba4d0b8ffbd78473273800f586ae8bde55cda6c5).
|
||||||
|
* [Fixes #27605: inline math blocks can't be preceded/followed by alphanumerical characters](https://codeberg.org/forgejo/forgejo/commit/2adc3a45fbd60126c0eab66b9cdd177a63bd4704).
|
||||||
|
* [Fix GPG subkey verify](https://codeberg.org/forgejo/forgejo/commit/5a674dd02ed3ea2853afa02dc15dcdadba069a6e).
|
||||||
|
* [Include encoding in signature payload](https://codeberg.org/forgejo/forgejo/commit/6925c0eee43980133896f9e4ee7e48e5751e9417).
|
||||||
|
* [Fix milestoneID filter bug in issue list](https://codeberg.org/forgejo/forgejo/commit/0da787f23737d252e6c80aa1a1f665e09dba0ea9).
|
||||||
|
* [Fix Citation modal responsiveness and clipboard copy](https://codeberg.org/forgejo/forgejo/commit/ca39d743636c9732f4422e130bac974555fb43c2).
|
||||||
|
* [Fix incorrect locale Tr for gpg command](https://codeberg.org/forgejo/forgejo/commit/071d871dcf8dd8097dc0af6d4baf304a2fbbe4e2).
|
||||||
|
* [Improve a11y document and dropdown item](https://codeberg.org/forgejo/forgejo/commit/1d4bf7e211db0866774fa3f6f563e15ffadac1f6).
|
||||||
|
* [Determine fuzziness of bleve indexer by keyword length](https://codeberg.org/forgejo/forgejo/commit/ab5f0b7558229b3ab5c3946a51e58b4caae775b0).
|
||||||
|
* [Fix ellipsis button not working if the last commit loading is deferred](https://codeberg.org/forgejo/forgejo/commit/1e29bccddbeb29eec3ceb507612851021ab4d60d).
|
||||||
|
* [Fix incorrect diff expander for deletion of last lines in a file](https://codeberg.org/forgejo/forgejo/commit/85bf170ff0d54471fe88903009a3fec4ef3e6e8c).
|
||||||
|
* [Do not exceed display for the PR page buttons on smaller screens](https://codeberg.org/forgejo/forgejo/commit/e7297d423f566a383c8861c4aaee028606591038).
|
||||||
|
* [Move citation button to proper place](https://codeberg.org/forgejo/forgejo/commit/eb4061babacfee2b72f4a33412530eb9f0de3b25).
|
||||||
|
* [Expire artifacts before deleting them physically](https://codeberg.org/forgejo/forgejo/commit/7f64e4d2a3f20b7d7de6542de5e0856c643e821f).
|
||||||
|
* [Fix can not select team reviewers when reviewers is empty](https://codeberg.org/forgejo/forgejo/commit/df439b6a983865ba559e517e5e93f5f1a53a97a0).
|
||||||
|
* [Fix default avatar image size in PR diff page](https://codeberg.org/forgejo/forgejo/commit/3aed8ae03475a430c0dc8e33f42fa9269a4844bd).
|
||||||
|
* [Fix branch list bug which displayed default branch twice](https://codeberg.org/forgejo/forgejo/commit/0e6fd0d1c1e31d22707e6f06124d5bf76361eaab).
|
||||||
|
* [Set the `isPermaLink` attribute to `false` in the `guid` sub-element](https://codeberg.org/forgejo/forgejo/commit/5574968ecbc34908dfa17b28bfc79c3490eaa685).
|
||||||
|
* [Fix long package version names overflowing](https://codeberg.org/forgejo/forgejo/commit/3d474110c181df7854576d78e46209908f7e1b52).
|
||||||
|
* [Fix wrong link in user and organization profile when using relative url](https://codeberg.org/forgejo/forgejo/commit/42149ff1a816501643ec2407ed61a83bf5b65059).
|
||||||
|
* [Fix session key conflict with database keyword](https://codeberg.org/forgejo/forgejo/commit/4c29c75968f520123f125e8305b2c29198664251).
|
||||||
|
* [Fix commit status in repo list](https://codeberg.org/forgejo/forgejo/commit/0abb5633e34fd14c2d49de0b4c98f7ba7d98a37e).
|
||||||
|
* [Fix incorrect action duration time when rerun the job before executed once](https://codeberg.org/forgejo/forgejo/commit/07ba4d9f87cf21b7ce87158ae5651cae3bb35604).
|
||||||
|
* [Fix missing mail reply address](https://codeberg.org/forgejo/forgejo/commit/3081e7e1536356346f73fb4a0d00101863b2cf05).
|
||||||
|
* [Filter inactive auth sources](https://codeberg.org/forgejo/forgejo/commit/e378545f3083990eb36ff5d72477662d9787280d).
|
||||||
|
* [Refactor Find Sources and fix bug when view a user who belongs to an inactive auth source](https://codeberg.org/forgejo/forgejo/commit/1bf5527eac6b947010c8faf408f6747de2a2384f).
|
||||||
|
* [Fix issue not showing on default board and add test](https://codeberg.org/forgejo/forgejo/commit/1eae2aadae0583ab092d6ed857bb727829aa52b7).
|
||||||
|
* [Improve file history UI and fix URL escaping bug](https://codeberg.org/forgejo/forgejo/commit/d1527dac3d1e68caf5a6f54c08144e28256e5c47).
|
||||||
|
* [Fix ldap admin privileges update bug](https://codeberg.org/forgejo/forgejo/commit/7ad31567cdc8206e0080b851a9b880729266b084).
|
||||||
|
* **other**
|
||||||
|
* [[PERFORMANCE] git check-attr on bare repo if supported](https://codeberg.org/forgejo/forgejo/pulls/2763)
|
||||||
|
* [[REFACTOR] [AGIT] Refactor the AGit code](https://codeberg.org/forgejo/forgejo/pulls/2386)
|
||||||
|
* [[REFACTOR] generation of JWT secret](https://codeberg.org/forgejo/forgejo/pulls/2227)
|
||||||
|
* [[REFACTOR] PKT protocol](https://codeberg.org/forgejo/forgejo/pulls/2868)
|
||||||
|
* [Remove .exe suffix when cross-compiling on Windows](https://codeberg.org/forgejo/forgejo/commit/6acce16ee3a03df1cc06c46398f594009a0e31b9).
|
||||||
|
* [Refactor repo header/list](https://codeberg.org/forgejo/forgejo/commit/65e190ae8bd6c72d8701a58d67b256c87b92c189).
|
||||||
|
* [Update register application URL for GitLab](https://codeberg.org/forgejo/forgejo/commit/64fcf0cb64d455d5ca1420aa832aa057cf61e6dd).
|
||||||
|
* [Update golang links to use https](https://codeberg.org/forgejo/forgejo/commit/8ef53c871bcb5c007b3640a347c7868585c9e4de).
|
||||||
|
* [Teams: new View button](https://codeberg.org/forgejo/forgejo/commit/e3afe4a248ac3a961f332e2ba221bedafa3dfb7e).
|
||||||
|
* [Commit-Dropdown: Show Author of commit if available](https://codeberg.org/forgejo/forgejo/commit/300c8dedfd01ba0ea63486b644e93aa2be6785b2).
|
||||||
|
* [Refactor dropzone](https://codeberg.org/forgejo/forgejo/commit/c1ac3e5891a49bedc5e54ed5811cb2c0e058c43c).
|
||||||
|
* [When the title in the issue has a value, set the text cursor at the end of the text.](https://codeberg.org/forgejo/forgejo/commit/8c2559a72603e07fe682efddd698e1fc190b2728).
|
||||||
|
* [Load citation JS only when needed](https://codeberg.org/forgejo/forgejo/commit/f2fc2dcfc9305a42242421c718ee3673bd1c851c).
|
||||||
|
* [Refactor markdown attention render](https://codeberg.org/forgejo/forgejo/commit/ec2201a3da5f18e55bfc0a54114ac935804f4ef8).
|
||||||
|
* [Light theme color enhancements](https://codeberg.org/forgejo/forgejo/commit/23e2ace77d1612cda09bc0d08690314e7321cca3).
|
||||||
|
* [Dark theme color enhancements](https://codeberg.org/forgejo/forgejo/commit/704a59e59584041f95939e3d90260173906f946a).
|
||||||
|
* [Refactor markup/csv: don't read all to memory](https://codeberg.org/forgejo/forgejo/commit/d413a8fcacc81b6f7039371408034c9c2fc6c15f).
|
||||||
|
* [Move all login and account creation page labels to be above inputs](https://codeberg.org/forgejo/forgejo/commit/3acea02eb66ea09248ff29eb6b9cefce29fcea37).
|
||||||
|
* [Fix Gitpod logic of setting ROOT_URL](https://codeberg.org/forgejo/forgejo/commit/e52d87758272c417bb9b30e944f9b0bd33d28cb7).
|
||||||
|
* [Fix broken following organization](https://codeberg.org/forgejo/forgejo/commit/fd3b4afa2b3621ece2d7d1587fd4b017142d75a0).
|
||||||
|
* [Don't do a full page load when clicking `Watch` or `Star`](https://codeberg.org/forgejo/forgejo/commit/6992ef98fc227a60cf06e0a06b9ae2492b3d61be).
|
||||||
|
* [Fix non-alphabetic sorting of repo topics](https://codeberg.org/forgejo/forgejo/commit/a240d5dfa7e261f2fb703cf24b1ba4dc6aa47bfd).
|
||||||
|
* [Make cross-reference issue links work in markdown documents again](https://codeberg.org/forgejo/forgejo/commit/12c0487e01d3fd9fe289345c53e8a220be55e864).
|
||||||
|
* [Fix tooltip of variable edit button](https://codeberg.org/forgejo/forgejo/commit/361839fb1c8bdfb8291bbcf9bd650b21a605bbd7).
|
||||||
|
* [Disable query token param in integration tests](https://codeberg.org/forgejo/forgejo/commit/33439b733a4f69640350b9cda370963ebe9d1e0a).
|
||||||
|
* [Add merge arrow direction and update styling](https://codeberg.org/forgejo/forgejo/commit/e522e774cae2240279fc48c349fc513c9d3353ee).
|
||||||
|
* [Add links to owner home page in explore](https://codeberg.org/forgejo/forgejo/commit/dd5693387e0642e1aba05b01eeb18139ce90ef5e).
|
||||||
|
* [Render PyPi long description as document](https://codeberg.org/forgejo/forgejo/commit/876a0cb3d652f42545abdb33dc4fd71a7c3343bf).
|
||||||
|
* [Ignore temporary files for directory size](https://codeberg.org/forgejo/forgejo/commit/cb8298b7178f5dde302604bfe34c658b725f16f8).
|
||||||
|
* [Make pushUpdate error verbose](https://codeberg.org/forgejo/forgejo/commit/1bfcdeef4cca0f5509476358e5931c13d37ed1ca).
|
||||||
|
* [Add download URL for executable files](https://codeberg.org/forgejo/forgejo/commit/9341b37520e5626352bf2df52e8dbace2985c0d7).
|
||||||
|
* [Improve profile for Organizations](https://codeberg.org/forgejo/forgejo/commit/089ac06969030b0886d4e20bf8f7a757f785f158).
|
||||||
|
* [Fix Show/hide filetree button on small displays](https://codeberg.org/forgejo/forgejo/commit/e31c6cfe6e30341c502302d1c0a03138f8bf5c9f).
|
||||||
|
* [Fix merge base commit for fast-forwarded GitLab PRs](https://codeberg.org/forgejo/forgejo/commit/02dae3f84b80047bef391960eea1350d551e4d72).
|
||||||
|
* [Align ISSUE_TEMPLATE with the new label system](https://codeberg.org/forgejo/forgejo/commit/248b7ee850ecdb538b22ddcfbe80b6f91be32b70).
|
||||||
|
* [Improve the list header in milestone page](https://codeberg.org/forgejo/forgejo/commit/8abc1aae4ab5b03be0bcbdd390bb903b54ccd21a).
|
||||||
|
|
||||||
|
## 1.21.11-1
|
||||||
|
|
||||||
|
This stable release contains a single bug fix for a regression introduced in v1.21.11-0 by which creating a tag via the API would fail with error 500 on a repository a where Forgejo Actions workflow triggered by tags exists.
|
||||||
|
|
||||||
|
* Recommended Action
|
||||||
|
|
||||||
|
We recommend that all Forgejo installations are [upgraded](https://forgejo.org/docs/v1.21/admin/upgrade/) to the latest version as soon as possible.
|
||||||
|
|
||||||
|
* [Forgejo Semantic Version](https://forgejo.org/docs/v1.21/user/semver/)
|
||||||
|
|
||||||
|
The semantic version was updated to `6.0.12+0-gitea-1.21.10`
|
||||||
|
|
||||||
|
* Bug fix
|
||||||
|
|
||||||
|
* [error 500 on tag creation when a workflow exists](https://codeberg.org/forgejo/forgejo/issues/3327)
|
||||||
|
|
||||||
|
## 1.21.11-0
|
||||||
|
|
||||||
|
[The complete list of new commits included in the Forgejo v1.21.11-0 release can be reviewed here](https://codeberg.org/forgejo/forgejo/compare/v1.21.10-0...v1.21.11-0), or from the comand line with:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ git clone https://codeberg.org/forgejo/forgejo
|
||||||
|
$ git -C forgejo log --oneline --no-merges v1.21.10-0..v1.21.11-0
|
||||||
|
```
|
||||||
|
|
||||||
|
This stable release contains bug fixes and **security fixes**.
|
||||||
|
|
||||||
|
* 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.11+0-gitea-1.21.10`
|
||||||
|
|
||||||
|
* Security fix
|
||||||
|
|
||||||
|
* [Fixed a privilege escalation through git push options](https://codeberg.org/forgejo/forgejo/commit/cc80e661531794fff7f8a336eaaefdb7e3bd3956) that allows any user to change the visibility of any repository they can see, regardless of their level of access.
|
||||||
|
* [Fixed a bug that allows user-supplied, non-sandboxed JavaScript to be run from the same domain as the forge](https://codeberg.org/forgejo/forgejo/commit/8dcc7d9e8ce36d94bae1a1becddc4735f51add3c), via `/{owner}/{repo}/render/branch/{branch}/{filename}` URLs.
|
||||||
|
|
||||||
|
* Bug fixes
|
||||||
|
|
||||||
|
* [Use system action user to trigger scheduled action workflows](https://codeberg.org/forgejo/forgejo/commit/387aea4434488555838e55e067242509bc1510a6)
|
||||||
|
* [Close file in upload function](https://codeberg.org/forgejo/forgejo/commit/fd47240545ab1c4f10d07434c2ba00fff044236a)
|
||||||
|
* [Prevent registering runners for deleted repositories](https://codeberg.org/forgejo/forgejo/commit/fd47240545ab1c4f10d07434c2ba00fff044236a). Prevents 500 Internal Server Error in admin interface.
|
||||||
|
* [More reliable pagination support when migrating from gitbucket](https://codeberg.org/forgejo/forgejo/commit/e702e79625980b08ec060a1690b76502455acad9)
|
||||||
|
* [Fix automerge when used with actions](https://codeberg.org/forgejo/forgejo/commit/4889a3a1713d91a5ae95af4edf1bb3352d1871fd)
|
||||||
|
|
||||||
## 1.21.10-0
|
## 1.21.10-0
|
||||||
|
|
||||||
|
@ -15,7 +522,7 @@ $ git -C forgejo log --oneline --no-merges v1.21.8-0..v1.21.10-0
|
||||||
|
|
||||||
This stable release contains bug fixes and a **security fix**.
|
This stable release contains bug fixes and a **security fix**.
|
||||||
|
|
||||||
Note that there is no `Forgejo v1.21.9-0` release. The release numbering of the `Forgejo v1.21` patch series follows the Gitea release numbering. However, the publication of `Gitea v1.21.9` and `Gitea v1.21.10` were a few days appart because of a regression that is not present on Forgejo and there was no need to publish `Forgejo v1.21.9-0`.
|
Note that there is no `Forgejo v1.21.9-0` release. The release numbering of the `Forgejo v1.21` patch series follows the Gitea release numbering. However, the publication of `Gitea v1.21.9` and `Gitea v1.21.10` were a few days apart because of a regression that is not present on Forgejo and there was no need to publish `Forgejo v1.21.9-0`.
|
||||||
|
|
||||||
* Recommended Action
|
* Recommended Action
|
||||||
|
|
||||||
|
@ -977,7 +1484,7 @@ This stable release contains security fixes.
|
||||||
|
|
||||||
* Security fixes
|
* Security fixes
|
||||||
|
|
||||||
* [An additional verification](https://codeberg.org/forgejo/forgejo/commit/a259a928a) was implemented to prevent [open redirects](https://en.wikipedia.org/wiki/Open_redirect).
|
* [An additional verification](https://codeberg.org/forgejo/forgejo/commit/a259a928a) was implemented to prevent [open redirects](https://en.wikipedia.org/wiki/Open_redirect).
|
||||||
|
|
||||||
* Bug fixes
|
* Bug fixes
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,8 @@ import (
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) != 2 {
|
if len(os.Args) != 2 {
|
||||||
println("usage: backport-locales <to-ref>")
|
fmt.Println("usage: backport-locales <to-ref>")
|
||||||
println("eg: backport-locales release/v1.19")
|
fmt.Println("eg: backport-locales release/v1.19")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,7 @@ func newFileCollector(fileFilter string, batchSize int) (*fileCollector, error)
|
||||||
co.includePatterns = append(co.includePatterns, regexp.MustCompile(`.*\.go$`))
|
co.includePatterns = append(co.includePatterns, regexp.MustCompile(`.*\.go$`))
|
||||||
|
|
||||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`.*\bbindata\.go$`))
|
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`.*\bbindata\.go$`))
|
||||||
|
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`\.pb\.go$`))
|
||||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/gitea-repositories-meta`))
|
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/gitea-repositories-meta`))
|
||||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/integration/migration-test`))
|
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/integration/migration-test`))
|
||||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`modules/git/tests`))
|
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`modules/git/tests`))
|
||||||
|
@ -203,17 +204,6 @@ Example:
|
||||||
`, "file-batch-exec")
|
`, "file-batch-exec")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getGoVersion() string {
|
|
||||||
goModFile, err := os.ReadFile("go.mod")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf(`Faild to read "go.mod": %v`, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
goModVersionRegex := regexp.MustCompile(`go \d+\.\d+`)
|
|
||||||
goModVersionLine := goModVersionRegex.Find(goModFile)
|
|
||||||
return string(goModVersionLine[3:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFileCollectorFromMainOptions(mainOptions map[string]string) (fc *fileCollector, err error) {
|
func newFileCollectorFromMainOptions(mainOptions map[string]string) (fc *fileCollector, err error) {
|
||||||
fileFilter := mainOptions["file-filter"]
|
fileFilter := mainOptions["file-filter"]
|
||||||
if fileFilter == "" {
|
if fileFilter == "" {
|
||||||
|
@ -278,7 +268,8 @@ func main() {
|
||||||
log.Print("the -d option is not supported by gitea-fmt")
|
log.Print("the -d option is not supported by gitea-fmt")
|
||||||
}
|
}
|
||||||
cmdErrors = append(cmdErrors, giteaFormatGoImports(files, containsString(subArgs, "-w")))
|
cmdErrors = append(cmdErrors, giteaFormatGoImports(files, containsString(subArgs, "-w")))
|
||||||
cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("GOFUMPT_PACKAGE"), "-extra", "-lang", getGoVersion()}, substArgs...)))
|
cmdErrors = append(cmdErrors, passThroughCmd("gofmt", append([]string{"-w", "-r", "interface{} -> any"}, substArgs...)))
|
||||||
|
cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("GOFUMPT_PACKAGE"), "-extra"}, substArgs...)))
|
||||||
default:
|
default:
|
||||||
log.Fatalf("unknown cmd: %s %v", subCmd, subArgs)
|
log.Fatalf("unknown cmd: %s %v", subCmd, subArgs)
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,7 +142,7 @@ func generate() ([]byte, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// gitea customizations
|
// Forgejo customizations
|
||||||
i, ok := aliasMap["tada"]
|
i, ok := aliasMap["tada"]
|
||||||
if ok {
|
if ok {
|
||||||
data[i].Aliases = append(data[i].Aliases, "hooray")
|
data[i].Aliases = append(data[i].Aliases, "hooray")
|
||||||
|
|
|
@ -36,6 +36,7 @@ var microcmdUserChangePassword = &cli.Command{
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "must-change-password",
|
Name: "must-change-password",
|
||||||
Usage: "User must change password",
|
Usage: "User must change password",
|
||||||
|
Value: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -57,23 +58,18 @@ func runChangePassword(c *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var mustChangePassword optional.Option[bool]
|
|
||||||
if c.IsSet("must-change-password") {
|
|
||||||
mustChangePassword = optional.Some(c.Bool("must-change-password"))
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := &user_service.UpdateAuthOptions{
|
opts := &user_service.UpdateAuthOptions{
|
||||||
Password: optional.Some(c.String("password")),
|
Password: optional.Some(c.String("password")),
|
||||||
MustChangePassword: mustChangePassword,
|
MustChangePassword: optional.Some(c.Bool("must-change-password")),
|
||||||
}
|
}
|
||||||
if err := user_service.UpdateAuth(ctx, user, opts); err != nil {
|
if err := user_service.UpdateAuth(ctx, user, opts); err != nil {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, password.ErrMinLength):
|
case errors.Is(err, password.ErrMinLength):
|
||||||
return fmt.Errorf("Password is not long enough. Needs to be at least %d", setting.MinPasswordLength)
|
return fmt.Errorf("password is not long enough, needs to be at least %d characters", setting.MinPasswordLength)
|
||||||
case errors.Is(err, password.ErrComplexity):
|
case errors.Is(err, password.ErrComplexity):
|
||||||
return errors.New("Password does not meet complexity requirements")
|
return errors.New("password does not meet complexity requirements")
|
||||||
case errors.Is(err, password.ErrIsPwned):
|
case errors.Is(err, password.ErrIsPwned):
|
||||||
return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords")
|
return errors.New("the password is in a list of stolen passwords previously exposed in public data breaches, please try again with a different password, to see more details: https://haveibeenpwned.com/Passwords")
|
||||||
default:
|
default:
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
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/optional"
|
||||||
|
@ -46,9 +47,10 @@ var microcmdUserCreate = &cli.Command{
|
||||||
Usage: "Generate a random password for the user",
|
Usage: "Generate a random password for the user",
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "must-change-password",
|
Name: "must-change-password",
|
||||||
Usage: "Set this option to false to prevent forcing the user to change their password after initial login",
|
Usage: "Set this option to false to prevent forcing the user to change their password after initial login",
|
||||||
Value: true,
|
Value: true,
|
||||||
|
DisableDefaultText: true,
|
||||||
},
|
},
|
||||||
&cli.IntFlag{
|
&cli.IntFlag{
|
||||||
Name: "random-password-length",
|
Name: "random-password-length",
|
||||||
|
@ -72,10 +74,10 @@ func runCreateUser(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.IsSet("name") && c.IsSet("username") {
|
if c.IsSet("name") && c.IsSet("username") {
|
||||||
return errors.New("Cannot set both --name and --username flags")
|
return errors.New("cannot set both --name and --username flags")
|
||||||
}
|
}
|
||||||
if !c.IsSet("name") && !c.IsSet("username") {
|
if !c.IsSet("name") && !c.IsSet("username") {
|
||||||
return errors.New("One of --name or --username flags must be set")
|
return errors.New("one of --name or --username flags must be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.IsSet("password") && c.IsSet("random-password") {
|
if c.IsSet("password") && c.IsSet("random-password") {
|
||||||
|
@ -111,12 +113,21 @@ func runCreateUser(c *cli.Context) error {
|
||||||
return errors.New("must set either password or random-password flag")
|
return errors.New("must set either password or random-password flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
changePassword := c.Bool("must-change-password")
|
isAdmin := c.Bool("admin")
|
||||||
|
mustChangePassword := true // always default to true
|
||||||
// If this is the first user being created.
|
if c.IsSet("must-change-password") {
|
||||||
// Take it as the admin and don't force a password update.
|
// if the flag is set, use the value provided by the user
|
||||||
if n := user_model.CountUsers(ctx, nil); n == 0 {
|
mustChangePassword = c.Bool("must-change-password")
|
||||||
changePassword = false
|
} else {
|
||||||
|
// check whether there are users in the database
|
||||||
|
hasUserRecord, err := db.IsTableNotEmpty(&user_model.User{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("IsTableNotEmpty: %w", err)
|
||||||
|
}
|
||||||
|
if !hasUserRecord {
|
||||||
|
// if this is the first admin being created, don't force to change password (keep the old behavior)
|
||||||
|
mustChangePassword = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
restricted := optional.None[bool]()
|
restricted := optional.None[bool]()
|
||||||
|
@ -132,8 +143,8 @@ func runCreateUser(c *cli.Context) error {
|
||||||
Name: username,
|
Name: username,
|
||||||
Email: c.String("email"),
|
Email: c.String("email"),
|
||||||
Passwd: password,
|
Passwd: password,
|
||||||
IsAdmin: c.Bool("admin"),
|
IsAdmin: isAdmin,
|
||||||
MustChangePassword: changePassword,
|
MustChangePassword: mustChangePassword,
|
||||||
Visibility: visibility,
|
Visibility: visibility,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -782,7 +782,7 @@ func writeFlushPktLine(ctx context.Context, out io.Writer) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write an Pkt-Line based on `data` to `out` according to the specifcation.
|
// Write an Pkt-Line based on `data` to `out` according to the specification.
|
||||||
// https://git-scm.com/docs/protocol-common
|
// https://git-scm.com/docs/protocol-common
|
||||||
func writeDataPktLine(ctx context.Context, out io.Writer, data []byte) error {
|
func writeDataPktLine(ctx context.Context, out io.Writer, data []byte) error {
|
||||||
// Implementations SHOULD NOT send an empty pkt-line ("0004").
|
// Implementations SHOULD NOT send an empty pkt-line ("0004").
|
||||||
|
|
10
cmd/serv.go
10
cmd/serv.go
|
@ -136,7 +136,7 @@ func runServ(c *cli.Context) error {
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
|
|
||||||
if setting.SSH.Disabled {
|
if setting.SSH.Disabled {
|
||||||
println("Forgejo: SSH has been disabled")
|
fmt.Println("Forgejo: SSH has been disabled")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,13 +164,13 @@ func runServ(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
switch key.Type {
|
switch key.Type {
|
||||||
case asymkey_model.KeyTypeDeploy:
|
case asymkey_model.KeyTypeDeploy:
|
||||||
println("Hi there! You've successfully authenticated with the deploy key named " + key.Name + ", but Forgejo does not provide shell access.")
|
fmt.Println("Hi there! You've successfully authenticated with the deploy key named " + key.Name + ", but Forgejo does not provide shell access.")
|
||||||
case asymkey_model.KeyTypePrincipal:
|
case asymkey_model.KeyTypePrincipal:
|
||||||
println("Hi there! You've successfully authenticated with the principal " + key.Content + ", but Forgejo does not provide shell access.")
|
fmt.Println("Hi there! You've successfully authenticated with the principal " + key.Content + ", but Forgejo does not provide shell access.")
|
||||||
default:
|
default:
|
||||||
println("Hi there, " + user.Name + "! You've successfully authenticated with the key named " + key.Name + ", but Forgejo does not provide shell access.")
|
fmt.Println("Hi there, " + user.Name + "! You've successfully authenticated with the key named " + key.Name + ", but Forgejo does not provide shell access.")
|
||||||
}
|
}
|
||||||
println("If this is unexpected, please log in with password and setup Forgejo under another user.")
|
fmt.Println("If this is unexpected, please log in with password and setup Forgejo under another user.")
|
||||||
return nil
|
return nil
|
||||||
} else if c.Bool("debug") {
|
} else if c.Bool("debug") {
|
||||||
log.Debug("SSH_ORIGINAL_COMMAND: %s", os.Getenv("SSH_ORIGINAL_COMMAND"))
|
log.Debug("SSH_ORIGINAL_COMMAND: %s", os.Getenv("SSH_ORIGINAL_COMMAND"))
|
||||||
|
|
|
@ -114,7 +114,7 @@ func showWebStartupMessage(msg string) {
|
||||||
log.Info("* WorkPath: %s", setting.AppWorkPath)
|
log.Info("* WorkPath: %s", setting.AppWorkPath)
|
||||||
log.Info("* CustomPath: %s", setting.CustomPath)
|
log.Info("* CustomPath: %s", setting.CustomPath)
|
||||||
log.Info("* ConfigFile: %s", setting.CustomConf)
|
log.Info("* ConfigFile: %s", setting.CustomConf)
|
||||||
log.Info("%s", msg)
|
log.Info("%s", msg) // show startup message
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveInstall(ctx *cli.Context) error {
|
func serveInstall(ctx *cli.Context) error {
|
||||||
|
|
|
@ -407,8 +407,8 @@ USER = root
|
||||||
;; Database connection max life time, default is 0 or 3s mysql (See #6804 & #7071 for reasoning)
|
;; Database connection max life time, default is 0 or 3s mysql (See #6804 & #7071 for reasoning)
|
||||||
;CONN_MAX_LIFETIME = 3s
|
;CONN_MAX_LIFETIME = 3s
|
||||||
;;
|
;;
|
||||||
;; Database maximum number of open connections, default is 0 meaning no maximum
|
;; Database maximum number of open connections, default is 100 which is the lowest default from Postgres (MariaDB + MySQL default to 151). Ensure you only increase the value if you configured your database server accordingly.
|
||||||
;MAX_OPEN_CONNS = 0
|
;MAX_OPEN_CONNS = 100
|
||||||
;;
|
;;
|
||||||
;; Whether execute database models migrations automatically
|
;; Whether execute database models migrations automatically
|
||||||
;AUTO_MIGRATION = true
|
;AUTO_MIGRATION = true
|
||||||
|
@ -2394,22 +2394,6 @@ LEVEL = Info
|
||||||
;; Enable issue by repository metrics; default is false
|
;; Enable issue by repository metrics; default is false
|
||||||
;ENABLED_ISSUE_BY_REPOSITORY = false
|
;ENABLED_ISSUE_BY_REPOSITORY = false
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;[task]
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;;
|
|
||||||
;; Task queue type, could be `channel` or `redis`.
|
|
||||||
;QUEUE_TYPE = channel
|
|
||||||
;;
|
|
||||||
;; Task queue length, available only when `QUEUE_TYPE` is `channel`.
|
|
||||||
;QUEUE_LENGTH = 1000
|
|
||||||
;;
|
|
||||||
;; Task queue connection string, available only when `QUEUE_TYPE` is `redis`.
|
|
||||||
;; If there is a password of redis, use `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` or `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` for `redis-clsuter`.
|
|
||||||
;QUEUE_CONN_STR = "redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s"
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;[migrations]
|
;[migrations]
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -64,7 +64,7 @@ require (
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||||
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4
|
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4
|
||||||
github.com/klauspost/compress v1.17.7
|
github.com/klauspost/compress v1.17.8
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6
|
github.com/klauspost/cpuid/v2 v2.2.6
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
github.com/markbates/goth v1.78.0
|
github.com/markbates/goth v1.78.0
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -551,8 +551,8 @@ github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs
|
||||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||||
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||||
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 877d11b403b2b573fe435b792245b403367a2bb2
|
|
|
@ -16,14 +16,9 @@ import (
|
||||||
type ActionJobList []*ActionRunJob
|
type ActionJobList []*ActionRunJob
|
||||||
|
|
||||||
func (jobs ActionJobList) GetRunIDs() []int64 {
|
func (jobs ActionJobList) GetRunIDs() []int64 {
|
||||||
ids := make(container.Set[int64], len(jobs))
|
return container.FilterSlice(jobs, func(j *ActionRunJob) (int64, bool) {
|
||||||
for _, j := range jobs {
|
return j.RunID, j.RunID != 0
|
||||||
if j.RunID == 0 {
|
})
|
||||||
continue
|
|
||||||
}
|
|
||||||
ids.Add(j.RunID)
|
|
||||||
}
|
|
||||||
return ids.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jobs ActionJobList) LoadRuns(ctx context.Context, withRepo bool) error {
|
func (jobs ActionJobList) LoadRuns(ctx context.Context, withRepo bool) error {
|
||||||
|
|
|
@ -19,19 +19,15 @@ type RunList []*ActionRun
|
||||||
|
|
||||||
// GetUserIDs returns a slice of user's id
|
// GetUserIDs returns a slice of user's id
|
||||||
func (runs RunList) GetUserIDs() []int64 {
|
func (runs RunList) GetUserIDs() []int64 {
|
||||||
ids := make(container.Set[int64], len(runs))
|
return container.FilterSlice(runs, func(run *ActionRun) (int64, bool) {
|
||||||
for _, run := range runs {
|
return run.TriggerUserID, true
|
||||||
ids.Add(run.TriggerUserID)
|
})
|
||||||
}
|
|
||||||
return ids.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (runs RunList) GetRepoIDs() []int64 {
|
func (runs RunList) GetRepoIDs() []int64 {
|
||||||
ids := make(container.Set[int64], len(runs))
|
return container.FilterSlice(runs, func(run *ActionRun) (int64, bool) {
|
||||||
for _, run := range runs {
|
return run.RepoID, true
|
||||||
ids.Add(run.RepoID)
|
})
|
||||||
}
|
|
||||||
return ids.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (runs RunList) LoadTriggerUser(ctx context.Context) error {
|
func (runs RunList) LoadTriggerUser(ctx context.Context) error {
|
||||||
|
|
|
@ -16,14 +16,9 @@ type RunnerList []*ActionRunner
|
||||||
|
|
||||||
// GetUserIDs returns a slice of user's id
|
// GetUserIDs returns a slice of user's id
|
||||||
func (runners RunnerList) GetUserIDs() []int64 {
|
func (runners RunnerList) GetUserIDs() []int64 {
|
||||||
ids := make(container.Set[int64], len(runners))
|
return container.FilterSlice(runners, func(runner *ActionRunner) (int64, bool) {
|
||||||
for _, runner := range runners {
|
return runner.OwnerID, runner.OwnerID != 0
|
||||||
if runner.OwnerID == 0 {
|
})
|
||||||
continue
|
|
||||||
}
|
|
||||||
ids.Add(runner.OwnerID)
|
|
||||||
}
|
|
||||||
return ids.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (runners RunnerList) LoadOwners(ctx context.Context) error {
|
func (runners RunnerList) LoadOwners(ctx context.Context) error {
|
||||||
|
@ -41,16 +36,9 @@ func (runners RunnerList) LoadOwners(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (runners RunnerList) getRepoIDs() []int64 {
|
func (runners RunnerList) getRepoIDs() []int64 {
|
||||||
repoIDs := make(container.Set[int64], len(runners))
|
return container.FilterSlice(runners, func(runner *ActionRunner) (int64, bool) {
|
||||||
for _, runner := range runners {
|
return runner.RepoID, runner.RepoID > 0
|
||||||
if runner.RepoID == 0 {
|
})
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := repoIDs[runner.RepoID]; !ok {
|
|
||||||
repoIDs[runner.RepoID] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return repoIDs.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (runners RunnerList) LoadRepos(ctx context.Context) error {
|
func (runners RunnerList) LoadRepos(ctx context.Context) error {
|
||||||
|
|
|
@ -18,19 +18,15 @@ type ScheduleList []*ActionSchedule
|
||||||
|
|
||||||
// GetUserIDs returns a slice of user's id
|
// GetUserIDs returns a slice of user's id
|
||||||
func (schedules ScheduleList) GetUserIDs() []int64 {
|
func (schedules ScheduleList) GetUserIDs() []int64 {
|
||||||
ids := make(container.Set[int64], len(schedules))
|
return container.FilterSlice(schedules, func(schedule *ActionSchedule) (int64, bool) {
|
||||||
for _, schedule := range schedules {
|
return schedule.TriggerUserID, true
|
||||||
ids.Add(schedule.TriggerUserID)
|
})
|
||||||
}
|
|
||||||
return ids.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (schedules ScheduleList) GetRepoIDs() []int64 {
|
func (schedules ScheduleList) GetRepoIDs() []int64 {
|
||||||
ids := make(container.Set[int64], len(schedules))
|
return container.FilterSlice(schedules, func(schedule *ActionSchedule) (int64, bool) {
|
||||||
for _, schedule := range schedules {
|
return schedule.RepoID, true
|
||||||
ids.Add(schedule.RepoID)
|
})
|
||||||
}
|
|
||||||
return ids.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (schedules ScheduleList) LoadTriggerUser(ctx context.Context) error {
|
func (schedules ScheduleList) LoadTriggerUser(ctx context.Context) error {
|
||||||
|
@ -44,6 +40,9 @@ func (schedules ScheduleList) LoadTriggerUser(ctx context.Context) error {
|
||||||
schedule.TriggerUser = user_model.NewActionsUser()
|
schedule.TriggerUser = user_model.NewActionsUser()
|
||||||
} else {
|
} else {
|
||||||
schedule.TriggerUser = users[schedule.TriggerUserID]
|
schedule.TriggerUser = users[schedule.TriggerUserID]
|
||||||
|
if schedule.TriggerUser == nil {
|
||||||
|
schedule.TriggerUser = user_model.NewGhostUser()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -16,11 +16,9 @@ import (
|
||||||
type SpecList []*ActionScheduleSpec
|
type SpecList []*ActionScheduleSpec
|
||||||
|
|
||||||
func (specs SpecList) GetScheduleIDs() []int64 {
|
func (specs SpecList) GetScheduleIDs() []int64 {
|
||||||
ids := make(container.Set[int64], len(specs))
|
return container.FilterSlice(specs, func(spec *ActionScheduleSpec) (int64, bool) {
|
||||||
for _, spec := range specs {
|
return spec.ScheduleID, true
|
||||||
ids.Add(spec.ScheduleID)
|
})
|
||||||
}
|
|
||||||
return ids.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (specs SpecList) LoadSchedules(ctx context.Context) error {
|
func (specs SpecList) LoadSchedules(ctx context.Context) error {
|
||||||
|
@ -46,11 +44,9 @@ func (specs SpecList) LoadSchedules(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (specs SpecList) GetRepoIDs() []int64 {
|
func (specs SpecList) GetRepoIDs() []int64 {
|
||||||
ids := make(container.Set[int64], len(specs))
|
return container.FilterSlice(specs, func(spec *ActionScheduleSpec) (int64, bool) {
|
||||||
for _, spec := range specs {
|
return spec.RepoID, true
|
||||||
ids.Add(spec.RepoID)
|
})
|
||||||
}
|
|
||||||
return ids.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (specs SpecList) LoadRepos(ctx context.Context) error {
|
func (specs SpecList) LoadRepos(ctx context.Context) error {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/unit"
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
@ -227,7 +228,9 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
|
||||||
if runner.RepoID != 0 {
|
if runner.RepoID != 0 {
|
||||||
jobCond = builder.Eq{"repo_id": runner.RepoID}
|
jobCond = builder.Eq{"repo_id": runner.RepoID}
|
||||||
} else if runner.OwnerID != 0 {
|
} else if runner.OwnerID != 0 {
|
||||||
jobCond = builder.In("repo_id", builder.Select("id").From("repository").Where(builder.Eq{"owner_id": runner.OwnerID}))
|
jobCond = builder.In("repo_id", builder.Select("`repository`.id").From("repository").
|
||||||
|
Join("INNER", "repo_unit", "`repository`.id = `repo_unit`.repo_id").
|
||||||
|
Where(builder.Eq{"`repository`.owner_id": runner.OwnerID, "`repo_unit`.type": unit.TypeActions}))
|
||||||
}
|
}
|
||||||
if jobCond.IsValid() {
|
if jobCond.IsValid() {
|
||||||
jobCond = builder.In("run_id", builder.Select("id").From("action_run").Where(jobCond))
|
jobCond = builder.In("run_id", builder.Select("id").From("action_run").Where(jobCond))
|
||||||
|
|
|
@ -16,14 +16,9 @@ import (
|
||||||
type TaskList []*ActionTask
|
type TaskList []*ActionTask
|
||||||
|
|
||||||
func (tasks TaskList) GetJobIDs() []int64 {
|
func (tasks TaskList) GetJobIDs() []int64 {
|
||||||
ids := make(container.Set[int64], len(tasks))
|
return container.FilterSlice(tasks, func(t *ActionTask) (int64, bool) {
|
||||||
for _, t := range tasks {
|
return t.JobID, t.JobID != 0
|
||||||
if t.JobID == 0 {
|
})
|
||||||
continue
|
|
||||||
}
|
|
||||||
ids.Add(t.JobID)
|
|
||||||
}
|
|
||||||
return ids.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tasks TaskList) LoadJobs(ctx context.Context) error {
|
func (tasks TaskList) LoadJobs(ctx context.Context) error {
|
||||||
|
|
|
@ -22,11 +22,9 @@ import (
|
||||||
type ActionList []*Action
|
type ActionList []*Action
|
||||||
|
|
||||||
func (actions ActionList) getUserIDs() []int64 {
|
func (actions ActionList) getUserIDs() []int64 {
|
||||||
userIDs := make(container.Set[int64], len(actions))
|
return container.FilterSlice(actions, func(action *Action) (int64, bool) {
|
||||||
for _, action := range actions {
|
return action.ActUserID, true
|
||||||
userIDs.Add(action.ActUserID)
|
})
|
||||||
}
|
|
||||||
return userIDs.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (actions ActionList) LoadActUsers(ctx context.Context) (map[int64]*user_model.User, error) {
|
func (actions ActionList) LoadActUsers(ctx context.Context) (map[int64]*user_model.User, error) {
|
||||||
|
@ -50,11 +48,9 @@ func (actions ActionList) LoadActUsers(ctx context.Context) (map[int64]*user_mod
|
||||||
}
|
}
|
||||||
|
|
||||||
func (actions ActionList) getRepoIDs() []int64 {
|
func (actions ActionList) getRepoIDs() []int64 {
|
||||||
repoIDs := make(container.Set[int64], len(actions))
|
return container.FilterSlice(actions, func(action *Action) (int64, bool) {
|
||||||
for _, action := range actions {
|
return action.RepoID, true
|
||||||
repoIDs.Add(action.RepoID)
|
})
|
||||||
}
|
|
||||||
return repoIDs.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (actions ActionList) LoadRepositories(ctx context.Context) error {
|
func (actions ActionList) LoadRepositories(ctx context.Context) error {
|
||||||
|
@ -80,18 +76,19 @@ func (actions ActionList) loadRepoOwner(ctx context.Context, userMap map[int64]*
|
||||||
userMap = make(map[int64]*user_model.User)
|
userMap = make(map[int64]*user_model.User)
|
||||||
}
|
}
|
||||||
|
|
||||||
userSet := make(container.Set[int64], len(actions))
|
missingUserIDs := container.FilterSlice(actions, func(action *Action) (int64, bool) {
|
||||||
for _, action := range actions {
|
|
||||||
if action.Repo == nil {
|
if action.Repo == nil {
|
||||||
continue
|
return 0, false
|
||||||
}
|
|
||||||
if _, ok := userMap[action.Repo.OwnerID]; !ok {
|
|
||||||
userSet.Add(action.Repo.OwnerID)
|
|
||||||
}
|
}
|
||||||
|
_, alreadyLoaded := userMap[action.Repo.OwnerID]
|
||||||
|
return action.Repo.OwnerID, !alreadyLoaded
|
||||||
|
})
|
||||||
|
if len(missingUserIDs) == 0 {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.GetEngine(ctx).
|
if err := db.GetEngine(ctx).
|
||||||
In("id", userSet.Values()).
|
In("id", missingUserIDs).
|
||||||
Find(&userMap); err != nil {
|
Find(&userMap); err != nil {
|
||||||
return fmt.Errorf("find user: %w", err)
|
return fmt.Errorf("find user: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -135,6 +132,9 @@ func (actions ActionList) LoadComments(ctx context.Context) error {
|
||||||
commentIDs = append(commentIDs, action.CommentID)
|
commentIDs = append(commentIDs, action.CommentID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(commentIDs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
commentsMap := make(map[int64]*issues_model.Comment, len(commentIDs))
|
commentsMap := make(map[int64]*issues_model.Comment, len(commentIDs))
|
||||||
if err := db.GetEngine(ctx).In("id", commentIDs).Find(&commentsMap); err != nil {
|
if err := db.GetEngine(ctx).In("id", commentIDs).Find(&commentsMap); err != nil {
|
||||||
|
|
|
@ -196,15 +196,11 @@ func (nl NotificationList) LoadAttributes(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getPendingRepoIDs returns all the repositoty ids which haven't been loaded
|
||||||
func (nl NotificationList) getPendingRepoIDs() []int64 {
|
func (nl NotificationList) getPendingRepoIDs() []int64 {
|
||||||
ids := make(container.Set[int64], len(nl))
|
return container.FilterSlice(nl, func(n *Notification) (int64, bool) {
|
||||||
for _, notification := range nl {
|
return n.RepoID, n.Repository == nil
|
||||||
if notification.Repository != nil {
|
})
|
||||||
continue
|
|
||||||
}
|
|
||||||
ids.Add(notification.RepoID)
|
|
||||||
}
|
|
||||||
return ids.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadRepos loads repositories from database
|
// LoadRepos loads repositories from database
|
||||||
|
|
|
@ -76,23 +76,14 @@ func calcFingerprintNative(publicKeyContent string) (string, error) {
|
||||||
// CalcFingerprint calculate public key's fingerprint
|
// CalcFingerprint calculate public key's fingerprint
|
||||||
func CalcFingerprint(publicKeyContent string) (string, error) {
|
func CalcFingerprint(publicKeyContent string) (string, error) {
|
||||||
// Call the method based on configuration
|
// Call the method based on configuration
|
||||||
var (
|
useNative := setting.SSH.KeygenPath == ""
|
||||||
fnName, fp string
|
calcFn := util.Iif(useNative, calcFingerprintNative, calcFingerprintSSHKeygen)
|
||||||
err error
|
fp, err := calcFn(publicKeyContent)
|
||||||
)
|
|
||||||
if len(setting.SSH.KeygenPath) == 0 {
|
|
||||||
fnName = "calcFingerprintNative"
|
|
||||||
fp, err = calcFingerprintNative(publicKeyContent)
|
|
||||||
} else {
|
|
||||||
fnName = "calcFingerprintSSHKeygen"
|
|
||||||
fp, err = calcFingerprintSSHKeygen(publicKeyContent)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if IsErrKeyUnableVerify(err) {
|
if IsErrKeyUnableVerify(err) {
|
||||||
log.Info("%s", publicKeyContent)
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return "", fmt.Errorf("%s: %w", fnName, err)
|
return "", fmt.Errorf("CalcFingerprint(%s): %w", util.Iif(useNative, "native", "ssh-keygen"), err)
|
||||||
}
|
}
|
||||||
return fp, nil
|
return fp, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,6 +144,11 @@ func (app *OAuth2Application) TableName() string {
|
||||||
|
|
||||||
// ContainsRedirectURI checks if redirectURI is allowed for app
|
// ContainsRedirectURI checks if redirectURI is allowed for app
|
||||||
func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool {
|
func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool {
|
||||||
|
// OAuth2 requires the redirect URI to be an exact match, no dynamic parts are allowed.
|
||||||
|
// https://stackoverflow.com/questions/55524480/should-dynamic-query-parameters-be-present-in-the-redirection-uri-for-an-oauth2
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc6819#section-5.2.3.3
|
||||||
|
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
||||||
|
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-12#section-3.1
|
||||||
contains := func(s string) bool {
|
contains := func(s string) bool {
|
||||||
s = strings.TrimSuffix(strings.ToLower(s), "/")
|
s = strings.TrimSuffix(strings.ToLower(s), "/")
|
||||||
for _, u := range app.RedirectURIs {
|
for _, u := range app.RedirectURIs {
|
||||||
|
|
|
@ -33,6 +33,7 @@ const (
|
||||||
DLDAP // 5
|
DLDAP // 5
|
||||||
OAuth2 // 6
|
OAuth2 // 6
|
||||||
SSPI // 7
|
SSPI // 7
|
||||||
|
Remote // 8
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns the string name of the LoginType
|
// String returns the string name of the LoginType
|
||||||
|
@ -53,6 +54,7 @@ var Names = map[Type]string{
|
||||||
PAM: "PAM",
|
PAM: "PAM",
|
||||||
OAuth2: "OAuth2",
|
OAuth2: "OAuth2",
|
||||||
SSPI: "SPNEGO with SSPI",
|
SSPI: "SPNEGO with SSPI",
|
||||||
|
Remote: "Remote",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config represents login config as far as the db is concerned
|
// Config represents login config as far as the db is concerned
|
||||||
|
@ -181,6 +183,10 @@ func (source *Source) IsSSPI() bool {
|
||||||
return source.Type == SSPI
|
return source.Type == SSPI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (source *Source) IsRemote() bool {
|
||||||
|
return source.Type == Remote
|
||||||
|
}
|
||||||
|
|
||||||
// HasTLS returns true of this source supports TLS.
|
// HasTLS returns true of this source supports TLS.
|
||||||
func (source *Source) HasTLS() bool {
|
func (source *Source) HasTLS() bool {
|
||||||
hasTLSer, ok := source.Cfg.(HasTLSer)
|
hasTLSer, ok := source.Cfg.(HasTLSer)
|
||||||
|
|
|
@ -150,7 +150,7 @@ func preprocessDatabaseCollation(x *xorm.Engine) {
|
||||||
|
|
||||||
// check column collation, and show warning/error to end users -- no need to fatal, do not block the startup
|
// check column collation, and show warning/error to end users -- no need to fatal, do not block the startup
|
||||||
if !r.IsCollationCaseSensitive(r.DatabaseCollation) {
|
if !r.IsCollationCaseSensitive(r.DatabaseCollation) {
|
||||||
log.Warn("Current database is using a case-insensitive collation %q, although Gitea could work with it, there might be some rare cases which don't work as expected.", r.DatabaseCollation)
|
log.Warn("Current database is using a case-insensitive collation %q, although Forgejo could work with it, there might be some rare cases which don't work as expected.", r.DatabaseCollation)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.InconsistentCollationColumns) > 0 {
|
if len(r.InconsistentCollationColumns) > 0 {
|
||||||
|
|
|
@ -293,8 +293,8 @@ func MaxBatchInsertSize(bean any) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTableNotEmpty returns true if table has at least one record
|
// IsTableNotEmpty returns true if table has at least one record
|
||||||
func IsTableNotEmpty(tableName string) (bool, error) {
|
func IsTableNotEmpty(beanOrTableName any) (bool, error) {
|
||||||
return x.Table(tableName).Exist()
|
return x.Table(beanOrTableName).Exist()
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAllRecords will delete all the records of this table
|
// DeleteAllRecords will delete all the records of this table
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
-
|
||||||
|
id: 1001
|
||||||
|
repo_id: 1
|
||||||
|
index: 1001
|
||||||
|
poster_id: 1
|
||||||
|
name: issue1
|
||||||
|
content: content for the first issue
|
||||||
|
is_pull: true
|
||||||
|
created: 111111111
|
||||||
|
created_unix: 946684800
|
||||||
|
updated_unix: 978307200
|
||||||
|
is_closed: false
|
|
@ -0,0 +1,13 @@
|
||||||
|
-
|
||||||
|
id: 1001
|
||||||
|
type: 0 # pull request
|
||||||
|
status: 2 # mergable
|
||||||
|
issue_id: 1001
|
||||||
|
index: 1001
|
||||||
|
head_repo_id: 1
|
||||||
|
base_repo_id: 1
|
||||||
|
head_branch: branchmax
|
||||||
|
base_branch: master
|
||||||
|
merge_base: 4a357436d925b5c974181ff12a994538ddc5a269
|
||||||
|
has_merged: false
|
||||||
|
flow: 0
|
|
@ -413,3 +413,23 @@
|
||||||
},
|
},
|
||||||
"total_commits": 0
|
"total_commits": 0
|
||||||
}
|
}
|
||||||
|
-
|
||||||
|
id: 891
|
||||||
|
title: "update actions"
|
||||||
|
repo_id: 1
|
||||||
|
owner_id: 1
|
||||||
|
workflow_id: "artifact.yaml"
|
||||||
|
index: 187
|
||||||
|
trigger_user_id: 1
|
||||||
|
ref: "refs/heads/branch2"
|
||||||
|
commit_sha: "985f0301dba5e7b34be866819cd15ad3d8f508ee"
|
||||||
|
event: "push"
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
status: 1 # success
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
created: 1683636108
|
||||||
|
updated: 1683636626
|
||||||
|
need_approval: 0
|
||||||
|
approved_by: 0
|
||||||
|
event_payload: '{"head_commit":{"id":"5f22f7d0d95d614d25a5b68592adb345a4b5c7fd"}}'
|
||||||
|
|
|
@ -26,3 +26,17 @@
|
||||||
status: 1
|
status: 1
|
||||||
started: 1683636528
|
started: 1683636528
|
||||||
stopped: 1683636626
|
stopped: 1683636626
|
||||||
|
-
|
||||||
|
id: 292
|
||||||
|
run_id: 891
|
||||||
|
repo_id: 1
|
||||||
|
owner_id: 1
|
||||||
|
commit_sha: 985f0301dba5e7b34be866819cd15ad3d8f508ee
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
name: job_2
|
||||||
|
attempt: 1
|
||||||
|
job_id: job_2
|
||||||
|
task_id: 47
|
||||||
|
status: 1
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
-
|
-
|
||||||
|
|
||||||
id: 2
|
id: 2 # this is an LFS orphan object
|
||||||
oid: 2eccdb43825d2a49d99d542daa20075cff1d97d9d2349a8977efe9c03661737c
|
oid: 2eccdb43825d2a49d99d542daa20075cff1d97d9d2349a8977efe9c03661737c
|
||||||
size: 107
|
size: 107
|
||||||
repository_id: 54
|
repository_id: 54
|
||||||
|
|
|
@ -59,7 +59,13 @@ var migrations = []*Migration{
|
||||||
// v9 -> v10
|
// v9 -> v10
|
||||||
NewMigration("Add pronouns to user", forgejo_v1_22.AddPronounsToUser),
|
NewMigration("Add pronouns to user", forgejo_v1_22.AddPronounsToUser),
|
||||||
// v11 -> v12
|
// v11 -> v12
|
||||||
|
NewMigration("Add the `created` column to the `issue` table", forgejo_v1_22.AddCreatedToIssue),
|
||||||
|
// v12 -> v13
|
||||||
NewMigration("Add repo_archive_download_count table", forgejo_v1_22.AddRepoArchiveDownloadCount),
|
NewMigration("Add repo_archive_download_count table", forgejo_v1_22.AddRepoArchiveDownloadCount),
|
||||||
|
// v13 -> v14
|
||||||
|
NewMigration("Add `hide_archive_links` column to `release` table", AddHideArchiveLinksToRelease),
|
||||||
|
// v14 -> v15
|
||||||
|
NewMigration("Remove Gitea-specific columns from the repository and badge tables", RemoveGiteaSpecificColumnsFromRepositoryAndBadge),
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentDBVersion returns the current Forgejo database version.
|
// GetCurrentDBVersion returns the current Forgejo database version.
|
||||||
|
|
15
models/forgejo_migrations/v13.go
Normal file
15
models/forgejo_migrations/v13.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package forgejo_migrations //nolint:revive
|
||||||
|
|
||||||
|
import "xorm.io/xorm"
|
||||||
|
|
||||||
|
func AddHideArchiveLinksToRelease(x *xorm.Engine) error {
|
||||||
|
type Release struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
HideArchiveLinks bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.Sync(&Release{})
|
||||||
|
}
|
43
models/forgejo_migrations/v14.go
Normal file
43
models/forgejo_migrations/v14.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package forgejo_migrations //nolint:revive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/models/migrations/base"
|
||||||
|
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RemoveGiteaSpecificColumnsFromRepositoryAndBadge(x *xorm.Engine) error {
|
||||||
|
// Make sure the columns exist before dropping them
|
||||||
|
type Repository struct {
|
||||||
|
ID int64
|
||||||
|
DefaultWikiBranch string
|
||||||
|
}
|
||||||
|
if err := x.Sync(&Repository{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type Badge struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
Slug string
|
||||||
|
}
|
||||||
|
err := x.Sync(new(Badge))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
if err := sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := base.DropTableColumns(sess, "repository", "default_wiki_branch"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := base.DropTableColumns(sess, "badge", "slug"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
|
@ -3,16 +3,17 @@
|
||||||
|
|
||||||
package v1_22 //nolint
|
package v1_22 //nolint
|
||||||
|
|
||||||
import "xorm.io/xorm"
|
import (
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
func AddRepoArchiveDownloadCount(x *xorm.Engine) error {
|
"xorm.io/xorm"
|
||||||
type RepoArchiveDownloadCount struct {
|
)
|
||||||
ID int64 `xorm:"pk autoincr"`
|
|
||||||
RepoID int64 `xorm:"index unique(s)"`
|
func AddCreatedToIssue(x *xorm.Engine) error {
|
||||||
ReleaseID int64 `xorm:"index unique(s)"`
|
type Issue struct {
|
||||||
Type int `xorm:"unique(s)"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
Count int64
|
Created timeutil.TimeStampNano
|
||||||
}
|
}
|
||||||
|
|
||||||
return x.Sync(&RepoArchiveDownloadCount{})
|
return x.Sync(&Issue{})
|
||||||
}
|
}
|
||||||
|
|
18
models/forgejo_migrations/v1_22/v12.go
Normal file
18
models/forgejo_migrations/v1_22/v12.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_22 //nolint
|
||||||
|
|
||||||
|
import "xorm.io/xorm"
|
||||||
|
|
||||||
|
func AddRepoArchiveDownloadCount(x *xorm.Engine) error {
|
||||||
|
type RepoArchiveDownloadCount struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
RepoID int64 `xorm:"index unique(s)"`
|
||||||
|
ReleaseID int64 `xorm:"index unique(s)"`
|
||||||
|
Type int `xorm:"unique(s)"`
|
||||||
|
Count int64
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.Sync(&RepoArchiveDownloadCount{})
|
||||||
|
}
|
|
@ -301,6 +301,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
|
||||||
|
|
||||||
sess := db.GetEngine(ctx)
|
sess := db.GetEngine(ctx)
|
||||||
|
|
||||||
|
// check whether from branch exist
|
||||||
var branch Branch
|
var branch Branch
|
||||||
exist, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repo.ID, from).Get(&branch)
|
exist, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repo.ID, from).Get(&branch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -312,6 +313,24 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check whether to branch exist or is_deleted
|
||||||
|
var dstBranch Branch
|
||||||
|
exist, err = db.GetEngine(ctx).Where("repo_id=? AND name=?", repo.ID, to).Get(&dstBranch)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exist {
|
||||||
|
if !dstBranch.IsDeleted {
|
||||||
|
return ErrBranchAlreadyExists{
|
||||||
|
BranchName: to,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := db.GetEngine(ctx).ID(dstBranch.ID).NoAutoCondition().Delete(&dstBranch); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 1. update branch in database
|
// 1. update branch in database
|
||||||
if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{
|
if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{
|
||||||
Name: to,
|
Name: to,
|
||||||
|
@ -366,12 +385,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. do git action
|
// 5. insert renamed branch record
|
||||||
if err = gitAction(ctx, isDefault); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. insert renamed branch record
|
|
||||||
renamedBranch := &RenamedBranch{
|
renamedBranch := &RenamedBranch{
|
||||||
RepoID: repo.ID,
|
RepoID: repo.ID,
|
||||||
From: from,
|
From: from,
|
||||||
|
@ -382,6 +396,11 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 6. do git action
|
||||||
|
if err = gitAction(ctx, isDefault); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return committer.Commit()
|
return committer.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,15 +17,12 @@ import (
|
||||||
type BranchList []*Branch
|
type BranchList []*Branch
|
||||||
|
|
||||||
func (branches BranchList) LoadDeletedBy(ctx context.Context) error {
|
func (branches BranchList) LoadDeletedBy(ctx context.Context) error {
|
||||||
ids := container.Set[int64]{}
|
ids := container.FilterSlice(branches, func(branch *Branch) (int64, bool) {
|
||||||
for _, branch := range branches {
|
return branch.DeletedByID, branch.IsDeleted
|
||||||
if !branch.IsDeleted {
|
})
|
||||||
continue
|
|
||||||
}
|
|
||||||
ids.Add(branch.DeletedByID)
|
|
||||||
}
|
|
||||||
usersMap := make(map[int64]*user_model.User, len(ids))
|
usersMap := make(map[int64]*user_model.User, len(ids))
|
||||||
if err := db.GetEngine(ctx).In("id", ids.Values()).Find(&usersMap); err != nil {
|
if err := db.GetEngine(ctx).In("id", ids).Find(&usersMap); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, branch := range branches {
|
for _, branch := range branches {
|
||||||
|
@ -41,14 +38,13 @@ func (branches BranchList) LoadDeletedBy(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (branches BranchList) LoadPusher(ctx context.Context) error {
|
func (branches BranchList) LoadPusher(ctx context.Context) error {
|
||||||
ids := container.Set[int64]{}
|
ids := container.FilterSlice(branches, func(branch *Branch) (int64, bool) {
|
||||||
for _, branch := range branches {
|
// pusher_id maybe zero because some branches are sync by backend with no pusher
|
||||||
if branch.PusherID > 0 { // pusher_id maybe zero because some branches are sync by backend with no pusher
|
return branch.PusherID, branch.PusherID > 0
|
||||||
ids.Add(branch.PusherID)
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
usersMap := make(map[int64]*user_model.User, len(ids))
|
usersMap := make(map[int64]*user_model.User, len(ids))
|
||||||
if err := db.GetEngine(ctx).In("id", ids.Values()).Find(&usersMap); err != nil {
|
if err := db.GetEngine(ctx).In("id", ids).Find(&usersMap); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, branch := range branches {
|
for _, branch := range branches {
|
||||||
|
|
|
@ -257,30 +257,27 @@ func GetLatestCommitStatus(ctx context.Context, repoID int64, sha string, listOp
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLatestCommitStatusForPairs returns all statuses with a unique context for a given list of repo-sha pairs
|
// GetLatestCommitStatusForPairs returns all statuses with a unique context for a given list of repo-sha pairs
|
||||||
func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHAs map[int64]string, listOptions db.ListOptions) (map[int64][]*CommitStatus, error) {
|
func GetLatestCommitStatusForPairs(ctx context.Context, repoSHAs []RepoSHA) (map[int64][]*CommitStatus, error) {
|
||||||
type result struct {
|
type result struct {
|
||||||
Index int64
|
Index int64
|
||||||
RepoID int64
|
RepoID int64
|
||||||
|
SHA string
|
||||||
}
|
}
|
||||||
|
|
||||||
results := make([]result, 0, len(repoIDsToLatestCommitSHAs))
|
results := make([]result, 0, len(repoSHAs))
|
||||||
|
|
||||||
getBase := func() *xorm.Session {
|
getBase := func() *xorm.Session {
|
||||||
return db.GetEngine(ctx).Table(&CommitStatus{})
|
return db.GetEngine(ctx).Table(&CommitStatus{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a disjunction of conditions for each repoID and SHA pair
|
// Create a disjunction of conditions for each repoID and SHA pair
|
||||||
conds := make([]builder.Cond, 0, len(repoIDsToLatestCommitSHAs))
|
conds := make([]builder.Cond, 0, len(repoSHAs))
|
||||||
for repoID, sha := range repoIDsToLatestCommitSHAs {
|
for _, repoSHA := range repoSHAs {
|
||||||
conds = append(conds, builder.Eq{"repo_id": repoID, "sha": sha})
|
conds = append(conds, builder.Eq{"repo_id": repoSHA.RepoID, "sha": repoSHA.SHA})
|
||||||
}
|
}
|
||||||
sess := getBase().Where(builder.Or(conds...)).
|
sess := getBase().Where(builder.Or(conds...)).
|
||||||
Select("max( `index` ) as `index`, repo_id").
|
Select("max( `index` ) as `index`, repo_id, sha").
|
||||||
GroupBy("context_hash, repo_id").OrderBy("max( `index` ) desc")
|
GroupBy("context_hash, repo_id, sha").OrderBy("max( `index` ) desc")
|
||||||
|
|
||||||
if !listOptions.IsListAll() {
|
|
||||||
sess = db.SetSessionPagination(sess, &listOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := sess.Find(&results)
|
err := sess.Find(&results)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -297,7 +294,7 @@ func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHA
|
||||||
cond := builder.Eq{
|
cond := builder.Eq{
|
||||||
"`index`": result.Index,
|
"`index`": result.Index,
|
||||||
"repo_id": result.RepoID,
|
"repo_id": result.RepoID,
|
||||||
"sha": repoIDsToLatestCommitSHAs[result.RepoID],
|
"sha": result.SHA,
|
||||||
}
|
}
|
||||||
conds = append(conds, cond)
|
conds = append(conds, cond)
|
||||||
}
|
}
|
||||||
|
|
88
models/git/commit_status_summary.go
Normal file
88
models/git/commit_status_summary.go
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
// Copyright 2024 Gitea. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
|
||||||
|
"xorm.io/builder"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommitStatusSummary holds the latest commit Status of a single Commit
|
||||||
|
type CommitStatusSummary struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
RepoID int64 `xorm:"INDEX UNIQUE(repo_id_sha)"`
|
||||||
|
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_id_sha)"`
|
||||||
|
State api.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
|
||||||
|
TargetURL string `xorm:"TEXT"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
db.RegisterModel(new(CommitStatusSummary))
|
||||||
|
}
|
||||||
|
|
||||||
|
type RepoSHA struct {
|
||||||
|
RepoID int64
|
||||||
|
SHA string
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLatestCommitStatusForRepoAndSHAs(ctx context.Context, repoSHAs []RepoSHA) ([]*CommitStatus, error) {
|
||||||
|
cond := builder.NewCond()
|
||||||
|
for _, rs := range repoSHAs {
|
||||||
|
cond = cond.Or(builder.Eq{"repo_id": rs.RepoID, "sha": rs.SHA})
|
||||||
|
}
|
||||||
|
|
||||||
|
var summaries []CommitStatusSummary
|
||||||
|
if err := db.GetEngine(ctx).Where(cond).Find(&summaries); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
commitStatuses := make([]*CommitStatus, 0, len(repoSHAs))
|
||||||
|
for _, summary := range summaries {
|
||||||
|
commitStatuses = append(commitStatuses, &CommitStatus{
|
||||||
|
RepoID: summary.RepoID,
|
||||||
|
SHA: summary.SHA,
|
||||||
|
State: summary.State,
|
||||||
|
TargetURL: summary.TargetURL,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return commitStatuses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateCommitStatusSummary(ctx context.Context, repoID int64, sha string) error {
|
||||||
|
commitStatuses, _, err := GetLatestCommitStatus(ctx, repoID, sha, db.ListOptionsAll)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
state := CalcCommitStatus(commitStatuses)
|
||||||
|
// mysql will return 0 when update a record which state hasn't been changed which behaviour is different from other database,
|
||||||
|
// so we need to use insert in on duplicate
|
||||||
|
if setting.Database.Type.IsMySQL() {
|
||||||
|
_, err := db.GetEngine(ctx).Exec("INSERT INTO commit_status_summary (repo_id,sha,state,target_url) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE state=?",
|
||||||
|
repoID, sha, state.State, state.TargetURL, state.State)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cnt, err := db.GetEngine(ctx).Where("repo_id=? AND sha=?", repoID, sha).
|
||||||
|
Cols("state, target_url").
|
||||||
|
Update(&CommitStatusSummary{
|
||||||
|
State: state.State,
|
||||||
|
TargetURL: state.TargetURL,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
} else if cnt == 0 {
|
||||||
|
_, err = db.GetEngine(ctx).Insert(&CommitStatusSummary{
|
||||||
|
RepoID: repoID,
|
||||||
|
SHA: sha,
|
||||||
|
State: state.State,
|
||||||
|
TargetURL: state.TargetURL,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1289,10 +1289,9 @@ func InsertIssueComments(ctx context.Context, comments []*Comment) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
issueIDs := make(container.Set[int64])
|
issueIDs := container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
|
||||||
for _, comment := range comments {
|
return comment.IssueID, true
|
||||||
issueIDs.Add(comment.IssueID)
|
})
|
||||||
}
|
|
||||||
|
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1315,7 +1314,7 @@ func InsertIssueComments(ctx context.Context, comments []*Comment) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for issueID := range issueIDs {
|
for _, issueID := range issueIDs {
|
||||||
if _, err := db.Exec(ctx, "UPDATE issue set num_comments = (SELECT count(*) FROM comment WHERE issue_id = ? AND `type`=?) WHERE id = ?",
|
if _, err := db.Exec(ctx, "UPDATE issue set num_comments = (SELECT count(*) FROM comment WHERE issue_id = ? AND `type`=?) WHERE id = ?",
|
||||||
issueID, CommentTypeComment, issueID); err != nil {
|
issueID, CommentTypeComment, issueID); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -17,13 +17,9 @@ import (
|
||||||
type CommentList []*Comment
|
type CommentList []*Comment
|
||||||
|
|
||||||
func (comments CommentList) getPosterIDs() []int64 {
|
func (comments CommentList) getPosterIDs() []int64 {
|
||||||
posterIDs := make(container.Set[int64], len(comments))
|
return container.FilterSlice(comments, func(c *Comment) (int64, bool) {
|
||||||
for _, comment := range comments {
|
return c.PosterID, c.PosterID > 0
|
||||||
if comment.PosterID > 0 {
|
})
|
||||||
posterIDs.Add(comment.PosterID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return posterIDs.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadPosters loads posters
|
// LoadPosters loads posters
|
||||||
|
@ -44,13 +40,9 @@ func (comments CommentList) LoadPosters(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (comments CommentList) getLabelIDs() []int64 {
|
func (comments CommentList) getLabelIDs() []int64 {
|
||||||
ids := make(container.Set[int64], len(comments))
|
return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
|
||||||
for _, comment := range comments {
|
return comment.LabelID, comment.LabelID > 0
|
||||||
if comment.LabelID > 0 {
|
})
|
||||||
ids.Add(comment.LabelID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ids.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (comments CommentList) loadLabels(ctx context.Context) error {
|
func (comments CommentList) loadLabels(ctx context.Context) error {
|
||||||
|
@ -94,13 +86,9 @@ func (comments CommentList) loadLabels(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (comments CommentList) getMilestoneIDs() []int64 {
|
func (comments CommentList) getMilestoneIDs() []int64 {
|
||||||
ids := make(container.Set[int64], len(comments))
|
return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
|
||||||
for _, comment := range comments {
|
return comment.MilestoneID, comment.MilestoneID > 0
|
||||||
if comment.MilestoneID > 0 {
|
})
|
||||||
ids.Add(comment.MilestoneID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ids.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (comments CommentList) loadMilestones(ctx context.Context) error {
|
func (comments CommentList) loadMilestones(ctx context.Context) error {
|
||||||
|
@ -137,13 +125,9 @@ func (comments CommentList) loadMilestones(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (comments CommentList) getOldMilestoneIDs() []int64 {
|
func (comments CommentList) getOldMilestoneIDs() []int64 {
|
||||||
ids := make(container.Set[int64], len(comments))
|
return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
|
||||||
for _, comment := range comments {
|
return comment.OldMilestoneID, comment.OldMilestoneID > 0
|
||||||
if comment.OldMilestoneID > 0 {
|
})
|
||||||
ids.Add(comment.OldMilestoneID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ids.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (comments CommentList) loadOldMilestones(ctx context.Context) error {
|
func (comments CommentList) loadOldMilestones(ctx context.Context) error {
|
||||||
|
@ -180,13 +164,9 @@ func (comments CommentList) loadOldMilestones(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (comments CommentList) getAssigneeIDs() []int64 {
|
func (comments CommentList) getAssigneeIDs() []int64 {
|
||||||
ids := make(container.Set[int64], len(comments))
|
return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
|
||||||
for _, comment := range comments {
|
return comment.AssigneeID, comment.AssigneeID > 0
|
||||||
if comment.AssigneeID > 0 {
|
})
|
||||||
ids.Add(comment.AssigneeID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ids.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (comments CommentList) loadAssignees(ctx context.Context) error {
|
func (comments CommentList) loadAssignees(ctx context.Context) error {
|
||||||
|
@ -237,14 +217,9 @@ func (comments CommentList) loadAssignees(ctx context.Context) error {
|
||||||
|
|
||||||
// getIssueIDs returns all the issue ids on this comment list which issue hasn't been loaded
|
// getIssueIDs returns all the issue ids on this comment list which issue hasn't been loaded
|
||||||
func (comments CommentList) getIssueIDs() []int64 {
|
func (comments CommentList) getIssueIDs() []int64 {
|
||||||
ids := make(container.Set[int64], len(comments))
|
return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
|
||||||
for _, comment := range comments {
|
return comment.IssueID, comment.Issue == nil
|
||||||
if comment.Issue != nil {
|
})
|
||||||
continue
|
|
||||||
}
|
|
||||||
ids.Add(comment.IssueID)
|
|
||||||
}
|
|
||||||
return ids.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issues returns all the issues of comments
|
// Issues returns all the issues of comments
|
||||||
|
@ -311,16 +286,12 @@ func (comments CommentList) LoadIssues(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (comments CommentList) getDependentIssueIDs() []int64 {
|
func (comments CommentList) getDependentIssueIDs() []int64 {
|
||||||
ids := make(container.Set[int64], len(comments))
|
return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
|
||||||
for _, comment := range comments {
|
|
||||||
if comment.DependentIssue != nil {
|
if comment.DependentIssue != nil {
|
||||||
continue
|
return 0, false
|
||||||
}
|
}
|
||||||
if comment.DependentIssueID > 0 {
|
return comment.DependentIssueID, comment.DependentIssueID > 0
|
||||||
ids.Add(comment.DependentIssueID)
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
return ids.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (comments CommentList) loadDependentIssues(ctx context.Context) error {
|
func (comments CommentList) loadDependentIssues(ctx context.Context) error {
|
||||||
|
@ -375,13 +346,9 @@ func (comments CommentList) loadDependentIssues(ctx context.Context) error {
|
||||||
|
|
||||||
// getAttachmentCommentIDs only return the comment ids which possibly has attachments
|
// getAttachmentCommentIDs only return the comment ids which possibly has attachments
|
||||||
func (comments CommentList) getAttachmentCommentIDs() []int64 {
|
func (comments CommentList) getAttachmentCommentIDs() []int64 {
|
||||||
ids := make(container.Set[int64], len(comments))
|
return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
|
||||||
for _, comment := range comments {
|
return comment.ID, comment.Type.HasAttachmentSupport()
|
||||||
if comment.Type.HasAttachmentSupport() {
|
})
|
||||||
ids.Add(comment.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ids.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadAttachmentsByIssue loads attachments by issue id
|
// LoadAttachmentsByIssue loads attachments by issue id
|
||||||
|
@ -449,13 +416,9 @@ func (comments CommentList) LoadAttachments(ctx context.Context) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (comments CommentList) getReviewIDs() []int64 {
|
func (comments CommentList) getReviewIDs() []int64 {
|
||||||
ids := make(container.Set[int64], len(comments))
|
return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
|
||||||
for _, comment := range comments {
|
return comment.ReviewID, comment.ReviewID > 0
|
||||||
if comment.ReviewID > 0 {
|
})
|
||||||
ids.Add(comment.ReviewID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ids.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (comments CommentList) loadReviews(ctx context.Context) error {
|
func (comments CommentList) loadReviews(ctx context.Context) error {
|
||||||
|
|
|
@ -124,6 +124,8 @@ type Issue struct {
|
||||||
|
|
||||||
DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"`
|
DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"`
|
||||||
|
|
||||||
|
Created timeutil.TimeStampNano
|
||||||
|
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||||
ClosedUnix timeutil.TimeStamp `xorm:"INDEX"`
|
ClosedUnix timeutil.TimeStamp `xorm:"INDEX"`
|
||||||
|
|
|
@ -9,6 +9,14 @@ import (
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func GetMaxIssueIndexForRepo(ctx context.Context, repoID int64) (int64, error) {
|
||||||
|
var max int64
|
||||||
|
if _, err := db.GetEngine(ctx).Select("MAX(`index`)").Table("issue").Where("repo_id=?", repoID).Get(&max); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return max, nil
|
||||||
|
}
|
||||||
|
|
||||||
// RecalculateIssueIndexForRepo create issue_index for repo if not exist and
|
// RecalculateIssueIndexForRepo create issue_index for repo if not exist and
|
||||||
// update it based on highest index of existing issues assigned to a repo
|
// update it based on highest index of existing issues assigned to a repo
|
||||||
func RecalculateIssueIndexForRepo(ctx context.Context, repoID int64) error {
|
func RecalculateIssueIndexForRepo(ctx context.Context, repoID int64) error {
|
||||||
|
@ -18,8 +26,8 @@ func RecalculateIssueIndexForRepo(ctx context.Context, repoID int64) error {
|
||||||
}
|
}
|
||||||
defer committer.Close()
|
defer committer.Close()
|
||||||
|
|
||||||
var max int64
|
max, err := GetMaxIssueIndexForRepo(ctx, repoID)
|
||||||
if _, err = db.GetEngine(ctx).Select(" MAX(`index`)").Table("issue").Where("repo_id=?", repoID).Get(&max); err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
38
models/issues/issue_index_test.go
Normal file
38
models/issues/issue_index_test.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package issues_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetMaxIssueIndexForRepo(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
|
||||||
|
maxPR, err := issues_model.GetMaxIssueIndexForRepo(db.DefaultContext, repo.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
issue := testCreateIssue(t, repo.ID, repo.OwnerID, "title1", "content1", false)
|
||||||
|
assert.Greater(t, issue.Index, maxPR)
|
||||||
|
|
||||||
|
maxPR, err = issues_model.GetMaxIssueIndexForRepo(db.DefaultContext, repo.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
pull := testCreateIssue(t, repo.ID, repo.OwnerID, "title2", "content2", true)
|
||||||
|
assert.Greater(t, pull.Index, maxPR)
|
||||||
|
|
||||||
|
maxPR, err = issues_model.GetMaxIssueIndexForRepo(db.DefaultContext, repo.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, maxPR, pull.Index)
|
||||||
|
}
|
|
@ -74,11 +74,9 @@ func (issues IssueList) LoadRepositories(ctx context.Context) (repo_model.Reposi
|
||||||
}
|
}
|
||||||
|
|
||||||
func (issues IssueList) getPosterIDs() []int64 {
|
func (issues IssueList) getPosterIDs() []int64 {
|
||||||
posterIDs := make(container.Set[int64], len(issues))
|
return container.FilterSlice(issues, func(issue *Issue) (int64, bool) {
|
||||||
for _, issue := range issues {
|
return issue.PosterID, true
|
||||||
posterIDs.Add(issue.PosterID)
|
})
|
||||||
}
|
|
||||||
return posterIDs.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (issues IssueList) loadPosters(ctx context.Context) error {
|
func (issues IssueList) loadPosters(ctx context.Context) error {
|
||||||
|
@ -193,11 +191,9 @@ func (issues IssueList) loadLabels(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (issues IssueList) getMilestoneIDs() []int64 {
|
func (issues IssueList) getMilestoneIDs() []int64 {
|
||||||
ids := make(container.Set[int64], len(issues))
|
return container.FilterSlice(issues, func(issue *Issue) (int64, bool) {
|
||||||
for _, issue := range issues {
|
return issue.MilestoneID, true
|
||||||
ids.Add(issue.MilestoneID)
|
})
|
||||||
}
|
|
||||||
return ids.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (issues IssueList) loadMilestones(ctx context.Context) error {
|
func (issues IssueList) loadMilestones(ctx context.Context) error {
|
||||||
|
|
|
@ -325,6 +325,8 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
|
||||||
return fmt.Errorf("issue exist")
|
return fmt.Errorf("issue exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
opts.Issue.Created = timeutil.TimeStampNanoNow()
|
||||||
|
|
||||||
if _, err := e.Insert(opts.Issue); err != nil {
|
if _, err := e.Insert(opts.Issue); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,14 @@ func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullR
|
||||||
return sess, nil
|
return sess, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetUnmergedPullRequestsByHeadInfoMax(ctx context.Context, repoID, olderThan int64, branch string) ([]*PullRequest, error) {
|
||||||
|
prs := make([]*PullRequest, 0, 2)
|
||||||
|
sess := db.GetEngine(ctx).
|
||||||
|
Join("INNER", "issue", "issue.id = `pull_request`.issue_id").
|
||||||
|
Where("`pull_request`.head_repo_id = ? AND `pull_request`.head_branch = ? AND `pull_request`.has_merged = ? AND `issue`.is_closed = ? AND `pull_request`.flow = ? AND (`issue`.`created` IS NULL OR `issue`.`created` <= ?)", repoID, branch, false, false, PullRequestFlowGithub, olderThan)
|
||||||
|
return prs, sess.Find(&prs)
|
||||||
|
}
|
||||||
|
|
||||||
// GetUnmergedPullRequestsByHeadInfo returns all pull requests that are open and has not been merged
|
// GetUnmergedPullRequestsByHeadInfo returns all pull requests that are open and has not been merged
|
||||||
func GetUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch string) ([]*PullRequest, error) {
|
func GetUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch string) ([]*PullRequest, error) {
|
||||||
prs := make([]*PullRequest, 0, 2)
|
prs := make([]*PullRequest, 0, 2)
|
||||||
|
|
|
@ -4,7 +4,9 @@
|
||||||
package issues_test
|
package issues_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
|
@ -12,6 +14,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
@ -156,6 +159,100 @@ func TestGetUnmergedPullRequestsByHeadInfo(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetUnmergedPullRequestsByHeadInfoMax(t *testing.T) {
|
||||||
|
defer tests.AddFixtures("models/fixtures/TestGetUnmergedPullRequestsByHeadInfoMax/")()
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
repoID := int64(1)
|
||||||
|
olderThan := int64(0)
|
||||||
|
|
||||||
|
// for NULL created field the olderThan condition is ignored
|
||||||
|
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfoMax(db.DefaultContext, repoID, olderThan, "branch2")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(1), prs[0].HeadRepoID)
|
||||||
|
|
||||||
|
// test for when the created field is set
|
||||||
|
branch := "branchmax"
|
||||||
|
prs, err = issues_model.GetUnmergedPullRequestsByHeadInfoMax(db.DefaultContext, repoID, olderThan, branch)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, prs, 0)
|
||||||
|
olderThan = time.Now().UnixNano()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
prs, err = issues_model.GetUnmergedPullRequestsByHeadInfoMax(db.DefaultContext, repoID, olderThan, branch)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, prs, 1)
|
||||||
|
for _, pr := range prs {
|
||||||
|
assert.Equal(t, int64(1), pr.HeadRepoID)
|
||||||
|
assert.Equal(t, branch, pr.HeadBranch)
|
||||||
|
}
|
||||||
|
pr := prs[0]
|
||||||
|
|
||||||
|
for _, testCase := range []struct {
|
||||||
|
table string
|
||||||
|
field string
|
||||||
|
id int64
|
||||||
|
match any
|
||||||
|
nomatch any
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
table: "issue",
|
||||||
|
field: "is_closed",
|
||||||
|
id: pr.IssueID,
|
||||||
|
match: false,
|
||||||
|
nomatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
table: "pull_request",
|
||||||
|
field: "flow",
|
||||||
|
id: pr.ID,
|
||||||
|
match: issues_model.PullRequestFlowGithub,
|
||||||
|
nomatch: issues_model.PullRequestFlowAGit,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
table: "pull_request",
|
||||||
|
field: "head_repo_id",
|
||||||
|
id: pr.ID,
|
||||||
|
match: pr.HeadRepoID,
|
||||||
|
nomatch: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
table: "pull_request",
|
||||||
|
field: "head_branch",
|
||||||
|
id: pr.ID,
|
||||||
|
match: pr.HeadBranch,
|
||||||
|
nomatch: "something else",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
table: "pull_request",
|
||||||
|
field: "has_merged",
|
||||||
|
id: pr.ID,
|
||||||
|
match: false,
|
||||||
|
nomatch: true,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(testCase.field, func(t *testing.T) {
|
||||||
|
update := fmt.Sprintf("UPDATE `%s` SET `%s` = ? WHERE `id` = ?", testCase.table, testCase.field)
|
||||||
|
|
||||||
|
// expect no match
|
||||||
|
_, err = db.GetEngine(db.DefaultContext).Exec(update, testCase.nomatch, testCase.id)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
prs, err = issues_model.GetUnmergedPullRequestsByHeadInfoMax(db.DefaultContext, repoID, olderThan, branch)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, prs, 0)
|
||||||
|
|
||||||
|
// expect one match
|
||||||
|
_, err = db.GetEngine(db.DefaultContext).Exec(update, testCase.match, testCase.id)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
prs, err = issues_model.GetUnmergedPullRequestsByHeadInfoMax(db.DefaultContext, repoID, olderThan, branch)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, prs, 1)
|
||||||
|
|
||||||
|
// identical to the known PR
|
||||||
|
assert.Equal(t, pr.ID, prs[0].ID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetUnmergedPullRequestsByBaseInfo(t *testing.T) {
|
func TestGetUnmergedPullRequestsByBaseInfo(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
prs, err := issues_model.GetUnmergedPullRequestsByBaseInfo(db.DefaultContext, 1, "master")
|
prs, err := issues_model.GetUnmergedPullRequestsByBaseInfo(db.DefaultContext, 1, "master")
|
||||||
|
|
|
@ -305,14 +305,12 @@ func (list ReactionList) GroupByType() map[string]ReactionList {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list ReactionList) getUserIDs() []int64 {
|
func (list ReactionList) getUserIDs() []int64 {
|
||||||
userIDs := make(container.Set[int64], len(list))
|
return container.FilterSlice(list, func(reaction *Reaction) (int64, bool) {
|
||||||
for _, reaction := range list {
|
|
||||||
if reaction.OriginalAuthor != "" {
|
if reaction.OriginalAuthor != "" {
|
||||||
continue
|
return 0, false
|
||||||
}
|
}
|
||||||
userIDs.Add(reaction.UserID)
|
return reaction.UserID, true
|
||||||
}
|
})
|
||||||
return userIDs.Values()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func valuesUser(m map[int64]*user_model.User) []*user_model.User {
|
func valuesUser(m map[int64]*user_model.User) []*user_model.User {
|
||||||
|
|
|
@ -38,12 +38,11 @@ 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.FilterSlice(reviews, func(review *Review) (int64, bool) {
|
||||||
for i := 0; i < len(reviews); i++ {
|
return review.IssueID, true
|
||||||
issueIDs.Add(reviews[i].IssueID)
|
})
|
||||||
}
|
|
||||||
|
|
||||||
issues, err := GetIssuesByIDs(ctx, issueIDs.Values())
|
issues, err := GetIssuesByIDs(ctx, issueIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,8 +161,7 @@ func MainTest(m *testing.M) {
|
||||||
exitStatus := m.Run()
|
exitStatus := m.Run()
|
||||||
|
|
||||||
if err := testlogger.WriterCloser.Reset(); err != nil && exitStatus == 0 {
|
if err := testlogger.WriterCloser.Reset(); err != nil && exitStatus == 0 {
|
||||||
fmt.Printf("testlogger.WriterCloser.Reset: %v\n", err)
|
fmt.Printf("testlogger.WriterCloser.Reset: error ignored: %v\n", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
if err := removeAllWithRetry(setting.RepoRootPath); err != nil {
|
if err := removeAllWithRetry(setting.RepoRootPath); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err)
|
fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err)
|
||||||
|
|
|
@ -578,7 +578,12 @@ var migrations = []Migration{
|
||||||
|
|
||||||
// Gitea 1.22.0 ends at 294
|
// Gitea 1.22.0 ends at 294
|
||||||
|
|
||||||
|
// v294 -> v295
|
||||||
NewMigration("Add unique index for project issue table", v1_23.AddUniqueIndexForProjectIssue),
|
NewMigration("Add unique index for project issue table", v1_23.AddUniqueIndexForProjectIssue),
|
||||||
|
// v295 -> v296
|
||||||
|
NewMigration("Add commit status summary table", v1_23.AddCommitStatusSummary),
|
||||||
|
// v296 -> v297
|
||||||
|
NewMigration("Add missing field of commit status summary table", v1_23.AddCommitStatusSummary2),
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentDBVersion returns the current db version
|
// GetCurrentDBVersion returns the current db version
|
||||||
|
@ -655,15 +660,15 @@ func Migrate(x *xorm.Engine) error {
|
||||||
|
|
||||||
v := currentVersion.Version
|
v := currentVersion.Version
|
||||||
if minDBVersion > v {
|
if minDBVersion > v {
|
||||||
log.Fatal(`Gitea no longer supports auto-migration from your previously installed version.
|
log.Fatal(`Forgejo no longer supports auto-migration from your previously installed version.
|
||||||
Please try upgrading to a lower version first (suggested v1.6.4), then upgrade to this version.`)
|
Please try upgrading to a lower version first (suggested v1.6.4), then upgrade to this version.`)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Downgrading Gitea's database version not supported
|
// Downgrading Forgejo database version is not supported
|
||||||
if int(v-minDBVersion) > len(migrations) {
|
if int(v-minDBVersion) > len(migrations) {
|
||||||
msg := fmt.Sprintf("Your database (migration version: %d) is for a newer Gitea, you can not use the newer database for this old Gitea release (%d).", v, minDBVersion+len(migrations))
|
msg := fmt.Sprintf("Your database (migration version: %d) is for a newer Forgejo, you can not use the newer database for this old Forgejo release (%d).", v, minDBVersion+len(migrations))
|
||||||
msg += "\nGitea will exit to keep your database safe and unchanged. Please use the correct Gitea release, do not change the migration version manually (incorrect manual operation may lose data)."
|
msg += "\nForgejo will exit to keep your database safe and unchanged. Please use the correct Forgejo release, do not change the migration version manually (incorrect manual operation may lose data)."
|
||||||
if !setting.IsProd {
|
if !setting.IsProd {
|
||||||
msg += fmt.Sprintf("\nIf you are in development and really know what you're doing, you can force changing the migration version by executing: UPDATE version SET version=%d WHERE id=1;", minDBVersion+len(migrations))
|
msg += fmt.Sprintf("\nIf you are in development and really know what you're doing, you can force changing the migration version by executing: UPDATE version SET version=%d WHERE id=1;", minDBVersion+len(migrations))
|
||||||
}
|
}
|
||||||
|
|
18
models/migrations/v1_23/v295.go
Normal file
18
models/migrations/v1_23/v295.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_23 //nolint
|
||||||
|
|
||||||
|
import "xorm.io/xorm"
|
||||||
|
|
||||||
|
func AddCommitStatusSummary(x *xorm.Engine) error {
|
||||||
|
type CommitStatusSummary struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
RepoID int64 `xorm:"INDEX UNIQUE(repo_id_sha)"`
|
||||||
|
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_id_sha)"`
|
||||||
|
State string `xorm:"VARCHAR(7) NOT NULL"`
|
||||||
|
}
|
||||||
|
// there is no migrations because if there is no data on this table, it will fall back to get data
|
||||||
|
// from commit status
|
||||||
|
return x.Sync2(new(CommitStatusSummary))
|
||||||
|
}
|
16
models/migrations/v1_23/v296.go
Normal file
16
models/migrations/v1_23/v296.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_23 //nolint
|
||||||
|
|
||||||
|
import "xorm.io/xorm"
|
||||||
|
|
||||||
|
func AddCommitStatusSummary2(x *xorm.Engine) error {
|
||||||
|
type CommitStatusSummary struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
TargetURL string `xorm:"TEXT"`
|
||||||
|
}
|
||||||
|
// there is no migrations because if there is no data on this table, it will fall back to get data
|
||||||
|
// from commit status
|
||||||
|
return x.Sync(new(CommitStatusSummary))
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
@ -401,6 +402,8 @@ func DeleteOrganization(ctx context.Context, org *Organization) error {
|
||||||
&TeamUnit{OrgID: org.ID},
|
&TeamUnit{OrgID: org.ID},
|
||||||
&TeamInvite{OrgID: org.ID},
|
&TeamInvite{OrgID: org.ID},
|
||||||
&secret_model.Secret{OwnerID: org.ID},
|
&secret_model.Secret{OwnerID: org.ID},
|
||||||
|
&actions_model.ActionRunner{OwnerID: org.ID},
|
||||||
|
&actions_model.ActionRunnerToken{OwnerID: org.ID},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("DeleteBeans: %w", err)
|
return fmt.Errorf("DeleteBeans: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -287,9 +287,10 @@ func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
|
||||||
// SearchVersions gets all versions of packages matching the search options
|
// SearchVersions gets all versions of packages matching the search options
|
||||||
func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
|
func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
|
||||||
sess := db.GetEngine(ctx).
|
sess := db.GetEngine(ctx).
|
||||||
Where(opts.ToConds()).
|
Select("package_version.*").
|
||||||
Table("package_version").
|
Table("package_version").
|
||||||
Join("INNER", "package", "package.id = package_version.package_id")
|
Join("INNER", "package", "package.id = package_version.package_id").
|
||||||
|
Where(opts.ToConds())
|
||||||
|
|
||||||
opts.configureOrderBy(sess)
|
opts.configureOrderBy(sess)
|
||||||
|
|
||||||
|
@ -304,19 +305,18 @@ func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*Package
|
||||||
|
|
||||||
// SearchLatestVersions gets the latest version of every package matching the search options
|
// SearchLatestVersions gets the latest version of every package matching the search options
|
||||||
func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
|
func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
|
||||||
cond := opts.ToConds().
|
in := builder.
|
||||||
And(builder.Expr("pv2.id IS NULL"))
|
Select("MAX(package_version.id)").
|
||||||
|
From("package_version").
|
||||||
joinCond := builder.Expr("package_version.package_id = pv2.package_id AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))")
|
InnerJoin("package", "package.id = package_version.package_id").
|
||||||
if opts.IsInternal.Has() {
|
Where(opts.ToConds()).
|
||||||
joinCond = joinCond.And(builder.Eq{"pv2.is_internal": opts.IsInternal.Value()})
|
GroupBy("package_version.package_id")
|
||||||
}
|
|
||||||
|
|
||||||
sess := db.GetEngine(ctx).
|
sess := db.GetEngine(ctx).
|
||||||
|
Select("package_version.*").
|
||||||
Table("package_version").
|
Table("package_version").
|
||||||
Join("LEFT", "package_version pv2", joinCond).
|
|
||||||
Join("INNER", "package", "package.id = package_version.package_id").
|
Join("INNER", "package", "package.id = package_version.package_id").
|
||||||
Where(cond)
|
Where(builder.In("package_version.id", in))
|
||||||
|
|
||||||
opts.configureOrderBy(sess)
|
opts.configureOrderBy(sess)
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ func (repo *Repository) IsDependenciesEnabled(ctx context.Context) bool {
|
||||||
var u *RepoUnit
|
var u *RepoUnit
|
||||||
var err error
|
var err error
|
||||||
if u, err = repo.GetUnit(ctx, unit.TypeIssues); err != nil {
|
if u, err = repo.GetUnit(ctx, unit.TypeIssues); err != nil {
|
||||||
log.Trace("%s", err)
|
log.Trace("IsDependenciesEnabled: %v", err)
|
||||||
return setting.Service.DefaultEnableDependencies
|
return setting.Service.DefaultEnableDependencies
|
||||||
}
|
}
|
||||||
return u.IssuesConfig().EnableDependencies
|
return u.IssuesConfig().EnableDependencies
|
||||||
|
|
|
@ -78,6 +78,7 @@ type Release struct {
|
||||||
TargetBehind string `xorm:"-"` // to handle non-existing or empty target
|
TargetBehind string `xorm:"-"` // to handle non-existing or empty target
|
||||||
Title string
|
Title string
|
||||||
Sha1 string `xorm:"VARCHAR(64)"`
|
Sha1 string `xorm:"VARCHAR(64)"`
|
||||||
|
HideArchiveLinks bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
NumCommits int64
|
NumCommits int64
|
||||||
NumCommitsBehind int64 `xorm:"-"`
|
NumCommitsBehind int64 `xorm:"-"`
|
||||||
Note string `xorm:"TEXT"`
|
Note string `xorm:"TEXT"`
|
||||||
|
|
|
@ -104,18 +104,19 @@ func (repos RepositoryList) LoadAttributes(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
set := make(container.Set[int64])
|
userIDs := container.FilterSlice(repos, func(repo *Repository) (int64, bool) {
|
||||||
|
return repo.OwnerID, true
|
||||||
|
})
|
||||||
repoIDs := make([]int64, len(repos))
|
repoIDs := make([]int64, len(repos))
|
||||||
for i := range repos {
|
for i := range repos {
|
||||||
set.Add(repos[i].OwnerID)
|
|
||||||
repoIDs[i] = repos[i].ID
|
repoIDs[i] = repos[i].ID
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load owners.
|
// Load owners.
|
||||||
users := make(map[int64]*user_model.User, len(set))
|
users := make(map[int64]*user_model.User, len(userIDs))
|
||||||
if err := db.GetEngine(ctx).
|
if err := db.GetEngine(ctx).
|
||||||
Where("id > 0").
|
Where("id > 0").
|
||||||
In("id", set.Values()).
|
In("id", userIDs).
|
||||||
Find(&users); err != nil {
|
Find(&users); err != nil {
|
||||||
return fmt.Errorf("find users: %w", err)
|
return fmt.Errorf("find users: %w", err)
|
||||||
}
|
}
|
||||||
|
|
36
models/user/fixtures/user.yml
Normal file
36
models/user/fixtures/user.yml
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
-
|
||||||
|
id: 1041
|
||||||
|
lower_name: remote01
|
||||||
|
name: remote01
|
||||||
|
full_name: Remote01
|
||||||
|
email: remote01@example.com
|
||||||
|
keep_email_private: false
|
||||||
|
email_notifications_preference: onmention
|
||||||
|
passwd: ZogKvWdyEx:password
|
||||||
|
passwd_hash_algo: dummy
|
||||||
|
must_change_password: false
|
||||||
|
login_source: 1001
|
||||||
|
login_name: 123
|
||||||
|
type: 5
|
||||||
|
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: true
|
||||||
|
avatar: avatarremote01
|
||||||
|
avatar_email: avatarremote01@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
|
|
@ -45,7 +45,11 @@ type SearchUserOptions struct {
|
||||||
|
|
||||||
func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Session {
|
func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Session {
|
||||||
var cond builder.Cond
|
var cond builder.Cond
|
||||||
cond = builder.Eq{"type": opts.Type}
|
if opts.Type == UserTypeIndividual {
|
||||||
|
cond = builder.In("type", UserTypeIndividual, UserTypeRemoteUser)
|
||||||
|
} else {
|
||||||
|
cond = builder.Eq{"type": opts.Type}
|
||||||
|
}
|
||||||
if opts.IncludeReserved {
|
if opts.IncludeReserved {
|
||||||
if opts.Type == UserTypeIndividual {
|
if opts.Type == UserTypeIndividual {
|
||||||
cond = cond.Or(builder.Eq{"type": UserTypeUserReserved}).Or(
|
cond = cond.Or(builder.Eq{"type": UserTypeUserReserved}).Or(
|
||||||
|
@ -140,7 +144,7 @@ func SearchUsers(ctx context.Context, opts *SearchUserOptions) (users []*User, _
|
||||||
|
|
||||||
sessQuery := opts.toSearchQueryBase(ctx).OrderBy(opts.OrderBy.String())
|
sessQuery := opts.toSearchQueryBase(ctx).OrderBy(opts.OrderBy.String())
|
||||||
defer sessQuery.Close()
|
defer sessQuery.Close()
|
||||||
if opts.Page != 0 {
|
if opts.PageSize > 0 {
|
||||||
sessQuery = db.SetSessionPagination(sessQuery, opts)
|
sessQuery = db.SetSessionPagination(sessQuery, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -220,7 +220,7 @@ func (u *User) GetEmail() string {
|
||||||
// GetAllUsers returns a slice of all individual users found in DB.
|
// GetAllUsers returns a slice of all individual users found in DB.
|
||||||
func GetAllUsers(ctx context.Context) ([]*User, error) {
|
func GetAllUsers(ctx context.Context) ([]*User, error) {
|
||||||
users := make([]*User, 0)
|
users := make([]*User, 0)
|
||||||
return users, db.GetEngine(ctx).OrderBy("id").Where("type = ?", UserTypeIndividual).Find(&users)
|
return users, db.GetEngine(ctx).OrderBy("id").In("type", UserTypeIndividual, UserTypeRemoteUser).Find(&users)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllAdmins returns a slice of all adminusers found in DB.
|
// GetAllAdmins returns a slice of all adminusers found in DB.
|
||||||
|
@ -425,6 +425,10 @@ func (u *User) IsBot() bool {
|
||||||
return u.Type == UserTypeBot
|
return u.Type == UserTypeBot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) IsRemote() bool {
|
||||||
|
return u.Type == UserTypeRemoteUser
|
||||||
|
}
|
||||||
|
|
||||||
// DisplayName returns full name if it's not empty,
|
// DisplayName returns full name if it's not empty,
|
||||||
// returns username otherwise.
|
// returns username otherwise.
|
||||||
func (u *User) DisplayName() string {
|
func (u *User) DisplayName() string {
|
||||||
|
@ -938,7 +942,8 @@ func GetUserByName(ctx context.Context, name string) (*User, error) {
|
||||||
if len(name) == 0 {
|
if len(name) == 0 {
|
||||||
return nil, ErrUserNotExist{Name: name}
|
return nil, ErrUserNotExist{Name: name}
|
||||||
}
|
}
|
||||||
u := &User{LowerName: strings.ToLower(name), Type: UserTypeIndividual}
|
// adding Type: UserTypeIndividual is a noop because it is zero and discarded
|
||||||
|
u := &User{LowerName: strings.ToLower(name)}
|
||||||
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
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"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"
|
||||||
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
@ -34,6 +35,35 @@ func TestOAuth2Application_LoadUser(t *testing.T) {
|
||||||
assert.NotNil(t, user)
|
assert.NotNil(t, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetUserByName(t *testing.T) {
|
||||||
|
defer tests.AddFixtures("models/user/fixtures/")()
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
{
|
||||||
|
_, err := user_model.GetUserByName(db.DefaultContext, "")
|
||||||
|
assert.True(t, user_model.IsErrUserNotExist(err), err)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
_, err := user_model.GetUserByName(db.DefaultContext, "UNKNOWN")
|
||||||
|
assert.True(t, user_model.IsErrUserNotExist(err), err)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
user, err := user_model.GetUserByName(db.DefaultContext, "USER2")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, user.Name, "user2")
|
||||||
|
}
|
||||||
|
{
|
||||||
|
user, err := user_model.GetUserByName(db.DefaultContext, "org3")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, user.Name, "org3")
|
||||||
|
}
|
||||||
|
{
|
||||||
|
user, err := user_model.GetUserByName(db.DefaultContext, "remote01")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, user.Name, "remote01")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetUserEmailsByNames(t *testing.T) {
|
func TestGetUserEmailsByNames(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
@ -62,6 +92,22 @@ func TestCanCreateOrganization(t *testing.T) {
|
||||||
assert.False(t, user.CanCreateOrganization())
|
assert.False(t, user.CanCreateOrganization())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetAllUsers(t *testing.T) {
|
||||||
|
defer tests.AddFixtures("models/user/fixtures/")()
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
users, err := user_model.GetAllUsers(db.DefaultContext)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
found := make(map[user_model.UserType]bool, 0)
|
||||||
|
for _, user := range users {
|
||||||
|
found[user.Type] = true
|
||||||
|
}
|
||||||
|
assert.True(t, found[user_model.UserTypeIndividual], users)
|
||||||
|
assert.True(t, found[user_model.UserTypeRemoteUser], users)
|
||||||
|
assert.False(t, found[user_model.UserTypeOrganization], users)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPAPIURL(t *testing.T) {
|
func TestAPAPIURL(t *testing.T) {
|
||||||
user := user_model.User{ID: 1}
|
user := user_model.User{ID: 1}
|
||||||
url := user.APAPIURL()
|
url := user.APAPIURL()
|
||||||
|
@ -72,6 +118,7 @@ func TestAPAPIURL(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearchUsers(t *testing.T) {
|
func TestSearchUsers(t *testing.T) {
|
||||||
|
defer tests.AddFixtures("models/user/fixtures/")()
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
testSuccess := func(opts *user_model.SearchUserOptions, expectedUserOrOrgIDs []int64) {
|
testSuccess := func(opts *user_model.SearchUserOptions, expectedUserOrOrgIDs []int64) {
|
||||||
users, _, err := user_model.SearchUsers(db.DefaultContext, opts)
|
users, _, err := user_model.SearchUsers(db.DefaultContext, opts)
|
||||||
|
@ -112,13 +159,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, 38, 39, 40})
|
[]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, 1041})
|
||||||
|
|
||||||
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(false)},
|
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(false)},
|
||||||
[]int64{9})
|
[]int64{9})
|
||||||
|
|
||||||
testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
|
testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
|
||||||
[]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})
|
[]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, 1041})
|
||||||
|
|
||||||
testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
|
testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
|
||||||
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
|
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
|
||||||
|
@ -134,7 +181,7 @@ func TestSearchUsers(t *testing.T) {
|
||||||
[]int64{29})
|
[]int64{29})
|
||||||
|
|
||||||
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsProhibitLogin: optional.Some(true)},
|
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsProhibitLogin: optional.Some(true)},
|
||||||
[]int64{37})
|
[]int64{1041, 37})
|
||||||
|
|
||||||
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsTwoFactorEnabled: optional.Some(true)},
|
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsTwoFactorEnabled: optional.Some(true)},
|
||||||
[]int64{24})
|
[]int64{24})
|
||||||
|
|
|
@ -361,6 +361,15 @@ func (w Webhook) HeaderAuthorization() (string, error) {
|
||||||
return secret.DecryptSecret(setting.SecretKey, w.HeaderAuthorizationEncrypted)
|
return secret.DecryptSecret(setting.SecretKey, w.HeaderAuthorizationEncrypted)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HeaderAuthorizationTrimPrefix returns the decrypted Authorization with a specified prefix trimmed.
|
||||||
|
func (w Webhook) HeaderAuthorizationTrimPrefix(prefix string) (string, error) {
|
||||||
|
s, err := w.HeaderAuthorization()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return strings.TrimPrefix(s, prefix), nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetHeaderAuthorization encrypts and sets the Authorization header.
|
// SetHeaderAuthorization encrypts and sets the Authorization header.
|
||||||
func (w *Webhook) SetHeaderAuthorization(cleartext string) error {
|
func (w *Webhook) SetHeaderAuthorization(cleartext string) error {
|
||||||
if cleartext == "" {
|
if cleartext == "" {
|
||||||
|
|
|
@ -8,15 +8,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetDefaultWebhooks returns all admin-default webhooks.
|
// GetDefaultWebhooks returns all admin-default webhooks.
|
||||||
func GetDefaultWebhooks(ctx context.Context) ([]*Webhook, error) {
|
func GetDefaultWebhooks(ctx context.Context) ([]*Webhook, error) {
|
||||||
webhooks := make([]*Webhook, 0, 5)
|
return getAdminWebhooks(ctx, false)
|
||||||
return webhooks, db.GetEngine(ctx).
|
|
||||||
Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, false).
|
|
||||||
Find(&webhooks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSystemOrDefaultWebhook returns admin system or default webhook by given ID.
|
// GetSystemOrDefaultWebhook returns admin system or default webhook by given ID.
|
||||||
|
@ -34,15 +30,21 @@ func GetSystemOrDefaultWebhook(ctx context.Context, id int64) (*Webhook, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSystemWebhooks returns all admin system webhooks.
|
// GetSystemWebhooks returns all admin system webhooks.
|
||||||
func GetSystemWebhooks(ctx context.Context, isActive optional.Option[bool]) ([]*Webhook, error) {
|
func GetSystemWebhooks(ctx context.Context, onlyActive bool) ([]*Webhook, error) {
|
||||||
|
return getAdminWebhooks(ctx, true, onlyActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAdminWebhooks(ctx context.Context, systemWebhooks bool, onlyActive ...bool) ([]*Webhook, error) {
|
||||||
webhooks := make([]*Webhook, 0, 5)
|
webhooks := make([]*Webhook, 0, 5)
|
||||||
if !isActive.Has() {
|
if len(onlyActive) > 0 && onlyActive[0] {
|
||||||
return webhooks, db.GetEngine(ctx).
|
return webhooks, db.GetEngine(ctx).
|
||||||
Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, true).
|
Where("repo_id=? AND owner_id=? AND is_system_webhook=? AND is_active = ?", 0, 0, systemWebhooks, true).
|
||||||
|
OrderBy("id").
|
||||||
Find(&webhooks)
|
Find(&webhooks)
|
||||||
}
|
}
|
||||||
return webhooks, db.GetEngine(ctx).
|
return webhooks, db.GetEngine(ctx).
|
||||||
Where("repo_id=? AND owner_id=? AND is_system_webhook=? AND is_active = ?", 0, 0, true, isActive.Value()).
|
Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, systemWebhooks).
|
||||||
|
OrderBy("id").
|
||||||
Find(&webhooks)
|
Find(&webhooks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -130,7 +130,7 @@ then resh (ר), and finally heh (ה) (which should appear leftmost).`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// UTF-8/16/32 all use the same codepoint for BOM
|
// UTF-8/16/32 all use the same codepoint for BOM
|
||||||
// Gitea could read UTF-16/32 content and convert into UTF-8 internally then render it, so we only process UTF-8 internally
|
// Forgejo could read UTF-16/32 content and convert into UTF-8 internally then render it, so we only process UTF-8 internally
|
||||||
name: "UTF BOM",
|
name: "UTF BOM",
|
||||||
text: "\xef\xbb\xbftest",
|
text: "\xef\xbb\xbftest",
|
||||||
result: "\xef\xbb\xbftest",
|
result: "\xef\xbb\xbftest",
|
||||||
|
|
21
modules/container/filter.go
Normal file
21
modules/container/filter.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package container
|
||||||
|
|
||||||
|
import "slices"
|
||||||
|
|
||||||
|
// FilterSlice ranges over the slice and calls include() for each element.
|
||||||
|
// If the second returned value is true, the first returned value will be included in the resulting
|
||||||
|
// slice (after deduplication).
|
||||||
|
func FilterSlice[E any, T comparable](s []E, include func(E) (T, bool)) []T {
|
||||||
|
filtered := make([]T, 0, len(s)) // slice will be clipped before returning
|
||||||
|
seen := make(map[T]bool, len(s))
|
||||||
|
for i := range s {
|
||||||
|
if v, ok := include(s[i]); ok && !seen[v] {
|
||||||
|
filtered = append(filtered, v)
|
||||||
|
seen[v] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return slices.Clip(filtered)
|
||||||
|
}
|
28
modules/container/filter_test.go
Normal file
28
modules/container/filter_test.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package container
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFilterMapUnique(t *testing.T) {
|
||||||
|
result := FilterSlice([]int{
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||||
|
}, func(i int) (int, bool) {
|
||||||
|
switch i {
|
||||||
|
case 0:
|
||||||
|
return 0, true // included later
|
||||||
|
case 1:
|
||||||
|
return 0, true // duplicate of previous (should be ignored)
|
||||||
|
case 2:
|
||||||
|
return 2, false // not included
|
||||||
|
default:
|
||||||
|
return i, true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert.Equal(t, []int{0, 3, 4, 5, 6, 7, 8, 9}, result)
|
||||||
|
}
|
|
@ -462,7 +462,7 @@ func parseCommitFileStatus(fileStatus *CommitFileStatus, stdout io.Reader) {
|
||||||
_, _ = rd.Discard(1)
|
_, _ = rd.Discard(1)
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
modifier, err := rd.ReadSlice('\x00')
|
modifier, err := rd.ReadString('\x00')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != io.EOF {
|
if err != io.EOF {
|
||||||
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
|
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
|
||||||
|
|
|
@ -159,22 +159,6 @@ func BenchmarkCutDiffAroundLine(b *testing.B) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleCutDiffAroundLine() {
|
|
||||||
const diff = `diff --git a/README.md b/README.md
|
|
||||||
--- a/README.md
|
|
||||||
+++ b/README.md
|
|
||||||
@@ -1,3 +1,6 @@
|
|
||||||
# gitea-github-migrator
|
|
||||||
+
|
|
||||||
+ Build Status
|
|
||||||
- Latest Release
|
|
||||||
Docker Pulls
|
|
||||||
+ cut off
|
|
||||||
+ cut off`
|
|
||||||
result, _ := CutDiffAroundLine(strings.NewReader(diff), 4, false, 3)
|
|
||||||
println(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseDiffHunkString(t *testing.T) {
|
func TestParseDiffHunkString(t *testing.T) {
|
||||||
leftLine, leftHunk, rightLine, rightHunk := ParseDiffHunkString("@@ -19,3 +19,5 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER")
|
leftLine, leftHunk, rightLine, rightHunk := ParseDiffHunkString("@@ -19,3 +19,5 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER")
|
||||||
assert.EqualValues(t, 19, leftLine)
|
assert.EqualValues(t, 19, leftLine)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -80,10 +81,21 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
|
||||||
defer stdoutReader.Close()
|
defer stdoutReader.Close()
|
||||||
|
|
||||||
isInBlock := false
|
isInBlock := false
|
||||||
scanner := bufio.NewScanner(stdoutReader)
|
scanner := bufio.NewReader(stdoutReader)
|
||||||
var res *GrepResult
|
var res *GrepResult
|
||||||
for scanner.Scan() {
|
for {
|
||||||
line := scanner.Text()
|
line, err := scanner.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Remove delimiter.
|
||||||
|
if len(line) > 0 {
|
||||||
|
line = line[:len(line)-1]
|
||||||
|
}
|
||||||
|
|
||||||
if !isInBlock {
|
if !isInBlock {
|
||||||
if _ /* ref */, filename, ok := strings.Cut(line, ":"); ok {
|
if _ /* ref */, filename, ok := strings.Cut(line, ":"); ok {
|
||||||
isInBlock = true
|
isInBlock = true
|
||||||
|
@ -109,7 +121,7 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
|
||||||
res.LineCodes = append(res.LineCodes, lineCode)
|
res.LineCodes = append(res.LineCodes, lineCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return scanner.Err()
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
// git grep exits by cancel (killed), usually it is caused by the limit of results
|
// git grep exits by cancel (killed), usually it is caused by the limit of results
|
||||||
|
|
|
@ -4,7 +4,10 @@
|
||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -49,3 +52,27 @@ func TestGrepSearch(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Len(t, res, 0)
|
assert.Len(t, res, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGrepLongFiles(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
err := InitRepository(DefaultContext, tmpDir, false, Sha1ObjectFormat.Name())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
gitRepo, err := openRepositoryWithDefaultContext(tmpDir)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
assert.NoError(t, os.WriteFile(path.Join(tmpDir, "README.md"), bytes.Repeat([]byte{'a'}, 65*1024), 0o666))
|
||||||
|
|
||||||
|
err = AddChanges(tmpDir, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = CommitChanges(tmpDir, CommitChangesOptions{Message: "Long file"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
res, err := GrepSearch(context.Background(), gitRepo, "a", GrepOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, res, 1)
|
||||||
|
assert.Len(t, res[0].LineCodes[0], 65*1024)
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ package git
|
||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"hash"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
@ -33,6 +34,15 @@ type ObjectFormat interface {
|
||||||
ComputeHash(t ObjectType, content []byte) ObjectID
|
ComputeHash(t ObjectType, content []byte) ObjectID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func computeHash(dst []byte, hasher hash.Hash, t ObjectType, content []byte) []byte {
|
||||||
|
_, _ = hasher.Write(t.Bytes())
|
||||||
|
_, _ = hasher.Write([]byte(" "))
|
||||||
|
_, _ = hasher.Write([]byte(strconv.Itoa(len(content))))
|
||||||
|
_, _ = hasher.Write([]byte{0})
|
||||||
|
_, _ = hasher.Write(content)
|
||||||
|
return hasher.Sum(dst)
|
||||||
|
}
|
||||||
|
|
||||||
/* SHA1 Type */
|
/* SHA1 Type */
|
||||||
type Sha1ObjectFormatImpl struct{}
|
type Sha1ObjectFormatImpl struct{}
|
||||||
|
|
||||||
|
@ -65,16 +75,9 @@ func (Sha1ObjectFormatImpl) MustID(b []byte) ObjectID {
|
||||||
|
|
||||||
// ComputeHash compute the hash for a given ObjectType and content
|
// ComputeHash compute the hash for a given ObjectType and content
|
||||||
func (h Sha1ObjectFormatImpl) ComputeHash(t ObjectType, content []byte) ObjectID {
|
func (h Sha1ObjectFormatImpl) ComputeHash(t ObjectType, content []byte) ObjectID {
|
||||||
hasher := sha1.New()
|
var obj Sha1Hash
|
||||||
_, _ = hasher.Write(t.Bytes())
|
computeHash(obj[:0], sha1.New(), t, content)
|
||||||
_, _ = hasher.Write([]byte(" "))
|
return &obj
|
||||||
_, _ = hasher.Write([]byte(strconv.FormatInt(int64(len(content)), 10)))
|
|
||||||
_, _ = hasher.Write([]byte{0})
|
|
||||||
|
|
||||||
// HashSum generates a SHA1 for the provided hash
|
|
||||||
var sha1 Sha1Hash
|
|
||||||
copy(sha1[:], hasher.Sum(nil))
|
|
||||||
return &sha1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SHA256 Type */
|
/* SHA256 Type */
|
||||||
|
@ -111,16 +114,9 @@ func (Sha256ObjectFormatImpl) MustID(b []byte) ObjectID {
|
||||||
|
|
||||||
// ComputeHash compute the hash for a given ObjectType and content
|
// ComputeHash compute the hash for a given ObjectType and content
|
||||||
func (h Sha256ObjectFormatImpl) ComputeHash(t ObjectType, content []byte) ObjectID {
|
func (h Sha256ObjectFormatImpl) ComputeHash(t ObjectType, content []byte) ObjectID {
|
||||||
hasher := sha256.New()
|
var obj Sha256Hash
|
||||||
_, _ = hasher.Write(t.Bytes())
|
computeHash(obj[:0], sha256.New(), t, content)
|
||||||
_, _ = hasher.Write([]byte(" "))
|
return &obj
|
||||||
_, _ = hasher.Write([]byte(strconv.FormatInt(int64(len(content)), 10)))
|
|
||||||
_, _ = hasher.Write([]byte{0})
|
|
||||||
|
|
||||||
// HashSum generates a SHA256 for the provided hash
|
|
||||||
var sha256 Sha1Hash
|
|
||||||
copy(sha256[:], hasher.Sum(nil))
|
|
||||||
return &sha256
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -18,4 +18,8 @@ func TestIsValidSHAPattern(t *testing.T) {
|
||||||
assert.False(t, h.IsValid("abc"))
|
assert.False(t, h.IsValid("abc"))
|
||||||
assert.False(t, h.IsValid("123g"))
|
assert.False(t, h.IsValid("123g"))
|
||||||
assert.False(t, h.IsValid("some random text"))
|
assert.False(t, h.IsValid("some random text"))
|
||||||
|
|
||||||
|
assert.Equal(t, "79ee38a6416c1ede423ec7ee0a8639ceea4aad22", ComputeBlobHash(Sha1ObjectFormat, []byte("some random blob")).String())
|
||||||
|
assert.Equal(t, "d5c6407415d85df49592672aa421aed39b9db5e3", ComputeBlobHash(Sha1ObjectFormat, []byte("same length blob")).String())
|
||||||
|
assert.Equal(t, "df0b5174ed06ae65aea40d43316bcbc21d82c9e3158ce2661df2ad28d7931dd6", ComputeBlobHash(Sha256ObjectFormat, []byte("some random blob")).String())
|
||||||
}
|
}
|
||||||
|
|
32
modules/git/pipeline/lfs_common.go
Normal file
32
modules/git/pipeline/lfs_common.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package pipeline
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LFSResult represents commits found using a provided pointer file hash
|
||||||
|
type LFSResult struct {
|
||||||
|
Name string
|
||||||
|
SHA string
|
||||||
|
Summary string
|
||||||
|
When time.Time
|
||||||
|
ParentHashes []git.ObjectID
|
||||||
|
BranchName string
|
||||||
|
FullCommitName string
|
||||||
|
}
|
||||||
|
|
||||||
|
type lfsResultSlice []*LFSResult
|
||||||
|
|
||||||
|
func (a lfsResultSlice) Len() int { return len(a) }
|
||||||
|
func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
|
||||||
|
|
||||||
|
func lfsError(msg string, err error) error {
|
||||||
|
return fmt.Errorf("LFS error occurred, %s: err: %w", msg, err)
|
||||||
|
}
|
|
@ -7,12 +7,10 @@ package pipeline
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
|
||||||
|
@ -21,23 +19,6 @@ import (
|
||||||
"github.com/go-git/go-git/v5/plumbing/object"
|
"github.com/go-git/go-git/v5/plumbing/object"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LFSResult represents commits found using a provided pointer file hash
|
|
||||||
type LFSResult struct {
|
|
||||||
Name string
|
|
||||||
SHA string
|
|
||||||
Summary string
|
|
||||||
When time.Time
|
|
||||||
ParentHashes []git.ObjectID
|
|
||||||
BranchName string
|
|
||||||
FullCommitName string
|
|
||||||
}
|
|
||||||
|
|
||||||
type lfsResultSlice []*LFSResult
|
|
||||||
|
|
||||||
func (a lfsResultSlice) Len() int { return len(a) }
|
|
||||||
func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
|
|
||||||
|
|
||||||
// FindLFSFile finds commits that contain a provided pointer file hash
|
// FindLFSFile finds commits that contain a provided pointer file hash
|
||||||
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
|
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
|
||||||
resultsMap := map[string]*LFSResult{}
|
resultsMap := map[string]*LFSResult{}
|
||||||
|
@ -51,7 +32,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
|
||||||
All: true,
|
All: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to get GoGit CommitsIter. Error: %w", err)
|
return nil, lfsError("failed to get GoGit CommitsIter", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = commitsIter.ForEach(func(gitCommit *object.Commit) error {
|
err = commitsIter.ForEach(func(gitCommit *object.Commit) error {
|
||||||
|
@ -85,7 +66,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
return nil, fmt.Errorf("Failure in CommitIter.ForEach: %w", err)
|
return nil, lfsError("failure in CommitIter.ForEach", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, result := range resultsMap {
|
for _, result := range resultsMap {
|
||||||
|
@ -156,7 +137,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
|
||||||
select {
|
select {
|
||||||
case err, has := <-errChan:
|
case err, has := <-errChan:
|
||||||
if has {
|
if has {
|
||||||
return nil, fmt.Errorf("Unable to obtain name for LFS files. Error: %w", err)
|
return nil, lfsError("unable to obtain name for LFS files", err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
}
|
}
|
|
@ -8,33 +8,14 @@ package pipeline
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LFSResult represents commits found using a provided pointer file hash
|
|
||||||
type LFSResult struct {
|
|
||||||
Name string
|
|
||||||
SHA string
|
|
||||||
Summary string
|
|
||||||
When time.Time
|
|
||||||
ParentIDs []git.ObjectID
|
|
||||||
BranchName string
|
|
||||||
FullCommitName string
|
|
||||||
}
|
|
||||||
|
|
||||||
type lfsResultSlice []*LFSResult
|
|
||||||
|
|
||||||
func (a lfsResultSlice) Len() int { return len(a) }
|
|
||||||
func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
|
|
||||||
|
|
||||||
// FindLFSFile finds commits that contain a provided pointer file hash
|
// FindLFSFile finds commits that contain a provided pointer file hash
|
||||||
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
|
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
|
||||||
resultsMap := map[string]*LFSResult{}
|
resultsMap := map[string]*LFSResult{}
|
||||||
|
@ -137,11 +118,11 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
|
||||||
n += int64(count)
|
n += int64(count)
|
||||||
if bytes.Equal(binObjectID, objectID.RawValue()) {
|
if bytes.Equal(binObjectID, objectID.RawValue()) {
|
||||||
result := LFSResult{
|
result := LFSResult{
|
||||||
Name: curPath + string(fname),
|
Name: curPath + string(fname),
|
||||||
SHA: curCommit.ID.String(),
|
SHA: curCommit.ID.String(),
|
||||||
Summary: strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0],
|
Summary: strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0],
|
||||||
When: curCommit.Author.When,
|
When: curCommit.Author.When,
|
||||||
ParentIDs: curCommit.Parents,
|
ParentHashes: curCommit.Parents,
|
||||||
}
|
}
|
||||||
resultsMap[curCommit.ID.String()+":"+curPath+string(fname)] = &result
|
resultsMap[curCommit.ID.String()+":"+curPath+string(fname)] = &result
|
||||||
} else if string(mode) == git.EntryModeTree.String() {
|
} else if string(mode) == git.EntryModeTree.String() {
|
||||||
|
@ -183,7 +164,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
|
||||||
|
|
||||||
for _, result := range resultsMap {
|
for _, result := range resultsMap {
|
||||||
hasParent := false
|
hasParent := false
|
||||||
for _, parentID := range result.ParentIDs {
|
for _, parentID := range result.ParentHashes {
|
||||||
if _, hasParent = resultsMap[parentID.String()+":"+result.Name]; hasParent {
|
if _, hasParent = resultsMap[parentID.String()+":"+result.Name]; hasParent {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -241,7 +222,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
|
||||||
select {
|
select {
|
||||||
case err, has := <-errChan:
|
case err, has := <-errChan:
|
||||||
if has {
|
if has {
|
||||||
return nil, fmt.Errorf("Unable to obtain name for LFS files. Error: %w", err)
|
return nil, lfsError("unable to obtain name for LFS files", err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,13 +225,13 @@ func TestGitAttributeCheckerError(t *testing.T) {
|
||||||
ac, err := gitRepo.GitAttributeChecker("", "linguist-language")
|
ac, err := gitRepo.GitAttributeChecker("", "linguist-language")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// calling CheckPath before would allow git to cache part of it and succesfully return later
|
// calling CheckPath before would allow git to cache part of it and successfully return later
|
||||||
assert.NoError(t, os.RemoveAll(gitRepo.Path))
|
assert.NoError(t, os.RemoveAll(gitRepo.Path))
|
||||||
|
|
||||||
_, err = ac.CheckPath("i-am-a-python.p")
|
_, err = ac.CheckPath("i-am-a-python.p")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Skip(
|
t.Skip(
|
||||||
"git check-attr started too fast and CheckPath was succesful (and likely cached)",
|
"git check-attr started too fast and CheckPath was successful (and likely cached)",
|
||||||
"https://codeberg.org/forgejo/forgejo/issues/2948",
|
"https://codeberg.org/forgejo/forgejo/issues/2948",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,20 +7,39 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIsRiskyRedirectURL(t *testing.T) {
|
func TestIsRiskyRedirectURL(t *testing.T) {
|
||||||
setting.AppURL = "http://localhost:3000/"
|
defer test.MockVariableValue(&setting.AppURL, "http://localhost:3000/sub/")()
|
||||||
|
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
want bool
|
want bool
|
||||||
}{
|
}{
|
||||||
{"", false},
|
{"", false},
|
||||||
{"foo", false},
|
{"foo", false},
|
||||||
{"/", false},
|
{"./", false},
|
||||||
|
{"?key=val", false},
|
||||||
|
{"/sub/", false},
|
||||||
|
{"http://localhost:3000/sub/", false},
|
||||||
|
{"/sub/foo", false},
|
||||||
|
{"http://localhost:3000/sub/foo", false},
|
||||||
|
{"http://localhost:3000/sub/test?param=false", false},
|
||||||
|
// FIXME: should probably be true (would requires resolving references using setting.appURL.ResolveReference(u))
|
||||||
|
{"/sub/../", false},
|
||||||
|
{"http://localhost:3000/sub/../", false},
|
||||||
|
{"/sUb/", false},
|
||||||
|
{"http://localhost:3000/sUb/foo", false},
|
||||||
|
{"/sub", false},
|
||||||
{"/foo?k=%20#abc", false},
|
{"/foo?k=%20#abc", false},
|
||||||
|
{"/", false},
|
||||||
|
{"a/", false},
|
||||||
|
{"test?param=false", false},
|
||||||
|
{"/hey/hey/hey#3244", false},
|
||||||
|
|
||||||
{"//", true},
|
{"//", true},
|
||||||
{"\\\\", true},
|
{"\\\\", true},
|
||||||
|
@ -28,7 +47,73 @@ func TestIsRiskyRedirectURL(t *testing.T) {
|
||||||
{"\\/", true},
|
{"\\/", true},
|
||||||
{"mail:a@b.com", true},
|
{"mail:a@b.com", true},
|
||||||
{"https://test.com", true},
|
{"https://test.com", true},
|
||||||
{setting.AppURL + "/foo", false},
|
{"http://localhost:3000/foo", true},
|
||||||
|
{"http://localhost:3000/sub", true},
|
||||||
|
{"http://localhost:3000/sub?key=val", true},
|
||||||
|
{"https://example.com/", true},
|
||||||
|
{"//example.com", true},
|
||||||
|
{"http://example.com", true},
|
||||||
|
{"http://localhost:3000/test?param=false", true},
|
||||||
|
{"//localhost:3000/test?param=false", true},
|
||||||
|
{"://missing protocol scheme", true},
|
||||||
|
// FIXME: should probably be false
|
||||||
|
{"//localhost:3000/sub/test?param=false", true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.input, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.want, IsRiskyRedirectURL(tt.input))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsRiskyRedirectURLWithoutSubURL(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.AppURL, "https://next.forgejo.org/")()
|
||||||
|
defer test.MockVariableValue(&setting.AppSubURL, "")()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"", false},
|
||||||
|
{"foo", false},
|
||||||
|
{"./", false},
|
||||||
|
{"?key=val", false},
|
||||||
|
{"/sub/", false},
|
||||||
|
{"https://next.forgejo.org/sub/", false},
|
||||||
|
{"/sub/foo", false},
|
||||||
|
{"https://next.forgejo.org/sub/foo", false},
|
||||||
|
{"https://next.forgejo.org/sub/test?param=false", false},
|
||||||
|
{"https://next.forgejo.org/sub/../", false},
|
||||||
|
{"/sub/../", false},
|
||||||
|
{"/sUb/", false},
|
||||||
|
{"https://next.forgejo.org/sUb/foo", false},
|
||||||
|
{"/sub", false},
|
||||||
|
{"/foo?k=%20#abc", false},
|
||||||
|
{"/", false},
|
||||||
|
{"a/", false},
|
||||||
|
{"test?param=false", false},
|
||||||
|
{"/hey/hey/hey#3244", false},
|
||||||
|
{"https://next.forgejo.org/test?param=false", false},
|
||||||
|
{"https://next.forgejo.org/foo", false},
|
||||||
|
{"https://next.forgejo.org/sub", false},
|
||||||
|
{"https://next.forgejo.org/sub?key=val", false},
|
||||||
|
|
||||||
|
{"//", true},
|
||||||
|
{"\\\\", true},
|
||||||
|
{"/\\", true},
|
||||||
|
{"\\/", true},
|
||||||
|
{"mail:a@b.com", true},
|
||||||
|
{"https://test.com", true},
|
||||||
|
{"https://example.com/", true},
|
||||||
|
{"//example.com", true},
|
||||||
|
{"http://example.com", true},
|
||||||
|
{"://missing protocol scheme", true},
|
||||||
|
{"https://forgejo.org", true},
|
||||||
|
{"https://example.org?url=https://next.forgejo.org", true},
|
||||||
|
// FIXME: should probably be false
|
||||||
|
{"https://next.forgejo.org", true},
|
||||||
|
{"//next.forgejo.org/test?param=false", true},
|
||||||
|
{"//next.forgejo.org/sub/test?param=false", true},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.input, func(t *testing.T) {
|
t.Run(tt.input, func(t *testing.T) {
|
||||||
|
|
|
@ -41,6 +41,8 @@ const (
|
||||||
maxBatchSize = 16
|
maxBatchSize = 16
|
||||||
// fuzzyDenominator determines the levenshtein distance per each character of a keyword
|
// fuzzyDenominator determines the levenshtein distance per each character of a keyword
|
||||||
fuzzyDenominator = 4
|
fuzzyDenominator = 4
|
||||||
|
// see https://github.com/blevesearch/bleve/issues/1563#issuecomment-786822311
|
||||||
|
maxFuzziness = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
func addUnicodeNormalizeTokenFilter(m *mapping.IndexMappingImpl) error {
|
func addUnicodeNormalizeTokenFilter(m *mapping.IndexMappingImpl) error {
|
||||||
|
@ -246,7 +248,7 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
|
||||||
phraseQuery.Analyzer = repoIndexerAnalyzer
|
phraseQuery.Analyzer = repoIndexerAnalyzer
|
||||||
keywordQuery = phraseQuery
|
keywordQuery = phraseQuery
|
||||||
if opts.IsKeywordFuzzy {
|
if opts.IsKeywordFuzzy {
|
||||||
phraseQuery.Fuzziness = len(opts.Keyword) / fuzzyDenominator
|
phraseQuery.Fuzziness = min(maxFuzziness, len(opts.Keyword)/fuzzyDenominator)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(opts.RepoIDs) > 0 {
|
if len(opts.RepoIDs) > 0 {
|
||||||
|
|
|
@ -120,9 +120,12 @@ func Init() {
|
||||||
case "bleve", "elasticsearch":
|
case "bleve", "elasticsearch":
|
||||||
handler := func(items ...*internal.IndexerData) (unhandled []*internal.IndexerData) {
|
handler := func(items ...*internal.IndexerData) (unhandled []*internal.IndexerData) {
|
||||||
indexer := *globalIndexer.Load()
|
indexer := *globalIndexer.Load()
|
||||||
|
// make it a process to allow for cancellation (especially during integration tests where no global shutdown happens)
|
||||||
|
batchCtx, _, finished := process.GetManager().AddContext(ctx, "CodeIndexer batch")
|
||||||
|
defer finished()
|
||||||
for _, indexerData := range items {
|
for _, indexerData := range items {
|
||||||
log.Trace("IndexerData Process Repo: %d", indexerData.RepoID)
|
log.Trace("IndexerData Process Repo: %d", indexerData.RepoID)
|
||||||
if err := index(ctx, indexer, indexerData.RepoID); err != nil {
|
if err := index(batchCtx, indexer, indexerData.RepoID); err != nil {
|
||||||
unhandled = append(unhandled, indexerData)
|
unhandled = append(unhandled, indexerData)
|
||||||
if !setting.IsInTesting {
|
if !setting.IsInTesting {
|
||||||
log.Error("Codes indexer handler: index error for repo %v: %v", indexerData.RepoID, err)
|
log.Error("Codes indexer handler: index error for repo %v: %v", indexerData.RepoID, err)
|
||||||
|
@ -155,7 +158,7 @@ func Init() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
log.Error("PANIC whilst initializing repository indexer: %v\nStacktrace: %s", err, log.Stack(2))
|
log.Error("PANIC whilst initializing repository indexer: %v\nStacktrace: %s", err, log.Stack(2))
|
||||||
log.Error("The indexer files are likely corrupted and may need to be deleted")
|
log.Error("The indexer files are likely corrupted and may need to be deleted")
|
||||||
log.Error("You can completely remove the \"%s\" directory to make Gitea recreate the indexes", setting.Indexer.RepoPath)
|
log.Error("You can completely remove the \"%s\" directory to make Forgejo recreate the indexes", setting.Indexer.RepoPath)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -173,17 +176,11 @@ func Init() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
log.Error("PANIC whilst initializing repository indexer: %v\nStacktrace: %s", err, log.Stack(2))
|
log.Error("PANIC whilst initializing repository indexer: %v\nStacktrace: %s", err, log.Stack(2))
|
||||||
log.Error("The indexer files are likely corrupted and may need to be deleted")
|
log.Error("The indexer files are likely corrupted and may need to be deleted")
|
||||||
log.Error("You can completely remove the \"%s\" index to make Gitea recreate the indexes", setting.Indexer.RepoConnStr)
|
log.Error("You can completely remove the \"%s\" index to make Forgejo recreate the indexes", setting.Indexer.RepoConnStr)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
rIndexer = elasticsearch.NewIndexer(setting.Indexer.RepoConnStr, setting.Indexer.RepoIndexerName)
|
rIndexer = elasticsearch.NewIndexer(setting.Indexer.RepoConnStr, setting.Indexer.RepoIndexerName)
|
||||||
if err != nil {
|
|
||||||
cancel()
|
|
||||||
(*globalIndexer.Load()).Close()
|
|
||||||
close(waitChannel)
|
|
||||||
log.Fatal("PID: %d Unable to create the elasticsearch Repository Indexer connstr: %s Error: %v", os.Getpid(), setting.Indexer.RepoConnStr, err)
|
|
||||||
}
|
|
||||||
existed, err = rIndexer.Init(ctx)
|
existed, err = rIndexer.Init(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue