mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-11 15:04:02 +09:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a9e8c4e8c | ||
|
|
321d3ec104 | ||
|
|
23fb49fea7 | ||
|
|
01fe36985d | ||
|
|
e60d1c7fc8 | ||
|
|
c30ddc3e14 | ||
|
|
c344004e53 | ||
|
|
251e697b0a | ||
|
|
39ae5d6e6b | ||
|
|
76aabbff9f | ||
|
|
2f94380222 | ||
|
|
437ca2dc6f | ||
|
|
c0a8eb8e74 | ||
|
|
1903c61627 | ||
|
|
871d29553b | ||
|
|
ab9bcd6a79 | ||
|
|
8a553332f7 | ||
|
|
02da29f2da | ||
|
|
a0b9ce05c4 | ||
|
|
cc623c5f2f | ||
|
|
89ec271d9c | ||
|
|
c8fc705632 | ||
|
|
21cf3f774d | ||
|
|
5bcb0ce0fc | ||
|
|
5a529ffd58 | ||
|
|
4cba1e18ed | ||
|
|
9b1d6f47f1 | ||
|
|
26475404d4 | ||
|
|
20ea3e204d | ||
|
|
a835fd78d1 | ||
|
|
1065f03454 | ||
|
|
052f660ba5 | ||
|
|
fc1f0dbec4 | ||
|
|
1264072754 | ||
|
|
bd2a6c40d7 | ||
|
|
43fedd662a | ||
|
|
6e7de91f99 | ||
|
|
b1726adebb | ||
|
|
4e2c3e43f2 | ||
|
|
d16b20b972 | ||
|
|
66546045b5 | ||
|
|
64f4b5856a | ||
|
|
27d876e311 | ||
|
|
b0d5a63e7a | ||
|
|
b71967b254 | ||
|
|
fc44404843 | ||
|
|
49adfd065d | ||
|
|
b576f5bb34 | ||
|
|
2705abf7f3 | ||
|
|
9bb8751b29 | ||
|
|
6809ecf2d2 | ||
|
|
db7d1da28f | ||
|
|
826c65d0ec | ||
|
|
a9ac8c0afd | ||
|
|
eb4468ff4e | ||
|
|
4bebd86285 | ||
|
|
7641fc3a8c | ||
|
|
cca466caae | ||
|
|
516a4883fa | ||
|
|
f65e01226a | ||
|
|
ab1979bcfb | ||
|
|
ad38b60983 | ||
|
|
3932624947 | ||
|
|
e0e18fc286 | ||
|
|
457510fa09 | ||
|
|
2baa5a16f8 | ||
|
|
fb067e1115 | ||
|
|
daaed0d7f4 | ||
|
|
99573bde0e | ||
|
|
986b0bcae0 | ||
|
|
6dad3ad43d | ||
|
|
cf89ecd887 | ||
|
|
bea6fcaa84 | ||
|
|
873efed5a6 | ||
|
|
e6ffbea888 | ||
|
|
2f01ec38bd | ||
|
|
fc81f2832a | ||
|
|
075ff8f516 | ||
|
|
2f5cdbd5c1 | ||
|
|
aae0a218c3 | ||
|
|
9b5c87a6b6 | ||
|
|
51b93d1d27 | ||
|
|
f3aed8b81d | ||
|
|
fc68608603 | ||
|
|
fe31237fd8 | ||
|
|
a71c5c94c5 | ||
|
|
f81a2ab69a | ||
|
|
b0d6cc1d22 | ||
|
|
8936303510 | ||
|
|
3d085dbaf1 | ||
|
|
9b2a3c267b | ||
|
|
590d2984d9 | ||
|
|
61e0343580 | ||
|
|
0b43bde974 | ||
|
|
cc95f141f8 | ||
|
|
a25fbd43c4 | ||
|
|
f365a6b9c8 | ||
|
|
e0095af8c3 |
No files matched your search
@@ -63,7 +63,6 @@ cpu.out
|
|||||||
/public/assets/fonts
|
/public/assets/fonts
|
||||||
/public/assets/img/avatar
|
/public/assets/img/avatar
|
||||||
/vendor
|
/vendor
|
||||||
/VERSION
|
|
||||||
/.air
|
/.air
|
||||||
/.go-licenses
|
/.go-licenses
|
||||||
/Dockerfile
|
/Dockerfile
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ runs:
|
|||||||
env:
|
env:
|
||||||
# pgsql is chosen to be the unlucky one to run with the slow "race detector", it is about 60% slower.
|
# pgsql is chosen to be the unlucky one to run with the slow "race detector", it is about 60% slower.
|
||||||
GOTEST_FLAGS: -race -timeout=40m
|
GOTEST_FLAGS: -race -timeout=40m
|
||||||
TAGS: bindata gogit
|
TAGS: bindata
|
||||||
TEST_LDAP: 1
|
TEST_LDAP: 1
|
||||||
TEST_SHARD: ${{ inputs.shard }}
|
TEST_SHARD: ${{ inputs.shard }}
|
||||||
TEST_TOTAL_SHARDS: ${{ inputs.total-shards }}
|
TEST_TOTAL_SHARDS: ${{ inputs.total-shards }}
|
||||||
@@ -37,22 +37,15 @@ jobs:
|
|||||||
- uses: ./.github/actions/go-setup
|
- uses: ./.github/actions/go-setup
|
||||||
- run: make deps-backend deps-tools
|
- run: make deps-backend deps-tools
|
||||||
- run: TAGS="bindata" make backend
|
- run: TAGS="bindata" make backend
|
||||||
- run: TAGS="bindata gogit" make backend
|
|
||||||
- name: warm test compile cache (bindata)
|
- name: warm test compile cache (bindata)
|
||||||
env:
|
env:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
GOTEST_FLAGS: -race -list=^$$ -count=1
|
GOTEST_FLAGS: -race -list=^$$ -count=1
|
||||||
run: make test-backend
|
run: make test-backend
|
||||||
- name: warm test compile cache (bindata gogit)
|
|
||||||
env:
|
|
||||||
TAGS: bindata gogit
|
|
||||||
GOTEST_FLAGS: -race -list=^$$ -count=1
|
|
||||||
run: make test-backend
|
|
||||||
- name: warm integration compile cache
|
- name: warm integration compile cache
|
||||||
run: |
|
run: |
|
||||||
TAGS="bindata" make test-integration-compile
|
TAGS="bindata" make test-integration-compile
|
||||||
TAGS="bindata gogit" make test-integration-compile
|
TAGS="bindata" GOTEST_FLAGS="-race" make test-integration-compile
|
||||||
TAGS="bindata gogit" GOTEST_FLAGS="-race" make test-integration-compile
|
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ concurrency:
|
|||||||
group: cron-renovate
|
group: cron-renovate
|
||||||
|
|
||||||
env:
|
env:
|
||||||
RENOVATE_VERSION: 44.109.1 # renovate: datasource=npm depName=renovate
|
RENOVATE_VERSION: 44.121.4 # renovate: datasource=npm depName=renovate
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
- uses: renovatebot/github-action@9fea9f0fbf80401026d11d03911d62b1f70fef1f # v46.3.3
|
- uses: renovatebot/github-action@6d26fcf0275dc65624cbc3d51fe78bc773f98321 # v46.3.6
|
||||||
with:
|
with:
|
||||||
renovate-version: ${{ env.RENOVATE_VERSION }}
|
renovate-version: ${{ env.RENOVATE_VERSION }}
|
||||||
configurationFile: renovate.json5
|
configurationFile: renovate.json5
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
- uses: crowdin/github-action@df474cdfb9f41d6ae777118749477c2cdf7cacc8 # v3.2.0
|
- uses: crowdin/github-action@9c23991700c0ec5256fd41089b9d9d7d540e424e # v3.3.0
|
||||||
with:
|
with:
|
||||||
upload_sources: true
|
upload_sources: true
|
||||||
upload_translations: false
|
upload_translations: false
|
||||||
|
|||||||
@@ -93,13 +93,13 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GOOS: linux
|
GOOS: linux
|
||||||
GOARCH: arm64
|
GOARCH: arm64
|
||||||
TAGS: bindata gogit
|
TAGS: bindata
|
||||||
- name: build-backend-windows
|
- name: build-backend-windows
|
||||||
run: go build -ldflags '-s -w' -o gitea-windows
|
run: go build -ldflags '-s -w' -o gitea-windows
|
||||||
env:
|
env:
|
||||||
GOOS: windows
|
GOOS: windows
|
||||||
GOARCH: amd64
|
GOARCH: amd64
|
||||||
TAGS: bindata gogit
|
TAGS: bindata
|
||||||
- name: build-backend-386
|
- name: build-backend-386
|
||||||
run: go build -ldflags '-s -w' -o gitea-linux-386
|
run: go build -ldflags '-s -w' -o gitea-linux-386
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -95,17 +95,17 @@ jobs:
|
|||||||
- run: make deps-backend
|
- run: make deps-backend
|
||||||
- run: make backend
|
- run: make backend
|
||||||
env:
|
env:
|
||||||
TAGS: bindata gogit
|
TAGS: bindata
|
||||||
- run: GITEA_TEST_DATABASE=sqlite make test-migration
|
- run: GITEA_TEST_DATABASE=sqlite make test-migration
|
||||||
env:
|
env:
|
||||||
TAGS: bindata gogit
|
TAGS: bindata
|
||||||
- name: run tests
|
- name: run tests
|
||||||
run: GITEA_TEST_DATABASE=sqlite make test-integration
|
run: GITEA_TEST_DATABASE=sqlite make test-integration
|
||||||
timeout-minutes: 50
|
timeout-minutes: 50
|
||||||
env:
|
env:
|
||||||
# sqlite driver can contain large amount of Golang code, so don't use race detector for it, otherwise, extremely slow
|
# sqlite driver can contain large amount of Golang code, so don't use race detector for it, otherwise, extremely slow
|
||||||
GOTEST_FLAGS: -timeout=40m
|
GOTEST_FLAGS: -timeout=40m
|
||||||
TAGS: bindata gogit
|
TAGS: bindata
|
||||||
|
|
||||||
test-unit:
|
test-unit:
|
||||||
if: needs.files-changed.outputs.backend == 'true'
|
if: needs.files-changed.outputs.backend == 'true'
|
||||||
@@ -163,13 +163,6 @@ jobs:
|
|||||||
GOTEST_FLAGS: -race -timeout=20m
|
GOTEST_FLAGS: -race -timeout=20m
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
||||||
- name: unit-tests-gogit
|
|
||||||
run: make test-backend
|
|
||||||
env:
|
|
||||||
GOTEST_FLAGS: -race -timeout=20m
|
|
||||||
TAGS: bindata gogit
|
|
||||||
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
|
||||||
GITEA_TEST_CI_SKIP_EXTERNAL: true
|
|
||||||
- run: make test-check
|
- run: make test-check
|
||||||
|
|
||||||
test-mysql:
|
test-mysql:
|
||||||
|
|||||||
@@ -37,8 +37,15 @@ jobs:
|
|||||||
# retry snapcraft uploads which can be flaky
|
# retry snapcraft uploads which can be flaky
|
||||||
- name: Publish snap
|
- name: Publish snap
|
||||||
run: |
|
run: |
|
||||||
|
# part-gitea-pull.sh decides what is built, see the comments there:
|
||||||
|
# * grade devel (main or a prerelease tag) goes to latest/edge
|
||||||
|
# * grade stable (a new release tag) goes to latest/stable, and to latest/candidate which marks it as released
|
||||||
|
channel=latest/edge
|
||||||
|
if unsquashfs -cat "$SNAP" meta/snap.yaml | grep -qx 'grade: stable'; then
|
||||||
|
channel=latest/stable,latest/candidate
|
||||||
|
fi
|
||||||
for attempt in 1 2 3 4 5; do
|
for attempt in 1 2 3 4 5; do
|
||||||
snapcraft upload "$SNAP" --release latest/edge && exit 0
|
snapcraft upload "$SNAP" --release "$channel" && exit 0
|
||||||
echo "::warning::snap upload attempt $attempt failed, retrying in 15s"
|
echo "::warning::snap upload attempt $attempt failed, retrying in 15s"
|
||||||
sleep 15
|
sleep 15
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -76,6 +76,10 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
previous=$(git tag --list --sort=-v:refname | grep -xE 'v[0-9]+\.[0-9]+\.[0-9]+' | grep -A1 -xF "$GITHUB_REF_NAME" | tail -1) # highest stable version below this one
|
previous=$(git tag --list --sort=-v:refname | grep -xE 'v[0-9]+\.[0-9]+\.[0-9]+' | grep -A1 -xF "$GITHUB_REF_NAME" | tail -1) # highest stable version below this one
|
||||||
echo "range=$previous..$GITHUB_SHA" >> "$GITHUB_OUTPUT"
|
echo "range=$previous..$GITHUB_SHA" >> "$GITHUB_OUTPUT"
|
||||||
|
- name: skip reverts and reverted commits in changelog
|
||||||
|
env:
|
||||||
|
RANGE: ${{ steps.range.outputs.range }}
|
||||||
|
run: ./tools/generate-cliffignore.sh "$RANGE" | sort -u > .cliffignore
|
||||||
- uses: orhun/git-cliff-action@a9a95522b26fe6403f7bb24031f21fb573d0f5ff # v4.9.1
|
- uses: orhun/git-cliff-action@a9a95522b26fe6403f7bb24031f21fb573d0f5ff # v4.9.1
|
||||||
with:
|
with:
|
||||||
args: --tag ${{ github.ref_name }} ${{ steps.range.outputs.range }}
|
args: --tag ${{ github.ref_name }} ${{ steps.range.outputs.range }}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
- In `options/locale`, only edit `locale_en-US.json`, other locales are synced automatically
|
- In `options/locale`, only edit `locale_en-US.json`, other locales are synced automatically
|
||||||
- In TS, use `!` instead of `?.`/`??` when a value always exists
|
- In TS, use `!` instead of `?.`/`??` when a value always exists
|
||||||
- In Go, prefer to use modern language features wherever possible
|
- In Go, prefer to use modern language features wherever possible
|
||||||
|
- In Go, function-name prefixes in errors like `fmt.Errorf("Foo: %w", err)` must always name the function they are in
|
||||||
- Write sizes as multiplications like `64 * 1024`, not bit shifts like `64 << 10`
|
- Write sizes as multiplications like `64 * 1024`, not bit shifts like `64 << 10`
|
||||||
- Prefer `tw-*` utilities over inline `style` and `flex-*` helpers over per-child `tw-ml-*`/`tw-mr-*` margins, falling back to `tw-*` where specificity requires `!important`
|
- Prefer `tw-*` utilities over inline `style` and `flex-*` helpers over per-child `tw-ml-*`/`tw-mr-*` margins, falling back to `tw-*` where specificity requires `!important`
|
||||||
- Run `make fmt` after `.go` edits, `make tidy` after `go.mod` edits, `make generate-swagger` after API changes, and lint what changed with `make lint-go`, `lint-js`, `lint-css` or `lint-templates`
|
- Run `make fmt` after `.go` edits, `make tidy` after `go.mod` edits, `make generate-swagger` after API changes, and lint what changed with `make lint-go`, `lint-js`, `lint-css` or `lint-templates`
|
||||||
|
|||||||
+1
-1
@@ -175,7 +175,7 @@ PR titles must follow the [Conventional Commits](https://www.conventionalcommits
|
|||||||
type(scope)!: subject
|
type(scope)!: subject
|
||||||
```
|
```
|
||||||
|
|
||||||
The scope in parentheses is optional. A `!` immediately before the colon marks a [breaking change](https://www.conventionalcommits.org/en/v1.0.0/#summary): either `type!:` or `type(scope)!:` (not `type!(scope):`).
|
The scope in parentheses is optional. A `!` immediately before the colon marks a [breaking change](https://www.conventionalcommits.org/en/v1.0.0/#summary): either `type!:` or `type(scope)!:` (not `type!(scope):`). Do not mark `build`, `chore`, `ci`, `docs`, `style`, or `test` as breaking.
|
||||||
|
|
||||||
Use one of these types:
|
Use one of these types:
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ SHASUM ?= shasum -a 256
|
|||||||
|
|
||||||
AIR_PACKAGE ?= github.com/air-verse/air@v1.67.4 # renovate: datasource=go
|
AIR_PACKAGE ?= github.com/air-verse/air@v1.67.4 # renovate: datasource=go
|
||||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v4/cmd/editorconfig-checker@v4.0.2 # renovate: datasource=go
|
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v4/cmd/editorconfig-checker@v4.0.2 # renovate: datasource=go
|
||||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.13.2 # renovate: datasource=go
|
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.14.0 # renovate: datasource=go
|
||||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.17 # renovate: datasource=go
|
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.17 # renovate: datasource=go
|
||||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.8.0 # renovate: datasource=go
|
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.8.0 # renovate: datasource=go
|
||||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.36.6 # renovate: datasource=go
|
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.36.6 # renovate: datasource=go
|
||||||
@@ -75,8 +75,7 @@ STORED_VERSION_FILE := VERSION
|
|||||||
GITHUB_REF_TYPE ?= branch
|
GITHUB_REF_TYPE ?= branch
|
||||||
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
|
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||||
|
|
||||||
# VERSION: the branch name for the build and filenames, e.g.: "feature/foo-bar", "main"
|
# VERSION: the name for the build and filenames, e.g.: "1.2.3" for tag "v1.2.3", "main-nightly" for branch "main"
|
||||||
# branch name "release/v1.27.2" is stripped to "1.27.2".
|
|
||||||
# GITEA_VERSION: the Gitea's internal version for display, e.g. "1.28.0+dev-356-ge47d0b66ea"
|
# GITEA_VERSION: the Gitea's internal version for display, e.g. "1.28.0+dev-356-ge47d0b66ea"
|
||||||
ifeq ($(GITHUB_REF_TYPE),tag)
|
ifeq ($(GITHUB_REF_TYPE),tag)
|
||||||
# convert tag "v1.2.3" to "1.2.3"
|
# convert tag "v1.2.3" to "1.2.3"
|
||||||
@@ -101,11 +100,6 @@ else
|
|||||||
$(error unsupported ref type $(GITHUB_REF_TYPE))
|
$(error unsupported ref type $(GITHUB_REF_TYPE))
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# if version == "main" then add "-nightly" to the version for nightly builds: "main-nightly"
|
|
||||||
ifeq ($(VERSION),main)
|
|
||||||
VERSION := main-nightly
|
|
||||||
endif
|
|
||||||
|
|
||||||
LDFLAGS := $(LDFLAGS) -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
|
LDFLAGS := $(LDFLAGS) -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
|
||||||
RELEASE_ENV = GO="$(GO)" TAGS="$(TAGS)" LDFLAGS="$(LDFLAGS)" DIST="$(DIST)" VERSION="$(VERSION)"
|
RELEASE_ENV = GO="$(GO)" TAGS="$(TAGS)" LDFLAGS="$(LDFLAGS)" DIST="$(DIST)" VERSION="$(VERSION)"
|
||||||
|
|
||||||
@@ -556,7 +550,7 @@ release-compress: | $(DIST_DIRS)
|
|||||||
|
|
||||||
.PHONY: release-sources
|
.PHONY: release-sources
|
||||||
release-sources: | $(DIST_DIRS)
|
release-sources: | $(DIST_DIRS)
|
||||||
echo $(VERSION) > $(STORED_VERSION_FILE)
|
echo $(GITEA_VERSION) > $(STORED_VERSION_FILE)
|
||||||
# bsdtar needs a ^ to prevent matching subdirectories
|
# bsdtar needs a ^ to prevent matching subdirectories
|
||||||
$(eval EXCL := --exclude=$(shell tar --help | grep -q bsdtar && echo "^")./)
|
$(eval EXCL := --exclude=$(shell tar --help | grep -q bsdtar && echo "^")./)
|
||||||
# use transform to a add a release-folder prefix; in bsdtar the transform parameter equivalent is -s
|
# use transform to a add a release-folder prefix; in bsdtar the transform parameter equivalent is -s
|
||||||
|
|||||||
Generated
-45
@@ -14,26 +14,6 @@
|
|||||||
"path": "filippo.io/edwards25519/LICENSE",
|
"path": "filippo.io/edwards25519/LICENSE",
|
||||||
"licenseText": "Copyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
|
"licenseText": "Copyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "gitea.com/go-chi/binding",
|
|
||||||
"path": "gitea.com/go-chi/binding/LICENSE",
|
|
||||||
"licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that YLine truncated
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "gitea.com/go-chi/cache",
|
|
||||||
"path": "gitea.com/go-chi/cache/LICENSE",
|
|
||||||
"licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that YLine truncated
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "gitea.com/go-chi/captcha",
|
|
||||||
"path": "gitea.com/go-chi/captcha/LICENSE",
|
|
||||||
"licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that YLine truncated
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "gitea.com/go-chi/session",
|
|
||||||
"path": "gitea.com/go-chi/session/LICENSE",
|
|
||||||
"licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that YLine truncated
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "gitea.com/lunny/dingtalk_webhook",
|
"name": "gitea.com/lunny/dingtalk_webhook",
|
||||||
"path": "gitea.com/lunny/dingtalk_webhook/LICENSE",
|
"path": "gitea.com/lunny/dingtalk_webhook/LICENSE",
|
||||||
@@ -334,21 +314,6 @@
|
|||||||
"path": "github.com/coder/websocket/LICENSE.txt",
|
"path": "github.com/coder/websocket/LICENSE.txt",
|
||||||
"licenseText": "Copyright (c) 2025 Coder\n\nPermission to use, copy, modify, and distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n"
|
"licenseText": "Copyright (c) 2025 Coder\n\nPermission to use, copy, modify, and distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "github.com/couchbase/go-couchbase",
|
|
||||||
"path": "github.com/couchbase/go-couchbase/LICENSE",
|
|
||||||
"licenseText": "Copyright (c) 2013 Couchbase, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "github.com/couchbase/gomemcached",
|
|
||||||
"path": "github.com/couchbase/gomemcached/LICENSE",
|
|
||||||
"licenseText": "Copyright (c) 2013 Dustin Sallings\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "github.com/couchbase/goutils",
|
|
||||||
"path": "github.com/couchbase/goutils/LICENSE.txt",
|
|
||||||
"licenseText": "Source code in this repository is licensed under various licenses. The\nBusiness Source License 1.1 (BSL) is one such license. Each file indicates in\na section at the beginning of the file the name of the license that applies to\nit. All licenses used in this repository can be found in the top-level\nlicenses directory.\n"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "github.com/cpuguy83/go-md2man/v2",
|
"name": "github.com/cpuguy83/go-md2man/v2",
|
||||||
"path": "github.com/cpuguy83/go-md2man/v2/LICENSE.md",
|
"path": "github.com/cpuguy83/go-md2man/v2/LICENSE.md",
|
||||||
@@ -529,11 +494,6 @@
|
|||||||
"path": "github.com/golang/snappy/LICENSE",
|
"path": "github.com/golang/snappy/LICENSE",
|
||||||
"licenseText": "Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
|
"licenseText": "Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "github.com/google/flatbuffers",
|
|
||||||
"path": "github.com/google/flatbuffers/LICENSE",
|
|
||||||
"licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under tLine truncated
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "github.com/google/go-github/v92",
|
"name": "github.com/google/go-github/v92",
|
||||||
"path": "github.com/google/go-github/v92/LICENSE",
|
"path": "github.com/google/go-github/v92/LICENSE",
|
||||||
@@ -994,11 +954,6 @@
|
|||||||
"path": "github.com/ulikunitz/xz/LICENSE",
|
"path": "github.com/ulikunitz/xz/LICENSE",
|
||||||
"licenseText": "Copyright (c) 2014-2022 Ulrich Kunitz\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n* My name, Ulrich Kunitz, may not be used to endorse or promote products\n derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
|
"licenseText": "Copyright (c) 2014-2022 Ulrich Kunitz\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n* My name, Ulrich Kunitz, may not be used to endorse or promote products\n derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "github.com/unknwon/com",
|
|
||||||
"path": "github.com/unknwon/com/LICENSE",
|
|
||||||
"licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that YLine truncated
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "github.com/urfave/cli-docs/v3",
|
"name": "github.com/urfave/cli-docs/v3",
|
||||||
"path": "github.com/urfave/cli-docs/v3/LICENSE",
|
"path": "github.com/urfave/cli-docs/v3/LICENSE",
|
||||||
|
|||||||
+2
-8
@@ -13,13 +13,11 @@ import (
|
|||||||
|
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
"gitea.dev/modules/dump"
|
"gitea.dev/modules/dump"
|
||||||
"gitea.dev/modules/json"
|
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
"gitea.dev/modules/storage"
|
"gitea.dev/modules/storage"
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
|
|
||||||
"gitea.com/go-chi/session"
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -249,12 +247,8 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
|
|||||||
log.Info("Packing data directory...%s", setting.AppDataPath)
|
log.Info("Packing data directory...%s", setting.AppDataPath)
|
||||||
|
|
||||||
var excludes []string
|
var excludes []string
|
||||||
if setting.SessionConfig.OriginalProvider == "file" {
|
if setting.SessionConfig.Provider == "file" {
|
||||||
var opts session.Options
|
excludes = append(excludes, setting.SessionConfig.ProviderConfig)
|
||||||
if err = json.Unmarshal([]byte(setting.SessionConfig.ProviderConfig), &opts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
excludes = append(excludes, opts.ProviderConfig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.IsSet("skip-index") && cmd.Bool("skip-index") {
|
if cmd.IsSet("skip-index") && cmd.Bool("skip-index") {
|
||||||
|
|||||||
+14
-50
@@ -169,10 +169,6 @@ func parseGitHookCommitRefLine(line string) (oldCommitID, newCommitID string, re
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runHookPreReceive(ctx context.Context, c *cli.Command) error {
|
func runHookPreReceive(ctx context.Context, c *cli.Command) error {
|
||||||
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
|
|
||||||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
||||||
@@ -213,7 +209,6 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
refFullNames := make([]git.RefName, hookBatchSize)
|
refFullNames := make([]git.RefName, hookBatchSize)
|
||||||
count := 0
|
count := 0
|
||||||
total := 0
|
total := 0
|
||||||
lastline := 0
|
|
||||||
|
|
||||||
out := io.Discard
|
out := io.Discard
|
||||||
if setting.Git.VerbosePush {
|
if setting.Git.VerbosePush {
|
||||||
@@ -226,8 +221,6 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
supportProcReceive := git.DefaultFeatures().SupportProcReceive
|
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
// TODO: support news feeds for wiki
|
// TODO: support news feeds for wiki
|
||||||
if isWiki {
|
if isWiki {
|
||||||
@@ -240,37 +233,23 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
}
|
}
|
||||||
|
|
||||||
total++
|
total++
|
||||||
lastline++
|
oldCommitIDs[count] = oldCommitID
|
||||||
|
newCommitIDs[count] = newCommitID
|
||||||
|
refFullNames[count] = refFullName
|
||||||
|
count++
|
||||||
|
fmt.Fprintf(out, "*")
|
||||||
|
|
||||||
// If the ref is a branch or tag, check if it's protected
|
if count >= hookBatchSize {
|
||||||
// if supportProcReceive all ref should be checked because
|
fmt.Fprintf(out, " Checking %d references\n", count)
|
||||||
// permission check was delayed
|
|
||||||
if supportProcReceive || refFullName.IsBranch() || refFullName.IsTag() {
|
|
||||||
oldCommitIDs[count] = oldCommitID
|
|
||||||
newCommitIDs[count] = newCommitID
|
|
||||||
refFullNames[count] = refFullName
|
|
||||||
count++
|
|
||||||
fmt.Fprintf(out, "*")
|
|
||||||
|
|
||||||
if count >= hookBatchSize {
|
hookOptions.OldCommitIDs = oldCommitIDs
|
||||||
fmt.Fprintf(out, " Checking %d references\n", count)
|
hookOptions.NewCommitIDs = newCommitIDs
|
||||||
|
hookOptions.RefFullNames = refFullNames
|
||||||
hookOptions.OldCommitIDs = oldCommitIDs
|
extra := private.HookPreReceive(ctx, ownerName, repoName, hookOptions)
|
||||||
hookOptions.NewCommitIDs = newCommitIDs
|
if extra.HasError() {
|
||||||
hookOptions.RefFullNames = refFullNames
|
return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error)
|
||||||
extra := private.HookPreReceive(ctx, ownerName, repoName, hookOptions)
|
|
||||||
if extra.HasError() {
|
|
||||||
return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error)
|
|
||||||
}
|
|
||||||
count = 0
|
|
||||||
lastline = 0
|
|
||||||
}
|
}
|
||||||
} else {
|
count = 0
|
||||||
fmt.Fprintf(out, ".")
|
|
||||||
}
|
|
||||||
if lastline >= hookBatchSize {
|
|
||||||
fmt.Fprintf(out, "\n")
|
|
||||||
lastline = 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
@@ -288,8 +267,6 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
if extra.HasError() {
|
if extra.HasError() {
|
||||||
return fail(ctx, extra.UserMsg, "HookPreReceive(last) failed: %v", extra.Error)
|
return fail(ctx, extra.UserMsg, "HookPreReceive(last) failed: %v", extra.Error)
|
||||||
}
|
}
|
||||||
} else if lastline > 0 {
|
|
||||||
fmt.Fprintf(out, "\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(out, "Checked %d references in total\n", total)
|
fmt.Fprintf(out, "Checked %d references in total\n", total)
|
||||||
@@ -299,10 +276,6 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
// runHookUpdate avoid to do heavy operations on update hook because it will be
|
// runHookUpdate avoid to do heavy operations on update hook because it will be
|
||||||
// invoked for every ref update which does not like pre-receive and post-receive
|
// invoked for every ref update which does not like pre-receive and post-receive
|
||||||
func runHookUpdate(_ context.Context, c *cli.Command) error {
|
func runHookUpdate(_ context.Context, c *cli.Command) error {
|
||||||
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update is empty and is kept only for backwards compatibility
|
// Update is empty and is kept only for backwards compatibility
|
||||||
if len(os.Args) < 3 {
|
if len(os.Args) < 3 {
|
||||||
return nil
|
return nil
|
||||||
@@ -323,11 +296,6 @@ func runHookPostReceive(ctx context.Context, c *cli.Command) error {
|
|||||||
return fmt.Errorf("failed to call 'git update-server-info': %w", err)
|
return fmt.Errorf("failed to call 'git update-server-info': %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now if we're an internal don't do anything else
|
|
||||||
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
||||||
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
|
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
|
||||||
return fail(ctx, `Rejecting changes as Gitea environment not set.
|
return fail(ctx, `Rejecting changes as Gitea environment not set.
|
||||||
@@ -475,10 +443,6 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !git.DefaultFeatures().SupportProcReceive {
|
|
||||||
return fail(ctx, "No proc-receive support", "current git version doesn't support proc-receive.")
|
|
||||||
}
|
|
||||||
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
repoUser := os.Getenv(repo_module.EnvRepoUsername)
|
repoUser := os.Getenv(repo_module.EnvRepoUsername)
|
||||||
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
|
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
|
||||||
|
|||||||
+4
-6
@@ -195,12 +195,10 @@ func runServ(ctx context.Context, c *cli.Command) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(sshCmdArgs) < 2 {
|
if len(sshCmdArgs) < 2 {
|
||||||
if git.DefaultFeatures().SupportProcReceive {
|
// for AGit Flow
|
||||||
// for AGit Flow
|
if cmd == "ssh_info" {
|
||||||
if cmd == "ssh_info" {
|
cprintf(c, "%s", agit.SshInfoJson)
|
||||||
cprintf(c, "%s", agit.SshInfoJson)
|
return nil
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
|
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-29
@@ -155,7 +155,7 @@
|
|||||||
;; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER.
|
;; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER.
|
||||||
;BUILTIN_SSH_SERVER_USER =
|
;BUILTIN_SSH_SERVER_USER =
|
||||||
;;
|
;;
|
||||||
;; Domain name to be exposed in clone URL, defaults to DOMAIN or the domain part of ROOT_URL
|
;; Domain name to be exposed in clone URL, defaults to the domain part of ROOT_URL
|
||||||
;SSH_DOMAIN =
|
;SSH_DOMAIN =
|
||||||
;;
|
;;
|
||||||
;; Port number to be exposed in clone URL.
|
;; Port number to be exposed in clone URL.
|
||||||
@@ -198,7 +198,7 @@
|
|||||||
;; For the built-in SSH server, choose the keypair to offer as the host key
|
;; For the built-in SSH server, choose the keypair to offer as the host key
|
||||||
;; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub
|
;; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub
|
||||||
;; relative paths are made absolute relative to the APP_DATA_PATH
|
;; relative paths are made absolute relative to the APP_DATA_PATH
|
||||||
;SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gogs.rsa
|
;SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gitea.ed25519, ssh/gitea.ecdsa, ssh/gogs.rsa
|
||||||
;;
|
;;
|
||||||
;; Enable SSH Authorized Key Backup when rewriting all keys, default is false
|
;; Enable SSH Authorized Key Backup when rewriting all keys, default is false
|
||||||
;SSH_AUTHORIZED_KEYS_BACKUP = false
|
;SSH_AUTHORIZED_KEYS_BACKUP = false
|
||||||
@@ -237,7 +237,7 @@
|
|||||||
;SSH_PER_WRITE_PER_KB_TIMEOUT = 30s
|
;SSH_PER_WRITE_PER_KB_TIMEOUT = 30s
|
||||||
;;
|
;;
|
||||||
;; Indicate whether to check minimum key size with corresponding type
|
;; Indicate whether to check minimum key size with corresponding type
|
||||||
;MINIMUM_KEY_SIZE_CHECK = false
|
;MINIMUM_KEY_SIZE_CHECK = true
|
||||||
;;
|
;;
|
||||||
;; TLS Settings: Either ACME or manual
|
;; TLS Settings: Either ACME or manual
|
||||||
;; (Other common TLS configuration are found before)
|
;; (Other common TLS configuration are found before)
|
||||||
@@ -459,7 +459,7 @@ INTERNAL_TOKEN =
|
|||||||
;LOGIN_REMEMBER_DAYS = 31
|
;LOGIN_REMEMBER_DAYS = 31
|
||||||
;;
|
;;
|
||||||
;; Name of cookie used to store authentication information.
|
;; Name of cookie used to store authentication information.
|
||||||
;COOKIE_REMEMBER_NAME = gitea_incredible
|
;COOKIE_REMEMBER_NAME = gitea_remember
|
||||||
;;
|
;;
|
||||||
;; URL or path that Gitea should redirect users to *after* performing its own logout.
|
;; URL or path that Gitea should redirect users to *after* performing its own logout.
|
||||||
;; Use this, if needed, when authentication is handled by a reverse proxy or SSO.
|
;; Use this, if needed, when authentication is handled by a reverse proxy or SSO.
|
||||||
@@ -782,8 +782,6 @@ LEVEL = Info
|
|||||||
;; Respond to pushes to a non-default branch with a URL for creating a Pull Request (if the repository has them enabled)
|
;; Respond to pushes to a non-default branch with a URL for creating a Pull Request (if the repository has them enabled)
|
||||||
;PULL_REQUEST_PUSH_MESSAGE = true
|
;PULL_REQUEST_PUSH_MESSAGE = true
|
||||||
;;
|
;;
|
||||||
;; (Go-Git only) Don't cache objects greater than this in memory. (Set to 0 to disable.)
|
|
||||||
;LARGE_OBJECT_THRESHOLD = 1048576
|
|
||||||
;; Set to true to forcibly set core.protectNTFS=false
|
;; Set to true to forcibly set core.protectNTFS=false
|
||||||
;DISABLE_CORE_PROTECT_NTFS=false
|
;DISABLE_CORE_PROTECT_NTFS=false
|
||||||
;; Disable the usage of using partial clones for git.
|
;; Disable the usage of using partial clones for git.
|
||||||
@@ -810,7 +808,6 @@ LEVEL = Info
|
|||||||
;[git.config]
|
;[git.config]
|
||||||
;diff.algorithm = histogram
|
;diff.algorithm = histogram
|
||||||
;core.logAllRefUpdates = true
|
;core.logAllRefUpdates = true
|
||||||
;gc.reflogExpire = 90
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@@ -838,7 +835,7 @@ LEVEL = Info
|
|||||||
;EMAIL_DOMAIN_BLOCKLIST =
|
;EMAIL_DOMAIN_BLOCKLIST =
|
||||||
;;
|
;;
|
||||||
;; Disallow registration, only allow admins to create accounts.
|
;; Disallow registration, only allow admins to create accounts.
|
||||||
;DISABLE_REGISTRATION = false
|
;DISABLE_REGISTRATION = true
|
||||||
;;
|
;;
|
||||||
;; Allow registration only using gitea itself, it works only when DISABLE_REGISTRATION is false
|
;; Allow registration only using gitea itself, it works only when DISABLE_REGISTRATION is false
|
||||||
;ALLOW_ONLY_INTERNAL_REGISTRATION = false
|
;ALLOW_ONLY_INTERNAL_REGISTRATION = false
|
||||||
@@ -970,12 +967,11 @@ LEVEL = Info
|
|||||||
;; Value for the domain part of the user's email address in the git log if user
|
;; Value for the domain part of the user's email address in the git log if user
|
||||||
;; has set KeepEmailPrivate to true. The user's email will be replaced with a
|
;; has set KeepEmailPrivate to true. The user's email will be replaced with a
|
||||||
;; concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS. Default
|
;; concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS. Default
|
||||||
;; value is "noreply." + DOMAIN, where DOMAIN resolves to the value from server.DOMAIN
|
;; value is "noreply." + the domain part of ROOT_URL
|
||||||
;; Note: do not use the <DOMAIN> notation below
|
;NO_REPLY_ADDRESS =
|
||||||
;NO_REPLY_ADDRESS = ; noreply.<DOMAIN>
|
|
||||||
;;
|
;;
|
||||||
;; Show Registration button
|
;; Show Registration button, defaults to true only if both DISABLE_REGISTRATION and ALLOW_ONLY_EXTERNAL_REGISTRATION are false
|
||||||
;SHOW_REGISTRATION_BUTTON = true
|
;SHOW_REGISTRATION_BUTTON = false
|
||||||
;;
|
;;
|
||||||
;; Show milestones dashboard page - a view of all the user's milestones
|
;; Show milestones dashboard page - a view of all the user's milestones
|
||||||
;SHOW_MILESTONES_DASHBOARD_PAGE = true
|
;SHOW_MILESTONES_DASHBOARD_PAGE = true
|
||||||
@@ -1098,8 +1094,9 @@ LEVEL = Info
|
|||||||
;; Force ssh:// clone url instead of scp-style uri when default SSH port is used
|
;; Force ssh:// clone url instead of scp-style uri when default SSH port is used
|
||||||
;USE_COMPAT_SSH_URI = false
|
;USE_COMPAT_SSH_URI = false
|
||||||
;;
|
;;
|
||||||
;; Value for the "go get" request returns the repository url as https or ssh, default is https
|
;; Scheme of the returned URL for the "go get" response.
|
||||||
;GO_GET_CLONE_URL_PROTOCOL = https
|
;; Default is "https" if DISABLE_HTTP_GIT=false or SSH is disabled, otherwise "ssh".
|
||||||
|
;GO_GET_CLONE_URL_PROTOCOL =
|
||||||
;;
|
;;
|
||||||
;; Close issues as long as a commit on any branch marks it as fixed
|
;; Close issues as long as a commit on any branch marks it as fixed
|
||||||
;DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false
|
;DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false
|
||||||
@@ -1672,13 +1669,13 @@ LEVEL = Info
|
|||||||
;;
|
;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;
|
;;
|
||||||
;; General queue queue type, currently support: persistable-channel, channel, level, redis, dummy
|
;; General queue type, currently support: level, channel, redis, dummy
|
||||||
;; default to persistable-channel
|
;; default to level
|
||||||
;TYPE = persistable-channel
|
;TYPE = level
|
||||||
;;
|
;;
|
||||||
;; data-dir for storing persistable queues and level queues, individual queues will default to `queues/common` meaning the queue is shared.
|
;; data-dir for storing level queues, individual queues will default to `queues/common` meaning the queue is shared.
|
||||||
;; Relative paths will be made absolute against "APP_DATA_PATH"
|
;; Relative paths will be made absolute against "APP_DATA_PATH"
|
||||||
;DATADIR = queues/
|
;DATADIR = queues/common
|
||||||
;;
|
;;
|
||||||
;; Default queue length before a channel queue will block
|
;; Default queue length before a channel queue will block
|
||||||
;LENGTH = 100000
|
;LENGTH = 100000
|
||||||
@@ -1686,7 +1683,7 @@ LEVEL = Info
|
|||||||
;; Batch size to send for batched queues
|
;; Batch size to send for batched queues
|
||||||
;BATCH_LENGTH = 20
|
;BATCH_LENGTH = 20
|
||||||
;;
|
;;
|
||||||
;; When `TYPE` is `persistable-channel`, this provides a directory for the underlying leveldb
|
;; When `TYPE` is `level`, this provides a directory for the underlying leveldb
|
||||||
;; or additional options of the form `leveldb://path/to/db?option=value&....`, and will override `DATADIR`.
|
;; or additional options of the form `leveldb://path/to/db?option=value&....`, and will override `DATADIR`.
|
||||||
;; When `TYPE` is `redis` and this is left empty, it falls back to the shared [redis] CONN_STR.
|
;; When `TYPE` is `redis` and this is left empty, it falls back to the shared [redis] CONN_STR.
|
||||||
;CONN_STR =
|
;CONN_STR =
|
||||||
@@ -1754,7 +1751,6 @@ LEVEL = Info
|
|||||||
;ENABLE_OPENID_SIGNIN = false
|
;ENABLE_OPENID_SIGNIN = false
|
||||||
;;
|
;;
|
||||||
;; Whether to allow registering via OpenID
|
;; Whether to allow registering via OpenID
|
||||||
;; Do not include to rely on rhw DISABLE_REGISTRATION setting
|
|
||||||
;;ENABLE_OPENID_SIGNUP = false
|
;;ENABLE_OPENID_SIGNUP = false
|
||||||
;;
|
;;
|
||||||
;; Allowed URI patterns (POSIX regexp).
|
;; Allowed URI patterns (POSIX regexp).
|
||||||
@@ -1985,7 +1981,7 @@ LEVEL = Info
|
|||||||
;; Either "memory", "redis", "memcache", or "twoqueue". default is "memory"
|
;; Either "memory", "redis", "memcache", or "twoqueue". default is "memory"
|
||||||
;ADAPTER = memory
|
;ADAPTER = memory
|
||||||
;;
|
;;
|
||||||
;; For "memory" only, GC interval in seconds, default is 60
|
;; For "memory" and "twoqueue", GC interval in seconds, default is 60
|
||||||
;INTERVAL = 60
|
;INTERVAL = 60
|
||||||
;;
|
;;
|
||||||
;; For "redis" and "memcache", connection host address
|
;; For "redis" and "memcache", connection host address
|
||||||
@@ -2016,19 +2012,17 @@ LEVEL = Info
|
|||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;
|
;;
|
||||||
;; Either "memory", "file", "redis", "db", "mysql", "couchbase", "memcache" or "postgres"
|
;; Either "memory", "file", "redis" or "db", default is "file". "db" will reuse the configuration in [database]
|
||||||
;; Default is "memory". "db" will reuse the configuration in [database]
|
;PROVIDER = file
|
||||||
;PROVIDER = memory
|
|
||||||
;;
|
;;
|
||||||
;; Provider config options
|
;; Provider config options
|
||||||
;; memory: doesn't have any config yet
|
;; memory: doesn't have any config yet
|
||||||
;; file: session file path, e.g. `data/sessions`, relative paths will be made absolute against _`AppWorkPath`_.
|
;; file: session file path, e.g. `data/sessions`, relative paths will be made absolute against _`AppWorkPath`_.
|
||||||
;; redis: default to [redis] CONN_STR
|
;; redis: default to [redis] CONN_STR
|
||||||
;; mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table`
|
|
||||||
;PROVIDER_CONFIG =
|
;PROVIDER_CONFIG =
|
||||||
;;
|
;;
|
||||||
;; Session cookie name
|
;; Session cookie name
|
||||||
;COOKIE_NAME = i_like_gitea
|
;COOKIE_NAME = gitea_session
|
||||||
;;
|
;;
|
||||||
;; If you use session in https only: true or false. If not set, it defaults to `true` if the ROOT_URL is an HTTPS URL.
|
;; If you use session in https only: true or false. If not set, it defaults to `true` if the ROOT_URL is an HTTPS URL.
|
||||||
;COOKIE_SECURE =
|
;COOKIE_SECURE =
|
||||||
@@ -2054,7 +2048,8 @@ LEVEL = Info
|
|||||||
;; How Gitea deals with missing repository avatars
|
;; How Gitea deals with missing repository avatars
|
||||||
;; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used
|
;; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used
|
||||||
;REPOSITORY_AVATAR_FALLBACK = none
|
;REPOSITORY_AVATAR_FALLBACK = none
|
||||||
;REPOSITORY_AVATAR_FALLBACK_IMAGE = /img/repo_default.png
|
;; Image URL for the "image" fallback, used as-is, defaults to Gitea's builtin repository avatar
|
||||||
|
;REPOSITORY_AVATAR_FALLBACK_IMAGE =
|
||||||
;;
|
;;
|
||||||
;; Max Width and Height of uploaded avatars.
|
;; Max Width and Height of uploaded avatars.
|
||||||
;; This is to limit the amount of RAM used when resizing the image.
|
;; This is to limit the amount of RAM used when resizing the image.
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ Depending on requirements, the following build tags can be included.
|
|||||||
- `bindata`: Build a single monolithic binary, with all assets included. Required for distribution and production build.
|
- `bindata`: Build a single monolithic binary, with all assets included. Required for distribution and production build.
|
||||||
- `pam`: Enable support for PAM (Linux Pluggable Authentication Modules).
|
- `pam`: Enable support for PAM (Linux Pluggable Authentication Modules).
|
||||||
Can be used to authenticate local users or extend authentication to methods available to PAM.
|
Can be used to authenticate local users or extend authentication to methods available to PAM.
|
||||||
- `gogit`: (EXPERIMENTAL) Use go-git variants of Git commands.
|
|
||||||
|
|
||||||
To include all assets, use the `bindata` tag:
|
To include all assets, use the `bindata` tag:
|
||||||
|
|
||||||
@@ -33,13 +32,6 @@ To include all assets, use the `bindata` tag:
|
|||||||
TAGS="bindata" make build
|
TAGS="bindata" make build
|
||||||
```
|
```
|
||||||
|
|
||||||
Tag `gogit` is used to try to resolve some Windows-specific performance problems, POSIX systems don't need it.
|
|
||||||
You can build a Windows binary by:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
GOOS=windows TAGS="bindata gogit" make build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Changing default paths
|
## Changing default paths
|
||||||
|
|
||||||
Gitea will search for a number of things from the _`CustomPath`_.
|
Gitea will search for a number of things from the _`CustomPath`_.
|
||||||
|
|||||||
+49
-29
@@ -17,6 +17,7 @@ import unescapedHtmlLiteral from './tools/eslint-rules/unescaped-html-literal.ts
|
|||||||
|
|
||||||
const jsExts = ['js', 'mjs', 'cjs'] as const;
|
const jsExts = ['js', 'mjs', 'cjs'] as const;
|
||||||
const tsExts = ['ts', 'mts', 'cts'] as const;
|
const tsExts = ['ts', 'mts', 'cts'] as const;
|
||||||
|
const vueExts = ['vue'] as const;
|
||||||
|
|
||||||
const restrictedGlobals = [
|
const restrictedGlobals = [
|
||||||
{name: 'localStorage', message: 'Use `modules/user-settings.ts` instead.'},
|
{name: 'localStorage', message: 'Use `modules/user-settings.ts` instead.'},
|
||||||
@@ -36,7 +37,7 @@ export default defineConfig([
|
|||||||
'public/assets/js',
|
'public/assets/js',
|
||||||
]),
|
]),
|
||||||
{
|
{
|
||||||
files: [`**/*.{${[...jsExts, ...tsExts].join(',')}}`],
|
files: [`**/*.{${[...jsExts, ...tsExts, ...vueExts].join(',')}}`],
|
||||||
ignores: ['dist/*'],
|
ignores: ['dist/*'],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
ecmaVersion: 'latest',
|
ecmaVersion: 'latest',
|
||||||
@@ -48,7 +49,9 @@ export default defineConfig([
|
|||||||
ecmaFeatures: {
|
ecmaFeatures: {
|
||||||
impliedStrict: true,
|
impliedStrict: true,
|
||||||
},
|
},
|
||||||
|
parser: typescriptParser,
|
||||||
project: true,
|
project: true,
|
||||||
|
extraFileExtensions: vueExts.map((ext) => `.${ext}`),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
linterOptions: {
|
linterOptions: {
|
||||||
@@ -66,8 +69,11 @@ export default defineConfig([
|
|||||||
wc,
|
wc,
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
'import-x/extensions': [...jsExts, ...tsExts].map((ext) => `.${ext}`),
|
'import-x/extensions': [...jsExts, ...tsExts, ...vueExts].map((ext) => `.${ext}`),
|
||||||
'import-x/parsers': {'@typescript-eslint/parser': [...jsExts, ...tsExts].map((ext) => `.${ext}`)},
|
'import-x/parsers': {
|
||||||
|
'@typescript-eslint/parser': [...jsExts, ...tsExts].map((ext) => `.${ext}`),
|
||||||
|
'vue-eslint-parser': vueExts.map((ext) => `.${ext}`),
|
||||||
|
},
|
||||||
'import-x/resolver': {'typescript': true},
|
'import-x/resolver': {'typescript': true},
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
@@ -233,6 +239,7 @@ export default defineConfig([
|
|||||||
'@typescript-eslint/no-unsafe-assignment': [0],
|
'@typescript-eslint/no-unsafe-assignment': [0],
|
||||||
'@typescript-eslint/no-unsafe-call': [0],
|
'@typescript-eslint/no-unsafe-call': [0],
|
||||||
'@typescript-eslint/no-unsafe-declaration-merging': [2],
|
'@typescript-eslint/no-unsafe-declaration-merging': [2],
|
||||||
|
'@typescript-eslint/no-unsafe-enum-assignment': [0],
|
||||||
'@typescript-eslint/no-unsafe-enum-comparison': [0],
|
'@typescript-eslint/no-unsafe-enum-comparison': [0],
|
||||||
'@typescript-eslint/no-unsafe-function-type': [2],
|
'@typescript-eslint/no-unsafe-function-type': [2],
|
||||||
'@typescript-eslint/no-unsafe-member-access': [0],
|
'@typescript-eslint/no-unsafe-member-access': [0],
|
||||||
@@ -704,6 +711,7 @@ export default defineConfig([
|
|||||||
'unicorn/better-dom-traversing': [2],
|
'unicorn/better-dom-traversing': [2],
|
||||||
'unicorn/catch-error-name': [0],
|
'unicorn/catch-error-name': [0],
|
||||||
'unicorn/class-reference-in-static-methods': [2],
|
'unicorn/class-reference-in-static-methods': [2],
|
||||||
|
'unicorn/comma-spacing': [0], // only applies to json language
|
||||||
'unicorn/comment-content': [0],
|
'unicorn/comment-content': [0],
|
||||||
'unicorn/consistent-arrow-return-style': [0],
|
'unicorn/consistent-arrow-return-style': [0],
|
||||||
'unicorn/consistent-assert': [0],
|
'unicorn/consistent-assert': [0],
|
||||||
@@ -734,8 +742,10 @@ export default defineConfig([
|
|||||||
'unicorn/filename-case': [0],
|
'unicorn/filename-case': [0],
|
||||||
'unicorn/id-match': [2],
|
'unicorn/id-match': [2],
|
||||||
'unicorn/import-style': [0],
|
'unicorn/import-style': [0],
|
||||||
|
'unicorn/indent': [0], // only applies to json and css languages
|
||||||
'unicorn/isolated-functions': [2, {functions: []}],
|
'unicorn/isolated-functions': [2, {functions: []}],
|
||||||
'unicorn/iteration-fallback-style': [2, 'fallback'],
|
'unicorn/iteration-fallback-style': [2, 'fallback'],
|
||||||
|
'unicorn/key-name-casing': [0], // only applies to json, yaml and toml languages
|
||||||
'unicorn/logical-assignment-operators': [0],
|
'unicorn/logical-assignment-operators': [0],
|
||||||
'unicorn/max-nested-calls': [0],
|
'unicorn/max-nested-calls': [0],
|
||||||
'unicorn/name-replacements': [0],
|
'unicorn/name-replacements': [0],
|
||||||
@@ -768,21 +778,20 @@ export default defineConfig([
|
|||||||
'unicorn/no-chained-comparison': [2],
|
'unicorn/no-chained-comparison': [2],
|
||||||
'unicorn/no-collection-bracket-access': [2],
|
'unicorn/no-collection-bracket-access': [2],
|
||||||
'unicorn/no-computed-property-existence-check': [0],
|
'unicorn/no-computed-property-existence-check': [0],
|
||||||
|
'unicorn/no-conflicting-constraints': [2],
|
||||||
'unicorn/no-confusing-array-splice': [2],
|
'unicorn/no-confusing-array-splice': [2],
|
||||||
'unicorn/no-confusing-array-with': [2],
|
'unicorn/no-confusing-array-with': [2],
|
||||||
'unicorn/no-console-spaces': [0],
|
'unicorn/no-console-spaces': [0],
|
||||||
'unicorn/no-constant-zero-expression': [2],
|
'unicorn/no-constant-zero-expression': [2],
|
||||||
'unicorn/no-declarations-before-early-exit': [0],
|
'unicorn/no-declarations-before-early-exit': [0],
|
||||||
'unicorn/no-deprecated-css-features': [0],
|
|
||||||
'unicorn/no-document-cookie': [2],
|
'unicorn/no-document-cookie': [2],
|
||||||
'unicorn/no-double-comparison': [2],
|
'unicorn/no-double-comparison': [2],
|
||||||
'unicorn/no-duplicate-css-selectors': [0],
|
|
||||||
'unicorn/no-duplicate-font-family-names': [0],
|
|
||||||
'unicorn/no-duplicate-if-branches': [2],
|
'unicorn/no-duplicate-if-branches': [2],
|
||||||
'unicorn/no-duplicate-logical-operands': [2],
|
'unicorn/no-duplicate-logical-operands': [2],
|
||||||
'unicorn/no-duplicate-loops': [0],
|
'unicorn/no-duplicate-loops': [0],
|
||||||
'unicorn/no-duplicate-set-values': [2],
|
'unicorn/no-duplicate-set-values': [2],
|
||||||
'unicorn/no-empty-file': [2],
|
'unicorn/no-empty-file': [2],
|
||||||
|
'unicorn/no-empty-link-text': [0], // only applies to markdown language
|
||||||
'unicorn/no-error-property-assignment': [2],
|
'unicorn/no-error-property-assignment': [2],
|
||||||
'unicorn/no-exports-in-scripts': [2],
|
'unicorn/no-exports-in-scripts': [2],
|
||||||
'unicorn/no-for-each': [2],
|
'unicorn/no-for-each': [2],
|
||||||
@@ -790,21 +799,34 @@ export default defineConfig([
|
|||||||
'unicorn/no-global-object-property-assignment': [0],
|
'unicorn/no-global-object-property-assignment': [0],
|
||||||
'unicorn/no-immediate-mutation': [0],
|
'unicorn/no-immediate-mutation': [0],
|
||||||
'unicorn/no-impossible-length-comparison': [2],
|
'unicorn/no-impossible-length-comparison': [2],
|
||||||
|
'unicorn/no-incomplete-accessor-override': [2],
|
||||||
'unicorn/no-incorrect-query-selector': [2],
|
'unicorn/no-incorrect-query-selector': [2],
|
||||||
'unicorn/no-incorrect-template-string-interpolation': [0],
|
'unicorn/no-incorrect-template-string-interpolation': [0],
|
||||||
|
'unicorn/no-ineffective-csp-directives': [2],
|
||||||
'unicorn/no-instanceof-builtins': [2],
|
'unicorn/no-instanceof-builtins': [2],
|
||||||
'unicorn/no-invalid-argument-count': [2],
|
'unicorn/no-invalid-argument-count': [2],
|
||||||
|
'unicorn/no-invalid-boolean-attribute-value': [2],
|
||||||
'unicorn/no-invalid-character-comparison': [2],
|
'unicorn/no-invalid-character-comparison': [2],
|
||||||
|
'unicorn/no-invalid-dom-token': [2],
|
||||||
'unicorn/no-invalid-fetch-options': [2],
|
'unicorn/no-invalid-fetch-options': [2],
|
||||||
'unicorn/no-invalid-file-input-accept': [2],
|
'unicorn/no-invalid-file-input-accept': [2],
|
||||||
'unicorn/no-invalid-media-features': [0],
|
'unicorn/no-invalid-integrity': [2],
|
||||||
|
'unicorn/no-invalid-intl-options': [2],
|
||||||
|
'unicorn/no-invalid-property-descriptor': [2],
|
||||||
'unicorn/no-invalid-remove-event-listener': [2],
|
'unicorn/no-invalid-remove-event-listener': [2],
|
||||||
|
'unicorn/no-invalid-response-options': [2],
|
||||||
|
'unicorn/no-invalid-style-set-property': [2],
|
||||||
|
'unicorn/no-invalid-temporal-arithmetic': [2],
|
||||||
|
'unicorn/no-invalid-url-protocol-comparison': [2],
|
||||||
'unicorn/no-invalid-well-known-symbol-methods': [2],
|
'unicorn/no-invalid-well-known-symbol-methods': [2],
|
||||||
|
'unicorn/no-javascript-url': [0], // only applies to markdown language
|
||||||
'unicorn/no-keyword-prefix': [0],
|
'unicorn/no-keyword-prefix': [0],
|
||||||
'unicorn/no-late-current-target-access': [2],
|
'unicorn/no-late-current-target-access': [2],
|
||||||
'unicorn/no-late-event-control': [2],
|
'unicorn/no-late-event-control': [2],
|
||||||
|
'unicorn/no-leading-empty-lines': [0], // handled by @stylistic/no-multiple-empty-lines
|
||||||
'unicorn/no-lonely-if': [2],
|
'unicorn/no-lonely-if': [2],
|
||||||
'unicorn/no-loop-iterable-mutation': [2],
|
'unicorn/no-loop-iterable-mutation': [2],
|
||||||
|
'unicorn/no-loss-of-precision': [0], // only applies to json, toml and css languages
|
||||||
'unicorn/no-magic-array-flat-depth': [0],
|
'unicorn/no-magic-array-flat-depth': [0],
|
||||||
'unicorn/no-manually-wrapped-comments': [0], // too opinionated
|
'unicorn/no-manually-wrapped-comments': [0], // too opinionated
|
||||||
'unicorn/no-mismatched-map-key': [2],
|
'unicorn/no-mismatched-map-key': [2],
|
||||||
@@ -817,7 +839,6 @@ export default defineConfig([
|
|||||||
'unicorn/no-negated-condition': [0],
|
'unicorn/no-negated-condition': [0],
|
||||||
'unicorn/no-negation-in-equality-check': [2],
|
'unicorn/no-negation-in-equality-check': [2],
|
||||||
'unicorn/no-nested-ternary': [0],
|
'unicorn/no-nested-ternary': [0],
|
||||||
'unicorn/no-nesting-with-mixed-specificity': [0],
|
|
||||||
'unicorn/no-new-array': [0],
|
'unicorn/no-new-array': [0],
|
||||||
'unicorn/no-new-buffer': [2],
|
'unicorn/no-new-buffer': [2],
|
||||||
'unicorn/no-non-function-verb-prefix': [0],
|
'unicorn/no-non-function-verb-prefix': [0],
|
||||||
@@ -826,9 +847,9 @@ export default defineConfig([
|
|||||||
'unicorn/no-object-as-default-parameter': [0],
|
'unicorn/no-object-as-default-parameter': [0],
|
||||||
'unicorn/no-object-methods-with-collections': [2],
|
'unicorn/no-object-methods-with-collections': [2],
|
||||||
'unicorn/no-optional-chaining-on-undeclared-variable': [2],
|
'unicorn/no-optional-chaining-on-undeclared-variable': [2],
|
||||||
|
'unicorn/no-prevent-default-in-passive-listener': [2],
|
||||||
'unicorn/no-process-exit': [0],
|
'unicorn/no-process-exit': [0],
|
||||||
'unicorn/no-redundant-comparison': [2],
|
'unicorn/no-redundant-comparison': [2],
|
||||||
'unicorn/no-redundant-nested-style-rules': [0],
|
|
||||||
'unicorn/no-return-array-push': [2],
|
'unicorn/no-return-array-push': [2],
|
||||||
'unicorn/no-selector-as-dom-name': [2],
|
'unicorn/no-selector-as-dom-name': [2],
|
||||||
'unicorn/no-shorthand-property-overrides': [0], // only applies to css language
|
'unicorn/no-shorthand-property-overrides': [0], // only applies to css language
|
||||||
@@ -844,8 +865,6 @@ export default defineConfig([
|
|||||||
'unicorn/no-typeof-undefined': [2],
|
'unicorn/no-typeof-undefined': [2],
|
||||||
'unicorn/no-uncalled-method': [2],
|
'unicorn/no-uncalled-method': [2],
|
||||||
'unicorn/no-undeclared-class-members': [2],
|
'unicorn/no-undeclared-class-members': [2],
|
||||||
'unicorn/no-unknown-css-annotations': [0],
|
|
||||||
'unicorn/no-unknown-pseudo-selectors': [0],
|
|
||||||
'unicorn/no-unnecessary-array-flat-depth': [2],
|
'unicorn/no-unnecessary-array-flat-depth': [2],
|
||||||
'unicorn/no-unnecessary-array-flat-map': [2],
|
'unicorn/no-unnecessary-array-flat-map': [2],
|
||||||
'unicorn/no-unnecessary-array-splice-count': [2],
|
'unicorn/no-unnecessary-array-splice-count': [2],
|
||||||
@@ -854,6 +873,7 @@ export default defineConfig([
|
|||||||
'unicorn/no-unnecessary-fetch-options': [0],
|
'unicorn/no-unnecessary-fetch-options': [0],
|
||||||
'unicorn/no-unnecessary-global-this': [0],
|
'unicorn/no-unnecessary-global-this': [0],
|
||||||
'unicorn/no-unnecessary-nested-ternary': [2],
|
'unicorn/no-unnecessary-nested-ternary': [2],
|
||||||
|
'unicorn/no-unnecessary-parameters': [0],
|
||||||
'unicorn/no-unnecessary-polyfills': [2],
|
'unicorn/no-unnecessary-polyfills': [2],
|
||||||
'unicorn/no-unnecessary-slice-end': [2],
|
'unicorn/no-unnecessary-slice-end': [2],
|
||||||
'unicorn/no-unnecessary-splice': [2],
|
'unicorn/no-unnecessary-splice': [2],
|
||||||
@@ -865,14 +885,15 @@ export default defineConfig([
|
|||||||
'unicorn/no-unreadable-object-destructuring': [0],
|
'unicorn/no-unreadable-object-destructuring': [0],
|
||||||
'unicorn/no-unsafe-buffer-conversion': [2],
|
'unicorn/no-unsafe-buffer-conversion': [2],
|
||||||
'unicorn/no-unsafe-dom-html': [0],
|
'unicorn/no-unsafe-dom-html': [0],
|
||||||
|
'unicorn/no-unsafe-json-serialization': [2],
|
||||||
'unicorn/no-unsafe-promise-all-settled-values': [2],
|
'unicorn/no-unsafe-promise-all-settled-values': [2],
|
||||||
'unicorn/no-unsafe-property-key': [0],
|
'unicorn/no-unsafe-property-key': [0],
|
||||||
'unicorn/no-unsafe-sqlite-interpolation': [2],
|
'unicorn/no-unsafe-sqlite-interpolation': [2],
|
||||||
'unicorn/no-unsafe-string-replacement': [2],
|
'unicorn/no-unsafe-string-replacement': [2],
|
||||||
'unicorn/no-unscoped-css-nesting-selector': [0],
|
|
||||||
'unicorn/no-unused-builtin-method-return': [2],
|
'unicorn/no-unused-builtin-method-return': [2],
|
||||||
'unicorn/no-unused-iterator-helper': [2],
|
'unicorn/no-unused-iterator-helper': [2],
|
||||||
'unicorn/no-unused-properties': [2],
|
'unicorn/no-unused-properties': [2],
|
||||||
|
'unicorn/no-url-in-search-params': [2],
|
||||||
'unicorn/no-useless-boolean-cast': [2],
|
'unicorn/no-useless-boolean-cast': [2],
|
||||||
'unicorn/no-useless-coercion': [2],
|
'unicorn/no-useless-coercion': [2],
|
||||||
'unicorn/no-useless-collection-argument': [2],
|
'unicorn/no-useless-collection-argument': [2],
|
||||||
@@ -909,12 +930,12 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-array-find': [0], // handled by @typescript-eslint/prefer-find
|
'unicorn/prefer-array-find': [0], // handled by @typescript-eslint/prefer-find
|
||||||
'unicorn/prefer-array-flat': [2],
|
'unicorn/prefer-array-flat': [2],
|
||||||
'unicorn/prefer-array-flat-map': [2],
|
'unicorn/prefer-array-flat-map': [2],
|
||||||
'unicorn/prefer-array-from-async': [2],
|
'unicorn/prefer-array-from-async': [0], // Array.fromAsync requires ES2026
|
||||||
'unicorn/prefer-array-from-map': [2],
|
'unicorn/prefer-array-from-map': [2],
|
||||||
'unicorn/prefer-array-from-range': [2],
|
'unicorn/prefer-array-from-range': [2],
|
||||||
'unicorn/prefer-array-index-of': [2],
|
'unicorn/prefer-array-index-of': [2],
|
||||||
'unicorn/prefer-array-iterable-methods': [2],
|
'unicorn/prefer-array-iterable-methods': [2],
|
||||||
'unicorn/prefer-array-last-methods': [2],
|
'unicorn/prefer-array-last-methods': [0], // Array#findLast/findLastIndex requires ES2023
|
||||||
'unicorn/prefer-array-slice': [2],
|
'unicorn/prefer-array-slice': [2],
|
||||||
'unicorn/prefer-array-some': [2],
|
'unicorn/prefer-array-some': [2],
|
||||||
'unicorn/prefer-at': [0],
|
'unicorn/prefer-at': [0],
|
||||||
@@ -931,7 +952,7 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-date-now': [2],
|
'unicorn/prefer-date-now': [2],
|
||||||
'unicorn/prefer-default-parameters': [0],
|
'unicorn/prefer-default-parameters': [0],
|
||||||
'unicorn/prefer-direct-iteration': [2],
|
'unicorn/prefer-direct-iteration': [2],
|
||||||
'unicorn/prefer-dispose': [2],
|
'unicorn/prefer-dispose': [0], // `using` requires ES2027
|
||||||
'unicorn/prefer-dom-node-append': [2],
|
'unicorn/prefer-dom-node-append': [2],
|
||||||
'unicorn/prefer-dom-node-html-methods': [0],
|
'unicorn/prefer-dom-node-html-methods': [0],
|
||||||
'unicorn/prefer-dom-node-remove': [2],
|
'unicorn/prefer-dom-node-remove': [2],
|
||||||
@@ -940,14 +961,14 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-early-return': [0],
|
'unicorn/prefer-early-return': [0],
|
||||||
'unicorn/prefer-else-if': [2],
|
'unicorn/prefer-else-if': [2],
|
||||||
'unicorn/prefer-error-is-error': [0],
|
'unicorn/prefer-error-is-error': [0],
|
||||||
|
'unicorn/prefer-escaped-irregular-whitespace': [2],
|
||||||
'unicorn/prefer-event-target': [2],
|
'unicorn/prefer-event-target': [2],
|
||||||
'unicorn/prefer-explicit-viewport-units': [0], // only applies to css language
|
|
||||||
'unicorn/prefer-export-from': [0],
|
'unicorn/prefer-export-from': [0],
|
||||||
'unicorn/prefer-flat-math-min-max': [2],
|
'unicorn/prefer-flat-math-min-max': [2],
|
||||||
'unicorn/prefer-get-or-insert-computed': [2],
|
'unicorn/prefer-get-or-insert-computed': [0], // Map#getOrInsertComputed requires ES2026
|
||||||
'unicorn/prefer-global-number-constants': [2],
|
'unicorn/prefer-global-number-constants': [2],
|
||||||
'unicorn/prefer-global-this': [0],
|
'unicorn/prefer-global-this': [0],
|
||||||
'unicorn/prefer-group-by': [2],
|
'unicorn/prefer-group-by': [0], // Object.groupBy/Map.groupBy requires ES2024
|
||||||
'unicorn/prefer-has-check': [2],
|
'unicorn/prefer-has-check': [2],
|
||||||
'unicorn/prefer-hoisting-branch-code': [2],
|
'unicorn/prefer-hoisting-branch-code': [2],
|
||||||
'unicorn/prefer-https': [0], // false-positives on namespace and schema URIs
|
'unicorn/prefer-https': [0], // false-positives on namespace and schema URIs
|
||||||
@@ -958,11 +979,12 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-iterable-in-constructor': [2],
|
'unicorn/prefer-iterable-in-constructor': [2],
|
||||||
'unicorn/prefer-iterator-concat': [0], // too opinionated
|
'unicorn/prefer-iterator-concat': [0], // too opinionated
|
||||||
'unicorn/prefer-iterator-helpers': [0],
|
'unicorn/prefer-iterator-helpers': [0],
|
||||||
'unicorn/prefer-iterator-to-array': [2],
|
'unicorn/prefer-iterator-to-array': [0], // Iterator#toArray requires ES2025
|
||||||
'unicorn/prefer-iterator-to-array-at-end': [2],
|
'unicorn/prefer-iterator-to-array-at-end': [2],
|
||||||
'unicorn/prefer-iterator-zip': [0],
|
'unicorn/prefer-iterator-zip': [0],
|
||||||
'unicorn/prefer-json-import': [0],
|
'unicorn/prefer-json-import': [0],
|
||||||
'unicorn/prefer-keyboard-event-key': [2],
|
'unicorn/prefer-keyboard-event-key': [2],
|
||||||
|
'unicorn/prefer-literal-ascii': [2],
|
||||||
'unicorn/prefer-location-assign': [2],
|
'unicorn/prefer-location-assign': [2],
|
||||||
'unicorn/prefer-logical-operator-over-ternary': [0],
|
'unicorn/prefer-logical-operator-over-ternary': [0],
|
||||||
'unicorn/prefer-map-from-entries': [0],
|
'unicorn/prefer-map-from-entries': [0],
|
||||||
@@ -970,7 +992,6 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-math-constants': [2],
|
'unicorn/prefer-math-constants': [2],
|
||||||
'unicorn/prefer-math-min-max': [2],
|
'unicorn/prefer-math-min-max': [2],
|
||||||
'unicorn/prefer-math-trunc': [2],
|
'unicorn/prefer-math-trunc': [2],
|
||||||
'unicorn/prefer-media-feature-range-syntax': [0],
|
|
||||||
'unicorn/prefer-minimal-ternary': [0],
|
'unicorn/prefer-minimal-ternary': [0],
|
||||||
'unicorn/prefer-modern-dom-apis': [0],
|
'unicorn/prefer-modern-dom-apis': [0],
|
||||||
'unicorn/prefer-modern-math-apis': [2],
|
'unicorn/prefer-modern-math-apis': [2],
|
||||||
@@ -989,8 +1010,9 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-optional-catch-binding': [2],
|
'unicorn/prefer-optional-catch-binding': [2],
|
||||||
'unicorn/prefer-path2d': [2],
|
'unicorn/prefer-path2d': [2],
|
||||||
'unicorn/prefer-private-class-fields': [0],
|
'unicorn/prefer-private-class-fields': [0],
|
||||||
'unicorn/prefer-promise-try': [2],
|
'unicorn/prefer-promise-static-methods': [2],
|
||||||
'unicorn/prefer-promise-with-resolvers': [2],
|
'unicorn/prefer-promise-try': [0], // Promise.try requires ES2025
|
||||||
|
'unicorn/prefer-promise-with-resolvers': [0], // Promise.withResolvers requires ES2024
|
||||||
'unicorn/prefer-prototype-methods': [2],
|
'unicorn/prefer-prototype-methods': [2],
|
||||||
'unicorn/prefer-query-selector': [2],
|
'unicorn/prefer-query-selector': [2],
|
||||||
'unicorn/prefer-queue-microtask': [2],
|
'unicorn/prefer-queue-microtask': [2],
|
||||||
@@ -1003,6 +1025,7 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-set-methods': [0],
|
'unicorn/prefer-set-methods': [0],
|
||||||
'unicorn/prefer-set-size': [2],
|
'unicorn/prefer-set-size': [2],
|
||||||
'unicorn/prefer-short-arrow-method': [2],
|
'unicorn/prefer-short-arrow-method': [2],
|
||||||
|
'unicorn/prefer-short-escape-sequences': [2],
|
||||||
'unicorn/prefer-simple-condition-first': [0],
|
'unicorn/prefer-simple-condition-first': [0],
|
||||||
'unicorn/prefer-simple-sort-comparator': [2],
|
'unicorn/prefer-simple-sort-comparator': [2],
|
||||||
'unicorn/prefer-simplified-conditions': [2],
|
'unicorn/prefer-simplified-conditions': [2],
|
||||||
@@ -1024,7 +1047,7 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-structured-clone': [2],
|
'unicorn/prefer-structured-clone': [2],
|
||||||
'unicorn/prefer-switch': [0],
|
'unicorn/prefer-switch': [0],
|
||||||
'unicorn/prefer-temporal': [0],
|
'unicorn/prefer-temporal': [0],
|
||||||
'unicorn/prefer-temporal-conversion': [2],
|
'unicorn/prefer-temporal-conversion': [0], // Temporal requires ES2027
|
||||||
'unicorn/prefer-ternary': [0],
|
'unicorn/prefer-ternary': [0],
|
||||||
'unicorn/prefer-then-catch': [2],
|
'unicorn/prefer-then-catch': [2],
|
||||||
'unicorn/prefer-toggle-attribute': [2],
|
'unicorn/prefer-toggle-attribute': [2],
|
||||||
@@ -1050,6 +1073,7 @@ export default defineConfig([
|
|||||||
'unicorn/require-passive-events': [2],
|
'unicorn/require-passive-events': [2],
|
||||||
'unicorn/require-post-message-target-origin': [0],
|
'unicorn/require-post-message-target-origin': [0],
|
||||||
'unicorn/require-proxy-trap-boolean-return': [2],
|
'unicorn/require-proxy-trap-boolean-return': [2],
|
||||||
|
'unicorn/require-text-decoder-streaming': [2],
|
||||||
'unicorn/single-line-block-comment-style': [0],
|
'unicorn/single-line-block-comment-style': [0],
|
||||||
'unicorn/string-content': [0],
|
'unicorn/string-content': [0],
|
||||||
'unicorn/switch-case-braces': [0],
|
'unicorn/switch-case-braces': [0],
|
||||||
@@ -1096,17 +1120,13 @@ export default defineConfig([
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.vue'],
|
files: vueExts.map((ext) => `**/*.${ext}`),
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
extends: [
|
extends: [
|
||||||
vue.configs['flat/recommended'],
|
vue.configs['flat/recommended'],
|
||||||
vueScopedCss.configs.recommended,
|
vueScopedCss.configs.recommended,
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
|
'@typescript-eslint/no-redundant-type-constituents': [0], // types imported from .vue files resolve to any via typescript-eslint's *.vue shim
|
||||||
'vue/attributes-order': [0],
|
'vue/attributes-order': [0],
|
||||||
'vue/html-closing-bracket-spacing': [2, {startTag: 'never', endTag: 'never', selfClosingTag: 'never'}],
|
'vue/html-closing-bracket-spacing': [2, {startTag: 'never', endTag: 'never', selfClosingTag: 'never'}],
|
||||||
'vue/max-attributes-per-line': [0],
|
'vue/max-attributes-per-line': [0],
|
||||||
|
|||||||
@@ -2,29 +2,26 @@ module gitea.dev
|
|||||||
|
|
||||||
go 1.27
|
go 1.27
|
||||||
|
|
||||||
toolchain go1.27.1
|
toolchain go1.27.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
connectrpc.com/connect v1.21.0
|
connectrpc.com/connect v1.21.0
|
||||||
gitea.com/go-chi/binding v0.0.0-20260819122636-082915a69981
|
|
||||||
gitea.com/go-chi/cache v0.2.1
|
|
||||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
|
|
||||||
gitea.com/go-chi/session v0.0.0-20260708011333-ebced8a7a2d6
|
|
||||||
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
||||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
|
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
|
||||||
gitea.dev/actionslib v1.2.1
|
gitea.dev/actionslib v1.3.0
|
||||||
gitea.dev/sdk v1.2.0
|
gitea.dev/sdk v1.2.0
|
||||||
github.com/42wim/httpsig v1.2.4
|
github.com/42wim/httpsig v1.2.4
|
||||||
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432
|
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432
|
||||||
github.com/Azure/go-ntlmssp v0.1.1
|
github.com/Azure/go-ntlmssp v0.1.1
|
||||||
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347
|
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347
|
||||||
github.com/ProtonMail/go-crypto v1.5.0
|
github.com/ProtonMail/go-crypto v1.5.2
|
||||||
github.com/PuerkitoBio/goquery v1.13.0
|
github.com/PuerkitoBio/goquery v1.13.0
|
||||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0
|
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0
|
||||||
github.com/alecthomas/chroma/v2 v2.27.0
|
github.com/alecthomas/chroma/v2 v2.27.0
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||||
github.com/blevesearch/bleve/v2 v2.6.1
|
github.com/blevesearch/bleve/v2 v2.6.1
|
||||||
github.com/bohde/codel v0.2.0
|
github.com/bohde/codel v0.2.0
|
||||||
|
github.com/bradfitz/gomemcache v0.0.0-20260422231931-4d751bb6e37c
|
||||||
github.com/buildkite/terminal-to-html/v3 v3.17.1
|
github.com/buildkite/terminal-to-html/v3 v3.17.1
|
||||||
github.com/caddyserver/certmagic v0.25.4
|
github.com/caddyserver/certmagic v0.25.4
|
||||||
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20260812203852-971c0284dc33
|
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20260812203852-971c0284dc33
|
||||||
@@ -35,7 +32,6 @@ require (
|
|||||||
github.com/dustin/go-humanize v1.1.0
|
github.com/dustin/go-humanize v1.1.0
|
||||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.5
|
github.com/editorconfig/editorconfig-core-go/v2 v2.6.5
|
||||||
github.com/emersion/go-imap v1.2.1
|
github.com/emersion/go-imap v1.2.1
|
||||||
github.com/emirpasic/gods v1.18.1
|
|
||||||
github.com/felixge/fgprof v0.9.5
|
github.com/felixge/fgprof v0.9.5
|
||||||
github.com/fsnotify/fsnotify v1.10.1
|
github.com/fsnotify/fsnotify v1.10.1
|
||||||
github.com/getkin/kin-openapi v0.149.0
|
github.com/getkin/kin-openapi v0.149.0
|
||||||
@@ -43,8 +39,6 @@ require (
|
|||||||
github.com/go-chi/cors v1.2.2
|
github.com/go-chi/cors v1.2.2
|
||||||
github.com/go-co-op/gocron/v2 v2.22.0
|
github.com/go-co-op/gocron/v2 v2.22.0
|
||||||
github.com/go-enry/go-enry/v2 v2.9.6
|
github.com/go-enry/go-enry/v2 v2.9.6
|
||||||
github.com/go-git/go-billy/v5 v5.9.1
|
|
||||||
github.com/go-git/go-git/v5 v5.19.2
|
|
||||||
github.com/go-ldap/ldap/v3 v3.4.14
|
github.com/go-ldap/ldap/v3 v3.4.14
|
||||||
github.com/go-redsync/redsync/v4 v4.18.0
|
github.com/go-redsync/redsync/v4 v4.18.0
|
||||||
github.com/go-sql-driver/mysql v1.10.1
|
github.com/go-sql-driver/mysql v1.10.1
|
||||||
@@ -62,7 +56,7 @@ require (
|
|||||||
github.com/huandu/xstrings v1.6.1
|
github.com/huandu/xstrings v1.6.1
|
||||||
github.com/jhillyerd/enmime/v2 v2.5.0
|
github.com/jhillyerd/enmime/v2 v2.5.0
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||||
github.com/klauspost/compress v1.20.0
|
github.com/klauspost/compress v1.20.1
|
||||||
github.com/lib/pq v1.12.3
|
github.com/lib/pq v1.12.3
|
||||||
github.com/markbates/goth v1.82.0
|
github.com/markbates/goth v1.82.0
|
||||||
github.com/mattn/go-isatty v0.0.24
|
github.com/mattn/go-isatty v0.0.24
|
||||||
@@ -93,12 +87,12 @@ require (
|
|||||||
github.com/yohcop/openid-go v1.0.1
|
github.com/yohcop/openid-go v1.0.1
|
||||||
github.com/yuin/goldmark v1.8.6
|
github.com/yuin/goldmark v1.8.6
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||||
gitlab.com/gitlab-org/api/client-go/v3 v3.13.0
|
gitlab.com/gitlab-org/api/client-go/v3 v3.15.0
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.6
|
go.yaml.in/yaml/v4 v4.0.0-rc.6
|
||||||
golang.org/x/crypto v0.57.0
|
golang.org/x/crypto v0.57.0
|
||||||
golang.org/x/image v0.46.0
|
golang.org/x/image v0.46.0
|
||||||
golang.org/x/mod v0.41.0
|
golang.org/x/mod v0.41.0
|
||||||
golang.org/x/net v0.59.0
|
golang.org/x/net v0.60.0
|
||||||
golang.org/x/oauth2 v0.37.0
|
golang.org/x/oauth2 v0.37.0
|
||||||
golang.org/x/sync v0.23.0
|
golang.org/x/sync v0.23.0
|
||||||
golang.org/x/sys v0.48.0
|
golang.org/x/sys v0.48.0
|
||||||
@@ -106,7 +100,7 @@ require (
|
|||||||
google.golang.org/grpc v1.84.0
|
google.golang.org/grpc v1.84.0
|
||||||
google.golang.org/protobuf v1.36.12
|
google.golang.org/protobuf v1.36.12
|
||||||
gopkg.in/ini.v1 v1.67.3
|
gopkg.in/ini.v1 v1.67.3
|
||||||
modernc.org/sqlite v1.59.0
|
modernc.org/sqlite v1.60.1
|
||||||
mvdan.cc/xurls/v2 v2.6.0
|
mvdan.cc/xurls/v2 v2.6.0
|
||||||
xorm.io/builder v0.3.13
|
xorm.io/builder v0.3.13
|
||||||
xorm.io/xorm v1.4.3
|
xorm.io/xorm v1.4.3
|
||||||
@@ -114,10 +108,8 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||||
dario.cat/mergo v1.0.2 // indirect
|
|
||||||
filippo.io/edwards25519 v1.2.0 // indirect
|
filippo.io/edwards25519 v1.2.0 // indirect
|
||||||
github.com/DataDog/zstd v1.5.7 // indirect
|
github.com/DataDog/zstd v1.5.7 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
|
||||||
github.com/RoaringBitmap/roaring/v2 v2.18.2 // indirect
|
github.com/RoaringBitmap/roaring/v2 v2.18.2 // indirect
|
||||||
github.com/STARRY-S/zip v0.2.3 // indirect
|
github.com/STARRY-S/zip v0.2.3 // indirect
|
||||||
github.com/andybalholm/brotli v1.2.1 // indirect
|
github.com/andybalholm/brotli v1.2.1 // indirect
|
||||||
@@ -143,23 +135,18 @@ require (
|
|||||||
github.com/blevesearch/zapx/v15 v15.4.3 // indirect
|
github.com/blevesearch/zapx/v15 v15.4.3 // indirect
|
||||||
github.com/blevesearch/zapx/v16 v16.3.4 // indirect
|
github.com/blevesearch/zapx/v16 v16.3.4 // indirect
|
||||||
github.com/blevesearch/zapx/v17 v17.2.3 // indirect
|
github.com/blevesearch/zapx/v17 v17.2.3 // indirect
|
||||||
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
|
github.com/bmatcuk/doublestar/v4 v4.10.2 // indirect
|
||||||
github.com/bodgit/plumbing v1.3.0 // indirect
|
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||||
github.com/bodgit/sevenzip v1.6.4 // indirect
|
github.com/bodgit/sevenzip v1.6.4 // indirect
|
||||||
github.com/bodgit/windows v1.0.1 // indirect
|
github.com/bodgit/windows v1.0.1 // indirect
|
||||||
github.com/boombuler/barcode v1.1.0 // indirect
|
github.com/boombuler/barcode v1.1.0 // indirect
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20260422231931-4d751bb6e37c // indirect
|
|
||||||
github.com/caddyserver/zerossl v0.1.5 // indirect
|
github.com/caddyserver/zerossl v0.1.5 // indirect
|
||||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||||
github.com/cloudflare/circl v1.6.3 // indirect
|
github.com/cloudflare/circl v1.6.3 // indirect
|
||||||
github.com/couchbase/go-couchbase v0.1.1 // indirect
|
|
||||||
github.com/couchbase/gomemcached v0.3.4 // indirect
|
|
||||||
github.com/couchbase/goutils v0.3.0 // indirect
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
|
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
|
||||||
@@ -168,7 +155,6 @@ require (
|
|||||||
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect
|
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.8 // indirect
|
||||||
github.com/go-enry/go-oniguruma v1.2.1 // indirect
|
github.com/go-enry/go-oniguruma v1.2.1 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
|
||||||
github.com/go-openapi/jsonpointer v0.23.1 // indirect
|
github.com/go-openapi/jsonpointer v0.23.1 // indirect
|
||||||
github.com/go-openapi/swag/jsonname v0.26.1 // indirect
|
github.com/go-openapi/swag/jsonname v0.26.1 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||||
@@ -176,9 +162,7 @@ require (
|
|||||||
github.com/goccy/go-json v0.10.6 // indirect
|
github.com/goccy/go-json v0.10.6 // indirect
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
|
||||||
github.com/golang/snappy v1.0.0 // indirect
|
github.com/golang/snappy v1.0.0 // indirect
|
||||||
github.com/google/flatbuffers v25.12.19+incompatible // indirect
|
|
||||||
github.com/google/go-querystring v1.2.0 // indirect
|
github.com/google/go-querystring v1.2.0 // indirect
|
||||||
github.com/google/go-tpm v0.9.8 // indirect
|
github.com/google/go-tpm v0.9.8 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
@@ -188,10 +172,8 @@ require (
|
|||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||||
github.com/inbucket/html2text v1.0.0 // indirect
|
github.com/inbucket/html2text v1.0.0 // indirect
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
|
||||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.6.0 // indirect
|
|
||||||
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
|
||||||
github.com/klauspost/crc32 v1.3.0 // indirect
|
github.com/klauspost/crc32 v1.3.0 // indirect
|
||||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||||
@@ -219,9 +201,9 @@ require (
|
|||||||
github.com/olekukonko/ll v0.1.8 // indirect
|
github.com/olekukonko/ll v0.1.8 // indirect
|
||||||
github.com/olekukonko/tablewriter v1.1.4 // indirect
|
github.com/olekukonko/tablewriter v1.1.4 // indirect
|
||||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||||
|
github.com/onsi/gomega v1.34.1 // indirect
|
||||||
github.com/philhofer/fwd v1.2.0 // indirect
|
github.com/philhofer/fwd v1.2.0 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.27 // indirect
|
github.com/pierrec/lz4/v4 v4.1.27 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.6.0 // indirect
|
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
@@ -232,15 +214,12 @@ require (
|
|||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/shopspring/decimal v1.4.0 // indirect
|
github.com/shopspring/decimal v1.4.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.10.2 // indirect
|
github.com/sirupsen/logrus v1.10.2 // indirect
|
||||||
github.com/skeema/knownhosts v1.3.2 // indirect
|
|
||||||
github.com/sorairolake/lzip-go v0.3.8 // indirect
|
github.com/sorairolake/lzip-go v0.3.8 // indirect
|
||||||
github.com/spf13/afero v1.15.0 // indirect
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||||
github.com/stangelandcl/ppmd v0.1.1 // indirect
|
github.com/stangelandcl/ppmd v0.1.1 // indirect
|
||||||
github.com/tinylib/msgp v1.6.4 // indirect
|
github.com/tinylib/msgp v1.6.4 // indirect
|
||||||
github.com/unknwon/com v1.0.1 // indirect
|
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
|
||||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||||
github.com/zeebo/xxh3 v1.1.0 // indirect
|
github.com/zeebo/xxh3 v1.1.0 // indirect
|
||||||
@@ -251,11 +230,11 @@ require (
|
|||||||
go.uber.org/zap/exp v0.3.0 // indirect
|
go.uber.org/zap/exp v0.3.0 // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||||
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
|
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
|
||||||
golang.org/x/time v0.15.0 // indirect
|
golang.org/x/time v0.15.0 // indirect
|
||||||
golang.org/x/tools v0.49.0 // indirect
|
golang.org/x/tools v0.50.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
modernc.org/libc v1.77.1 // indirect
|
||||||
modernc.org/libc v1.75.7 // indirect
|
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.12.1 // indirect
|
modernc.org/memory v1.12.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,26 +4,16 @@ code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE=
|
|||||||
code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM=
|
code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM=
|
||||||
connectrpc.com/connect v1.21.0 h1:LhqSJt7jHf5NJBo9Jq/t/9FjcYAideif0mg+qe2jCUs=
|
connectrpc.com/connect v1.21.0 h1:LhqSJt7jHf5NJBo9Jq/t/9FjcYAideif0mg+qe2jCUs=
|
||||||
connectrpc.com/connect v1.21.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4=
|
connectrpc.com/connect v1.21.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4=
|
||||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
|
||||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
|
||||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||||
gitea.com/go-chi/binding v0.0.0-20260819122636-082915a69981 h1:LmdlwGbzgZFZA3bK3R1q8QrNabaSz3KpnOJBLmzhN6E=
|
|
||||||
gitea.com/go-chi/binding v0.0.0-20260819122636-082915a69981/go.mod h1:q1SSPpkC9A0gfNnoqqZ3My6kEHIpKN6QsTI+Zx73B/o=
|
|
||||||
gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g=
|
|
||||||
gitea.com/go-chi/cache v0.2.1/go.mod h1:Qic0HZ8hOHW62ETGbonpwz8WYypj9NieU9659wFUJ8Q=
|
|
||||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR98IP2KZQKRzJJiV7aTeMAFwaWo=
|
|
||||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098/go.mod h1:LjzIOHlRemuUyO7WR12fmm18VZIlCAaOt9L3yKw40pk=
|
|
||||||
gitea.com/go-chi/session v0.0.0-20260708011333-ebced8a7a2d6 h1:YWzVGeC/8SZThrJS48ZmQYLkzssdeABxHPhbdnxPDIU=
|
|
||||||
gitea.com/go-chi/session v0.0.0-20260708011333-ebced8a7a2d6/go.mod h1:KDvcfMUoXfATPHs2mbMoXFTXT45/FAFAS39waz9tPk0=
|
|
||||||
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:+wWBi6Qfruqu7xJgjOIrKVQGiLUZdpKYCZewJ4clqhw=
|
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:+wWBi6Qfruqu7xJgjOIrKVQGiLUZdpKYCZewJ4clqhw=
|
||||||
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:VyMQP6ue6MKHM8UsOXfNfuMKD0oSAWZdXVcpHIN2yaY=
|
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:VyMQP6ue6MKHM8UsOXfNfuMKD0oSAWZdXVcpHIN2yaY=
|
||||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHqdhS7keYWioqfmxdnfblFDTGoOwcZ+o=
|
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHqdhS7keYWioqfmxdnfblFDTGoOwcZ+o=
|
||||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
|
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||||
gitea.dev/actionslib v1.2.1 h1:GL//K/0zIZV6h1OOksv1awvFVg0rg7fug2xWcG7mkG0=
|
gitea.dev/actionslib v1.3.0 h1:xZRoTL1+sK/PaglT9uXkZJ9g0nk1WLMEH4FUXdXpKKw=
|
||||||
gitea.dev/actionslib v1.2.1/go.mod h1:1+gqOKGSEPn2IFHgY8S3GC5Ld+Hn8jF3OxiFHQ/fpx4=
|
gitea.dev/actionslib v1.3.0/go.mod h1:svCefEsavx4jmn8vJ4wTG7Q0KBcGfrFmGUjrhxuNUAQ=
|
||||||
gitea.dev/sdk v1.2.0 h1:avRtJl/nKCGispgSalo9czoZM9Rto1awnE0caNAoXGo=
|
gitea.dev/sdk v1.2.0 h1:avRtJl/nKCGispgSalo9czoZM9Rto1awnE0caNAoXGo=
|
||||||
gitea.dev/sdk v1.2.0/go.mod h1:rfh5oNdIK24cbCREwIn1tqWKQW+IICXFGWJyebuOAOE=
|
gitea.dev/sdk v1.2.0/go.mod h1:rfh5oNdIK24cbCREwIn1tqWKQW+IICXFGWJyebuOAOE=
|
||||||
github.com/42wim/httpsig v1.2.4 h1:mI5bH0nm4xn7K18fo1K3okNDRq8CCJ0KbBYWyA6r8lU=
|
github.com/42wim/httpsig v1.2.4 h1:mI5bH0nm4xn7K18fo1K3okNDRq8CCJ0KbBYWyA6r8lU=
|
||||||
@@ -46,13 +36,10 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 h1:Nljr4q1GRA/
|
|||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI=
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI=
|
||||||
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
|
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
|
||||||
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
|
||||||
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347 h1:3JhDl+JysaO8nhNU1XMaw35VSGjV4IEQAefaG4Lyok4=
|
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347 h1:3JhDl+JysaO8nhNU1XMaw35VSGjV4IEQAefaG4Lyok4=
|
||||||
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347/go.mod h1:2ErI0aycD43Ufr6CFK5lT/NrHGmoZuVbn1nlPThw69o=
|
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347/go.mod h1:2ErI0aycD43Ufr6CFK5lT/NrHGmoZuVbn1nlPThw69o=
|
||||||
github.com/ProtonMail/go-crypto v1.5.0 h1:sKmuvjOgsnrtpMvZ+84MCnTJCpjxZ1qCFn076lz1yT0=
|
github.com/ProtonMail/go-crypto v1.5.2 h1:cucYnvqcY7UOXVD//mSyjeaPY0SSN3v5cDkYPxumINk=
|
||||||
github.com/ProtonMail/go-crypto v1.5.0/go.mod h1:/RaSu30DaKO4RY+XdV/ACcCcZkGr7AhUIduq5sjzzCo=
|
github.com/ProtonMail/go-crypto v1.5.2/go.mod h1:/RaSu30DaKO4RY+XdV/ACcCcZkGr7AhUIduq5sjzzCo=
|
||||||
github.com/PuerkitoBio/goquery v1.13.0 h1:mqHbjD7Jmnul4DTR24LKTjo1uUmHUh072kteGV+xpFM=
|
github.com/PuerkitoBio/goquery v1.13.0 h1:mqHbjD7Jmnul4DTR24LKTjo1uUmHUh072kteGV+xpFM=
|
||||||
github.com/PuerkitoBio/goquery v1.13.0/go.mod h1:Hip5mdBL8K2wEGKJdr27sRaNwIdDajmCwB/ExUPwW+g=
|
github.com/PuerkitoBio/goquery v1.13.0/go.mod h1:Hip5mdBL8K2wEGKJdr27sRaNwIdDajmCwB/ExUPwW+g=
|
||||||
github.com/RoaringBitmap/roaring/v2 v2.18.2 h1:oPq3Cgx//iDuJQVp6xSInAKW34J9CEwE5GmLI2z+Eic=
|
github.com/RoaringBitmap/roaring/v2 v2.18.2 h1:oPq3Cgx//iDuJQVp6xSInAKW34J9CEwE5GmLI2z+Eic=
|
||||||
@@ -75,10 +62,6 @@ github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eT
|
|||||||
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
github.com/andybalholm/cascadia v1.3.4 h1:vM2lgh0Vru9Vwyfm4cQqWP2HHMW0u0+2PAW7Q38Qufg=
|
github.com/andybalholm/cascadia v1.3.4 h1:vM2lgh0Vru9Vwyfm4cQqWP2HHMW0u0+2PAW7Q38Qufg=
|
||||||
github.com/andybalholm/cascadia v1.3.4/go.mod h1:BLRmbRjpEtNKieZOCCvYj4RqN+KRA41GBe/5O+G93kM=
|
github.com/andybalholm/cascadia v1.3.4/go.mod h1:BLRmbRjpEtNKieZOCCvYj4RqN+KRA41GBe/5O+G93kM=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
|
||||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
@@ -125,8 +108,8 @@ github.com/blevesearch/zapx/v16 v16.3.4 h1:hDAqA8qusZTNbPEL7//w5P65UZ2de6yhSeUaT
|
|||||||
github.com/blevesearch/zapx/v16 v16.3.4/go.mod h1:zqkPPqs9GS9FzVWzCO3Wf1X044yWAV17+4zb+FTiEHg=
|
github.com/blevesearch/zapx/v16 v16.3.4/go.mod h1:zqkPPqs9GS9FzVWzCO3Wf1X044yWAV17+4zb+FTiEHg=
|
||||||
github.com/blevesearch/zapx/v17 v17.2.3 h1:UYYJPAt5b2tVxldx5h0jmv23RMsg8/UZKFVya7v92po=
|
github.com/blevesearch/zapx/v17 v17.2.3 h1:UYYJPAt5b2tVxldx5h0jmv23RMsg8/UZKFVya7v92po=
|
||||||
github.com/blevesearch/zapx/v17 v17.2.3/go.mod h1:r7mb4QWbDQSkbAnOjCb9iCfkcrzajB4yBdJpuBIo/fE=
|
github.com/blevesearch/zapx/v17 v17.2.3/go.mod h1:r7mb4QWbDQSkbAnOjCb9iCfkcrzajB4yBdJpuBIo/fE=
|
||||||
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
|
github.com/bmatcuk/doublestar/v4 v4.10.2 h1:eF7W7HWKg3z9NrWV9pTLnNeoXaqq3Tq9DNKXVMfoCnw=
|
||||||
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
github.com/bmatcuk/doublestar/v4 v4.10.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||||
github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q=
|
github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q=
|
||||||
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
||||||
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
|
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
|
||||||
@@ -173,17 +156,8 @@ github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg
|
|||||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||||
github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA=
|
github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA=
|
||||||
github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||||
github.com/couchbase/go-couchbase v0.1.1 h1:ClFXELcKj/ojyoTYbsY34QUrrYCBi/1G749sXSCkdhk=
|
|
||||||
github.com/couchbase/go-couchbase v0.1.1/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A=
|
|
||||||
github.com/couchbase/gomemcached v0.3.4 h1:VGdrZUJbt5lLyI/MXnyVCZKHKYXg/vaud08lJIAeZps=
|
|
||||||
github.com/couchbase/gomemcached v0.3.4/go.mod h1:pISAjweI42vljCumsJIo7CVhqIMIIP9g3Wfhl1JJw68=
|
|
||||||
github.com/couchbase/goutils v0.1.2/go.mod h1:h89Ek/tiOxxqjz30nPPlwZdQbdB8BwgnuBxeoUe/ViE=
|
|
||||||
github.com/couchbase/goutils v0.3.0 h1:rsv72B6BDjW9jmwlfiDUrdu3EpNvPuo5WLULHzQ0DLE=
|
|
||||||
github.com/couchbase/goutils v0.3.0/go.mod h1:7Gm+D3vXfV4HS+hQWvKfy6e6ILCptGXNqBKvQXhplhk=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
|
||||||
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
@@ -205,8 +179,6 @@ github.com/dustin/go-humanize v1.1.0 h1:dbKTrvD0klcbBV/h4AWJdMuZogJACoMlvWIWZ5b2
|
|||||||
github.com/dustin/go-humanize v1.1.0/go.mod h1:hc1CvRkJMsgxqjmjMQF3QNRAZBwY8AXBAzKYoSX9sFI=
|
github.com/dustin/go-humanize v1.1.0/go.mod h1:hc1CvRkJMsgxqjmjMQF3QNRAZBwY8AXBAzKYoSX9sFI=
|
||||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.5 h1:MTcuJQkIFRLfNn9FfAvdO0p3FhNyaCf+IGTwy0TxX6E=
|
github.com/editorconfig/editorconfig-core-go/v2 v2.6.5 h1:MTcuJQkIFRLfNn9FfAvdO0p3FhNyaCf+IGTwy0TxX6E=
|
||||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.5/go.mod h1:SizrS3EM1vFF0v/JZlJ5LfK6tXhwln2823YZwNh/vBE=
|
github.com/editorconfig/editorconfig-core-go/v2 v2.6.5/go.mod h1:SizrS3EM1vFF0v/JZlJ5LfK6tXhwln2823YZwNh/vBE=
|
||||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
|
||||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
|
||||||
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
|
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
|
||||||
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
|
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
|
||||||
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
|
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
|
||||||
@@ -214,8 +186,6 @@ github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTe
|
|||||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
|
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
|
||||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
|
||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
|
||||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||||
github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
|
github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
|
||||||
@@ -230,8 +200,6 @@ github.com/getkin/kin-openapi v0.149.0 h1:ZbhmVJ4yq5RZDUsyP8lcBcGMsjsaTqXEFt6isd
|
|||||||
github.com/getkin/kin-openapi v0.149.0/go.mod h1:1+BHDzstro+P5CKtPy1X4PfofnFgmRe6uvMy9+r9fKY=
|
github.com/getkin/kin-openapi v0.149.0/go.mod h1:1+BHDzstro+P5CKtPy1X4PfofnFgmRe6uvMy9+r9fKY=
|
||||||
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0=
|
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0=
|
||||||
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A=
|
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A=
|
||||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
|
||||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8 h1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ=
|
github.com/go-asn1-ber/asn1-ber v1.5.8 h1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
github.com/go-asn1-ber/asn1-ber v1.5.8/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
@@ -245,14 +213,6 @@ github.com/go-enry/go-enry/v2 v2.9.6 h1:np63eOtMV56zfYDHnFVgpEVOk8fr2kmylcMnAZUD
|
|||||||
github.com/go-enry/go-enry/v2 v2.9.6/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
|
github.com/go-enry/go-enry/v2 v2.9.6/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
|
||||||
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
|
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
|
||||||
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
|
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
|
||||||
github.com/go-git/go-billy/v5 v5.9.1 h1:8U73XiOTfINdItHVa6z4Gv7ToObcZ6grkqQbLryLCdA=
|
|
||||||
github.com/go-git/go-billy/v5 v5.9.1/go.mod h1:ExsU+jcGwXTBOnyilvAnEM1wug1IxHr4yP2ZXsNRtV0=
|
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
|
||||||
github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY=
|
|
||||||
github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk=
|
|
||||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.14 h1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs=
|
github.com/go-ldap/ldap/v3 v3.4.14 h1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs=
|
||||||
@@ -297,8 +257,6 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt
|
|||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
@@ -314,9 +272,6 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
|||||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/gomodule/redigo v1.9.3 h1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8=
|
github.com/gomodule/redigo v1.9.3 h1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8=
|
||||||
github.com/gomodule/redigo v1.9.3/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
|
github.com/gomodule/redigo v1.9.3/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
|
||||||
github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
|
||||||
github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
|
|
||||||
github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
@@ -343,8 +298,6 @@ github.com/google/pprof v0.0.0-20260906184651-6331bc6350fe h1:QAinXoAFJdGQYztXn3
|
|||||||
github.com/google/pprof v0.0.0-20260906184651-6331bc6350fe/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
|
github.com/google/pprof v0.0.0-20260906184651-6331bc6350fe/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
|
||||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||||
@@ -381,8 +334,6 @@ github.com/huandu/xstrings v1.6.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq
|
|||||||
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||||
github.com/inbucket/html2text v1.0.0 h1:N5kza++4uBBDJ2Z3KUnTRyPNoBcW+YfOgNiNmNB+sgs=
|
github.com/inbucket/html2text v1.0.0 h1:N5kza++4uBBDJ2Z3KUnTRyPNoBcW+YfOgNiNmNB+sgs=
|
||||||
github.com/inbucket/html2text v1.0.0/go.mod h1:5TrhXQKGU+LXurODaSm55Y9eXoPBRnYiOz4x2XfUoJU=
|
github.com/inbucket/html2text v1.0.0/go.mod h1:5TrhXQKGU+LXurODaSm55Y9eXoPBRnYiOz4x2XfUoJU=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
|
||||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||||
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
|
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
|
||||||
@@ -402,16 +353,11 @@ github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7X
|
|||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=
|
|
||||||
github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
|
||||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
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.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
||||||
@@ -526,8 +472,6 @@ github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
|||||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||||
github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk=
|
github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk=
|
||||||
github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||||
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
|
|
||||||
github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@@ -573,16 +517,8 @@ github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
|||||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
|
||||||
github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo=
|
github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo=
|
||||||
github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q=
|
github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q=
|
||||||
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
|
|
||||||
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
|
|
||||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
|
|
||||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
|
||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
|
||||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
|
|
||||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
|
||||||
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
|
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
|
||||||
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
|
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
|
||||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
@@ -606,7 +542,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
||||||
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
||||||
@@ -619,8 +554,6 @@ github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77ro
|
|||||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/ulikunitz/xz v0.5.17 h1:flR0y/x1hgM8EGV1AW3Xll6T413G0glV8UfBwR617V4=
|
github.com/ulikunitz/xz v0.5.17 h1:flR0y/x1hgM8EGV1AW3Xll6T413G0glV8UfBwR617V4=
|
||||||
github.com/ulikunitz/xz v0.5.17/go.mod h1:H9Rt/W6/Qj27PGauhQc6nfCDy7vHpzsOThBSaYDoEhw=
|
github.com/ulikunitz/xz v0.5.17/go.mod h1:H9Rt/W6/Qj27PGauhQc6nfCDy7vHpzsOThBSaYDoEhw=
|
||||||
github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
|
|
||||||
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
|
|
||||||
github.com/urfave/cli-docs/v3 v3.1.0 h1:Sa5xm19IpE5gpm6tZzXdfjdFxn67PnEsE4dpXF7vsKw=
|
github.com/urfave/cli-docs/v3 v3.1.0 h1:Sa5xm19IpE5gpm6tZzXdfjdFxn67PnEsE4dpXF7vsKw=
|
||||||
github.com/urfave/cli-docs/v3 v3.1.0/go.mod h1:59d+5Hz1h6GSGJ10cvcEkbIe3j233t4XDqI72UIx7to=
|
github.com/urfave/cli-docs/v3 v3.1.0/go.mod h1:59d+5Hz1h6GSGJ10cvcEkbIe3j233t4XDqI72UIx7to=
|
||||||
github.com/urfave/cli/v3 v3.13.0 h1:Dr6jqMfIyyFsRVn7Nz5mqLsMY+ZMpfh3a0aMs+umPVY=
|
github.com/urfave/cli/v3 v3.13.0 h1:Dr6jqMfIyyFsRVn7Nz5mqLsMY+ZMpfh3a0aMs+umPVY=
|
||||||
@@ -629,8 +562,6 @@ github.com/wneessen/go-mail v0.8.1 h1:tVcncj02/QySVFw3zr/kXOzZcuFQqBNT6K+Rbgm/pc
|
|||||||
github.com/wneessen/go-mail v0.8.1/go.mod h1:dWZ61zadzCIyvB4y1/YzC5O7MrbbzBfPkARmbosdf8w=
|
github.com/wneessen/go-mail v0.8.1/go.mod h1:dWZ61zadzCIyvB4y1/YzC5O7MrbbzBfPkARmbosdf8w=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
|
||||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
|
||||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
@@ -652,8 +583,8 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
|||||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||||
gitlab.com/gitlab-org/api/client-go/v3 v3.13.0 h1:CTUXvcL6OrGjYNafbws5C1lJwlXrxGJHWQGMF+hCdz0=
|
gitlab.com/gitlab-org/api/client-go/v3 v3.15.0 h1:67OL6f2VrQJ4sBYBLgZPpP4XEbnA5qfCBKbbn46GBoU=
|
||||||
gitlab.com/gitlab-org/api/client-go/v3 v3.13.0/go.mod h1:k7uYxRoIeuSqyWZSJfAPZlf0L32OH0lUBImAl4gWUFc=
|
gitlab.com/gitlab-org/api/client-go/v3 v3.15.0/go.mod h1:k7uYxRoIeuSqyWZSJfAPZlf0L32OH0lUBImAl4gWUFc=
|
||||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
@@ -681,11 +612,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
|
||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
|
||||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
|
||||||
golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
|
golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
|
||||||
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
|
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
|
||||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
|
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
|
||||||
@@ -694,10 +620,6 @@ golang.org/x/image v0.46.0 h1:b1+oYj0Jbp6K5MDT4i4/eZpYlk3V8SJhhDKh6LBHAyQ=
|
|||||||
golang.org/x/image v0.46.0/go.mod h1:3B3W05VGVQyuXucLINLjXKrqISASfi4Xj+iCVkLMwew=
|
golang.org/x/image v0.46.0/go.mod h1:3B3W05VGVQyuXucLINLjXKrqISASfi4Xj+iCVkLMwew=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
|
||||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
|
||||||
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
|
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
|
||||||
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
|
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -706,16 +628,10 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU=
|
||||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
|
||||||
golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
|
|
||||||
golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
|
|
||||||
golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
|
golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
|
||||||
golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58=
|
golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -723,11 +639,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|
||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
|
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
|
||||||
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -736,37 +647,22 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
|
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
|
||||||
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
||||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
|
||||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
|
||||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
|
||||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
|
||||||
golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
|
golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
|
||||||
golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
|
golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -774,11 +670,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
|
||||||
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
|
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
|
||||||
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
|
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
|
||||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||||
@@ -787,11 +678,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
|||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.50.0 h1:c2ifzfcuY7L90lZ2aKd8S4K2NpASF08SZx9ZuJkHmSU=
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
golang.org/x/tools v0.50.0/go.mod h1:7ulVMw3831Mwi5EZD6RomGyffr4VFjuNYXf2BbCEAV0=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
|
||||||
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
|
|
||||||
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -817,8 +705,6 @@ gopkg.in/ini.v1 v1.67.3 h1:iM9Lhz5MRSGhHVGGwCuzG9KO8PoirCXj/m/qTmOJJQw=
|
|||||||
gopkg.in/ini.v1 v1.67.3/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
|
gopkg.in/ini.v1 v1.67.3/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
|
||||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
@@ -828,10 +714,10 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
modernc.org/cc/v4 v4.29.2 h1:h6+9ciCnPKutf4I03CvheAvDLX7+IHlqR6Iy6J+cgd8=
|
modernc.org/cc/v4 v4.29.7 h1:q+NXGJ0bK3b4TXFYQQVr9pYETGnmwFWkrUzJnMya/Tg=
|
||||||
modernc.org/cc/v4 v4.29.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
modernc.org/cc/v4 v4.29.7/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||||
modernc.org/ccgo/v4 v4.35.0 h1:F+TUsmw09QxLzmi3aeYYGxjAXarmZaKgj3mKQHNaA8w=
|
modernc.org/ccgo/v4 v4.36.1 h1:ZNIUZAryN0UgnJwtyxrdEzcFc3yD4Cu4AzjfPXsLsIE=
|
||||||
modernc.org/ccgo/v4 v4.35.0/go.mod h1:qrVGs9S3Sr2Ztcg9ve+kTAYMp5a3YvWjo+SoN06kJ5I=
|
modernc.org/ccgo/v4 v4.36.1/go.mod h1:rrtGc2QkS239nYb/mQNuBMyjq3/y3ZXWbBjPoV3wqzA=
|
||||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
@@ -840,8 +726,8 @@ modernc.org/gc/v3 v3.1.5 h1:21ldfPfRYE31Tb7B3mwAK8gy1AxP4+dKjrOQPfqakoc=
|
|||||||
modernc.org/gc/v3 v3.1.5/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
modernc.org/gc/v3 v3.1.5/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
modernc.org/libc v1.75.7 h1:o3DTP9/0p9pKmY2WCKQaySW6wIiZhNM7wc2lUoyhfew=
|
modernc.org/libc v1.77.1 h1:Ct8j47QtiZ1Enj2DtFXQtUqrPCAjdCmPjtCuvrYQ0Hs=
|
||||||
modernc.org/libc v1.75.7/go.mod h1:bO5o2ztHxBb2rjz0PgdHN0sSMw57CgxGFLZ3Qd/QpVQ=
|
modernc.org/libc v1.77.1/go.mod h1:87/pZ4L6nD1zqW4nItuS12YO7hN1igAah34xjnQo/W0=
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
modernc.org/memory v1.12.1 h1:nFMiWrpStgZczNl6XI9GnIk/rWhYIyHGUaR04pGbp9g=
|
modernc.org/memory v1.12.1 h1:nFMiWrpStgZczNl6XI9GnIk/rWhYIyHGUaR04pGbp9g=
|
||||||
@@ -850,8 +736,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
|||||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
modernc.org/sqlite v1.59.0 h1:X1es1GpqBlS/5T+vbM4HLUdaa8OtQx468DF2vrx+38A=
|
modernc.org/sqlite v1.60.1 h1:/blz53O951KWFOso4QQvEs/Fq6cDBKLtMVrYNSeJVKw=
|
||||||
modernc.org/sqlite v1.59.0/go.mod h1:+paeT2A3iPRHkQDwG7oA6Tk0zQd5woMEI8q7orfry8k=
|
modernc.org/sqlite v1.60.1/go.mod h1:1dIoEagfDE72QytD5scH1lxARtaUgKgHC/NuApA27r0=
|
||||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
|||||||
@@ -431,7 +431,7 @@ func prepareMigrationTasks() []*migration {
|
|||||||
newMigration(354, "Add Actions job queue indexes", v28.AddActionQueueIndexes),
|
newMigration(354, "Add Actions job queue indexes", v28.AddActionQueueIndexes),
|
||||||
newMigration(355, "Add AutoMerge merged_commit_id column", v28.AddAutoMergeMergedCommitID),
|
newMigration(355, "Add AutoMerge merged_commit_id column", v28.AddAutoMergeMergedCommitID),
|
||||||
// Gitea 28.0.0 ends at migration ID number 355 (database version 356)
|
// Gitea 28.0.0 ends at migration ID number 355 (database version 356)
|
||||||
// HERE: 2 migrations from 29 are added since they don't change database structure
|
|
||||||
newMigration(356, "Add index on action_run commit_sha", v29.AddActionRunCommitSHAIndex),
|
newMigration(356, "Add index on action_run commit_sha", v29.AddActionRunCommitSHAIndex),
|
||||||
newMigration(357, "Normalize legacy team authorize values", v29.NormalizeLegacyTeamAuthorize),
|
newMigration(357, "Normalize legacy team authorize values", v29.NormalizeLegacyTeamAuthorize),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
// NormalizeLegacyTeamAuthorize sets leftover read/write authorize values to none.
|
// NormalizeLegacyTeamAuthorize sets leftover read/write authorize values to none.
|
||||||
// https://github.com/go-gitea/gitea/pull/34128 made non-admin teams use team_unit (authorize=none).
|
// https://github.com/go-gitea/gitea/pull/34128 made non-admin teams use team_unit (authorize=none).
|
||||||
// authorize>=write now means blanket access on every unit; migrating legacy read/write
|
// Any positive authorize now means blanket access on every unit; migrating legacy read/write
|
||||||
// to none preserves their existing team_unit-scoped access.
|
// to none preserves their existing team_unit-scoped access.
|
||||||
func NormalizeLegacyTeamAuthorize(_ context.Context, x base.EngineMigration) error {
|
func NormalizeLegacyTeamAuthorize(_ context.Context, x base.EngineMigration) error {
|
||||||
// AccessModeNone=0, AccessModeRead=1, AccessModeWrite=2, AccessModeAdmin=3
|
// AccessModeNone=0, AccessModeRead=1, AccessModeWrite=2, AccessModeAdmin=3
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package actions
|
||||||
|
|
||||||
|
// BelongingScope identifies the owner (user/org) or repository an Actions secret or variable belongs to.
|
||||||
|
// Both zero values mean "global scope".
|
||||||
|
// Always use the functions to create a BelongingScope, rather than creating one directly, to avoid accidentally creating an invalid scope.
|
||||||
|
type BelongingScope struct {
|
||||||
|
internalOwnerID int64
|
||||||
|
internalRepoID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func BelongingScopeOwner(ownerID int64) BelongingScope {
|
||||||
|
return BelongingScope{internalOwnerID: ownerID}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BelongingScopeRepo(repoID int64) BelongingScope {
|
||||||
|
return BelongingScope{internalRepoID: repoID}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BelongingScopeGlobal() BelongingScope {
|
||||||
|
return BelongingScope{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scope BelongingScope) GetOwnerRepoIDs() (ownerID, repoID int64) {
|
||||||
|
return scope.internalOwnerID, scope.internalRepoID
|
||||||
|
}
|
||||||
@@ -296,7 +296,9 @@ func GetRunByRepoAndID(ctx context.Context, repoID, runID int64) (*ActionRun, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetRunsByRepoAndID(ctx context.Context, repoID int64, runIDs []int64) (runs []*ActionRun, err error) {
|
func GetRunsByRepoAndID(ctx context.Context, repoID int64, runIDs []int64) (runs []*ActionRun, err error) {
|
||||||
err = db.GetEngine(ctx).In("id", runIDs).Where("repo_id=?", repoID).OrderBy("id").Find(&runs)
|
if len(runIDs) > 0 {
|
||||||
|
err = db.GetEngine(ctx).In("id", runIDs).Where("repo_id=?", repoID).OrderBy("id").Find(&runs)
|
||||||
|
}
|
||||||
return runs, err
|
return runs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ func UpdateRunAttempt(ctx context.Context, attempt *ActionRunAttempt, cols ...st
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if run.LatestAttemptID != attempt.ID {
|
if run.LatestAttemptID != attempt.ID {
|
||||||
log.Warn("run %d cannot be updated by an old attempt %d", run.LatestAttemptID, attempt.ID)
|
log.Warn("run %d cannot be updated by old attempt %d, latest attempt is %d", run.ID, attempt.ID, run.LatestAttemptID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-11
@@ -853,25 +853,34 @@ func cancelReusableCaller(ctx context.Context, caller *ActionRunJob, force bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cancel descendants deepest-first, then the caller: a caller's status is aggregated from its children,
|
// Cancel descendants deepest-first, then the caller: a caller's status is aggregated from its children,
|
||||||
// so each child must reach its final state before its parent caller is re-aggregated.
|
// so each child must be cancelled before its parent caller is re-read.
|
||||||
// A child's ID always exceeds its parent's, so descending ID is a valid deepest-first order.
|
// A child's ID always exceeds its parent's, so descending ID is a valid deepest-first order.
|
||||||
descendants := CollectAllDescendantJobs(caller, attemptJobs)
|
descendants := CollectAllDescendantJobs(caller, attemptJobs)
|
||||||
slices.SortFunc(descendants, func(a, b *ActionRunJob) int { return cmp.Compare(b.ID, a.ID) })
|
slices.SortFunc(descendants, func(a, b *ActionRunJob) int { return cmp.Compare(b.ID, a.ID) })
|
||||||
|
callersWithChildren := make(container.Set[int64])
|
||||||
|
for _, d := range descendants {
|
||||||
|
callersWithChildren.Add(d.ParentJobID)
|
||||||
|
}
|
||||||
|
|
||||||
for _, c := range descendants {
|
for _, job := range append(descendants, caller) {
|
||||||
cancelled, err := cancelOneJob(ctx, c, force)
|
if !callersWithChildren.Contains(job.ID) {
|
||||||
|
cancelled, err := cancelOneJob(ctx, job, force)
|
||||||
|
if err != nil {
|
||||||
|
return cancelledJobs, err
|
||||||
|
}
|
||||||
|
if cancelled != nil {
|
||||||
|
cancelledJobs = append(cancelledJobs, cancelled)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// the job is a caller and its children's cascade already re-aggregated it
|
||||||
|
reloaded, err := GetRunJobByRunAndID(ctx, job.RunID, job.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cancelledJobs, err
|
return cancelledJobs, err
|
||||||
}
|
}
|
||||||
if cancelled != nil {
|
if reloaded.Status != job.Status {
|
||||||
cancelledJobs = append(cancelledJobs, cancelled)
|
cancelledJobs = append(cancelledJobs, reloaded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c, err := cancelOneJob(ctx, caller, force); err != nil {
|
|
||||||
return cancelledJobs, err
|
|
||||||
} else if c != nil {
|
|
||||||
cancelledJobs = append(cancelledJobs, c)
|
|
||||||
}
|
|
||||||
return cancelledJobs, nil
|
return cancelledJobs, nil
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
runnerv1 "gitea.dev/actionslib/runner/v1"
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
"gitea.dev/models/unittest"
|
"gitea.dev/models/unittest"
|
||||||
"gitea.dev/modules/timeutil"
|
"gitea.dev/modules/timeutil"
|
||||||
@@ -397,3 +398,72 @@ func TestForceCancelJobs(t *testing.T) {
|
|||||||
assert.Equal(t, StatusCancelled, callerAfter.Status)
|
assert.Equal(t, StatusCancelled, callerAfter.Status)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCancelJobs_CallerWaitsForCancellingChild(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
|
run := &ActionRun{
|
||||||
|
Title: "caller-cancelling-child",
|
||||||
|
RepoID: 4,
|
||||||
|
Index: 9811,
|
||||||
|
OwnerID: 1,
|
||||||
|
WorkflowID: "test.yaml",
|
||||||
|
TriggerUserID: 1,
|
||||||
|
Ref: "refs/heads/master",
|
||||||
|
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
|
||||||
|
Event: "push",
|
||||||
|
TriggerEvent: "push",
|
||||||
|
EventPayload: "{}",
|
||||||
|
Status: StatusRunning,
|
||||||
|
}
|
||||||
|
require.NoError(t, db.Insert(ctx, run))
|
||||||
|
attempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1, Status: StatusRunning}
|
||||||
|
require.NoError(t, db.Insert(ctx, attempt))
|
||||||
|
run.LatestAttemptID = attempt.ID
|
||||||
|
require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id"))
|
||||||
|
|
||||||
|
newJob := func(name string, parentID int64, isCaller bool) *ActionRunJob {
|
||||||
|
job := &ActionRunJob{
|
||||||
|
RunID: run.ID,
|
||||||
|
RunAttemptID: attempt.ID,
|
||||||
|
RepoID: run.RepoID,
|
||||||
|
OwnerID: run.OwnerID,
|
||||||
|
CommitSHA: run.CommitSHA,
|
||||||
|
Name: name,
|
||||||
|
JobID: name,
|
||||||
|
Attempt: 1,
|
||||||
|
Status: StatusRunning,
|
||||||
|
ParentJobID: parentID,
|
||||||
|
IsReusableCaller: isCaller,
|
||||||
|
IsExpanded: isCaller,
|
||||||
|
}
|
||||||
|
require.NoError(t, db.Insert(ctx, job))
|
||||||
|
return job
|
||||||
|
}
|
||||||
|
outer := newJob("outer", 0, true)
|
||||||
|
inner := newJob("inner", outer.ID, true)
|
||||||
|
child := newJob("child", inner.ID, false)
|
||||||
|
|
||||||
|
runner := &ActionRunner{UUID: "caller-cancelling-child", Name: "caller-cancelling-child", HasCancellingSupport: true}
|
||||||
|
require.NoError(t, db.Insert(ctx, runner))
|
||||||
|
task := &ActionTask{JobID: child.ID, Attempt: 1, RunnerID: runner.ID, Status: StatusRunning, Started: timeutil.TimeStampNow(), RepoID: run.RepoID, OwnerID: run.OwnerID, CommitSHA: run.CommitSHA}
|
||||||
|
require.NoError(t, db.Insert(ctx, task))
|
||||||
|
child.TaskID = task.ID
|
||||||
|
_, err := UpdateRunJob(ctx, child, nil, "task_id")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cancelled, err := CancelJobs(ctx, []*ActionRunJob{outer}, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, cancelled, 3)
|
||||||
|
for _, job := range []*ActionRunJob{outer, inner, child} {
|
||||||
|
assert.Equal(t, StatusCancelling, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = UpdateTaskByState(ctx, runner.ID, &runnerv1.TaskState{Id: task.ID, Result: runnerv1.Result_RESULT_CANCELLED})
|
||||||
|
require.NoError(t, err)
|
||||||
|
for _, job := range []*ActionRunJob{outer, inner, child} {
|
||||||
|
assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name)
|
||||||
|
}
|
||||||
|
assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID}).Status)
|
||||||
|
}
|
||||||
@@ -200,7 +200,7 @@ func (r *ActionRunner) GenerateAndFillToken() {
|
|||||||
// CanMatchLabels checks whether the runner's labels can match a job's "runs-on"
|
// CanMatchLabels checks whether the runner's labels can match a job's "runs-on"
|
||||||
// See https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idruns-on
|
// See https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idruns-on
|
||||||
func (r *ActionRunner) CanMatchLabels(jobRunsOn []string) bool {
|
func (r *ActionRunner) CanMatchLabels(jobRunsOn []string) bool {
|
||||||
return !slices.ContainsFunc(jobRunsOn, func(label string) bool { return !util.SliceContainsString(r.AgentLabels, label, true) })
|
return len(jobRunsOn) > 0 && !slices.ContainsFunc(jobRunsOn, func(label string) bool { return !util.SliceContainsString(r.AgentLabels, label, true) })
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -86,4 +86,5 @@ func TestCanMatchLabelsCaseInsensitive(t *testing.T) {
|
|||||||
runner := &ActionRunner{AgentLabels: []string{"self-hosted", "Linux", "X64"}}
|
runner := &ActionRunner{AgentLabels: []string{"self-hosted", "Linux", "X64"}}
|
||||||
assert.True(t, runner.CanMatchLabels([]string{"SELF-HOSTED", "linux"}))
|
assert.True(t, runner.CanMatchLabels([]string{"SELF-HOSTED", "linux"}))
|
||||||
assert.False(t, runner.CanMatchLabels([]string{"linux", "arm64"}))
|
assert.False(t, runner.CanMatchLabels([]string{"linux", "arm64"}))
|
||||||
|
assert.False(t, runner.CanMatchLabels(nil))
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ package actions
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
@@ -119,3 +120,12 @@ func GetLatestRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRu
|
|||||||
}
|
}
|
||||||
return &runnerToken, nil
|
return &runnerToken, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetOrCreateActiveRunnerToken returns the latest runner token, or a new one if there is none or it is no longer active
|
||||||
|
func GetOrCreateActiveRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
|
||||||
|
token, err := GetLatestRunnerToken(ctx, ownerID, repoID)
|
||||||
|
if errors.Is(err, util.ErrNotExist) || (token != nil && !token.IsActive) {
|
||||||
|
return NewRunnerToken(ctx, ownerID, repoID)
|
||||||
|
}
|
||||||
|
return token, err
|
||||||
|
}
|
||||||
@@ -104,7 +104,7 @@ func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) ([
|
|||||||
// If actions disabled when there is schedule task, this will remove the outdated schedule tasks
|
// If actions disabled when there is schedule task, this will remove the outdated schedule tasks
|
||||||
// There is no other place we can do this because the app.ini will be changed manually
|
// There is no other place we can do this because the app.ini will be changed manually
|
||||||
if err := DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
|
if err := DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
|
||||||
return nil, fmt.Errorf("DeleteCronTaskByRepo: %v", err)
|
return nil, fmt.Errorf("CleanRepoScheduleTasks: %w", err)
|
||||||
}
|
}
|
||||||
// cancel running cron jobs of this repository and delete old schedules
|
// cancel running cron jobs of this repository and delete old schedules
|
||||||
jobs, err := CancelPreviousJobs(
|
jobs, err := CancelPreviousJobs(
|
||||||
@@ -115,7 +115,7 @@ func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) ([
|
|||||||
webhook_module.HookEventSchedule,
|
webhook_module.HookEventSchedule,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return jobs, fmt.Errorf("CancelPreviousJobs: %v", err)
|
return jobs, fmt.Errorf("CleanRepoScheduleTasks: %w", err)
|
||||||
}
|
}
|
||||||
return jobs, nil
|
return jobs, nil
|
||||||
}
|
}
|
||||||
@@ -59,10 +59,6 @@ func (s Status) HasRun() bool {
|
|||||||
return s.In(StatusSuccess, StatusFailure)
|
return s.In(StatusSuccess, StatusFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Status) IsUnknown() bool {
|
|
||||||
return s == StatusUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Status) IsSuccess() bool {
|
func (s Status) IsSuccess() bool {
|
||||||
return s == StatusSuccess
|
return s == StatusSuccess
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,22 +89,3 @@ func calculateDuration(started, stopped timeutil.TimeStamp, status Status, fallb
|
|||||||
}
|
}
|
||||||
return max(timeSince(s), 0).Truncate(time.Second)
|
return max(timeSince(s), 0).Truncate(time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
// best effort function to convert an action schedule to action run, to be used in GenerateGiteaContext
|
|
||||||
func (s *ActionSchedule) ToActionRun() *ActionRun {
|
|
||||||
return &ActionRun{
|
|
||||||
Title: s.Title,
|
|
||||||
RepoID: s.RepoID,
|
|
||||||
Repo: s.Repo,
|
|
||||||
OwnerID: s.OwnerID,
|
|
||||||
WorkflowID: s.WorkflowID,
|
|
||||||
TriggerUserID: s.TriggerUserID,
|
|
||||||
TriggerUser: s.TriggerUser,
|
|
||||||
Ref: s.Ref,
|
|
||||||
CommitSHA: s.CommitSHA,
|
|
||||||
Event: s.Event,
|
|
||||||
EventPayload: s.EventPayload,
|
|
||||||
Created: s.Created,
|
|
||||||
Updated: s.Updated,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+13
-18
@@ -48,13 +48,7 @@ func init() {
|
|||||||
db.RegisterModel(new(ActionVariable))
|
db.RegisterModel(new(ActionVariable))
|
||||||
}
|
}
|
||||||
|
|
||||||
func InsertVariable(ctx context.Context, ownerID, repoID int64, name, data, description string) (*ActionVariable, error) {
|
func InsertVariable(ctx context.Context, scope BelongingScope, name, data, description string) (*ActionVariable, error) {
|
||||||
if ownerID != 0 && repoID != 0 {
|
|
||||||
// It's trying to create a variable that belongs to a repository, but OwnerID has been set accidentally.
|
|
||||||
// Remove OwnerID to avoid confusion; it's not worth returning an error here.
|
|
||||||
ownerID = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if utf8.RuneCountInString(data) > VariableDataMaxLength {
|
if utf8.RuneCountInString(data) > VariableDataMaxLength {
|
||||||
return nil, util.NewInvalidArgumentErrorf("data too long")
|
return nil, util.NewInvalidArgumentErrorf("data too long")
|
||||||
}
|
}
|
||||||
@@ -62,8 +56,8 @@ func InsertVariable(ctx context.Context, ownerID, repoID int64, name, data, desc
|
|||||||
description = util.TruncateRunes(description, VariableDescriptionMaxLength)
|
description = util.TruncateRunes(description, VariableDescriptionMaxLength)
|
||||||
|
|
||||||
variable := &ActionVariable{
|
variable := &ActionVariable{
|
||||||
OwnerID: ownerID,
|
OwnerID: scope.internalOwnerID,
|
||||||
RepoID: repoID,
|
RepoID: scope.internalRepoID,
|
||||||
Name: strings.ToUpper(name),
|
Name: strings.ToUpper(name),
|
||||||
Data: data,
|
Data: data,
|
||||||
Description: description,
|
Description: description,
|
||||||
@@ -73,10 +67,10 @@ func InsertVariable(ctx context.Context, ownerID, repoID int64, name, data, desc
|
|||||||
|
|
||||||
type FindVariablesOpts struct {
|
type FindVariablesOpts struct {
|
||||||
db.ListOptions
|
db.ListOptions
|
||||||
IDs []int64
|
IDs []int64
|
||||||
RepoID int64
|
Name string
|
||||||
OwnerID int64 // it will be ignored if RepoID is set
|
|
||||||
Name string
|
BelongingScope BelongingScope
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts FindVariablesOpts) ToOrders() string {
|
func (opts FindVariablesOpts) ToOrders() string {
|
||||||
@@ -96,12 +90,13 @@ func (opts FindVariablesOpts) ToConds() builder.Cond {
|
|||||||
|
|
||||||
// Since we now support instance-level variables,
|
// Since we now support instance-level variables,
|
||||||
// there is no need to check for null values for `owner_id` and `repo_id`
|
// there is no need to check for null values for `owner_id` and `repo_id`
|
||||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
ownerID, repoID := opts.BelongingScope.GetOwnerRepoIDs()
|
||||||
if opts.RepoID != 0 { // if RepoID is set
|
cond = cond.And(builder.Eq{"repo_id": repoID})
|
||||||
|
if repoID != 0 { // if RepoID is set
|
||||||
// ignore OwnerID and treat it as 0
|
// ignore OwnerID and treat it as 0
|
||||||
cond = cond.And(builder.Eq{"owner_id": 0})
|
cond = cond.And(builder.Eq{"owner_id": 0})
|
||||||
} else {
|
} else {
|
||||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
cond = cond.And(builder.Eq{"owner_id": ownerID})
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Name != "" {
|
if opts.Name != "" {
|
||||||
@@ -152,14 +147,14 @@ func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Org / User level
|
// Org / User level
|
||||||
ownerVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{OwnerID: run.Repo.OwnerID})
|
ownerVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{BelongingScope: BelongingScopeOwner(run.Repo.OwnerID)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("find variables of org: %d, error: %v", run.Repo.OwnerID, err)
|
log.Error("find variables of org: %d, error: %v", run.Repo.OwnerID, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repo level
|
// Repo level
|
||||||
repoVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{RepoID: run.RepoID})
|
repoVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{BelongingScope: BelongingScopeRepo(run.RepoID)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("find variables of repo: %d, error: %v", run.RepoID, err)
|
log.Error("find variables of repo: %d, error: %v", run.RepoID, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -83,23 +83,17 @@ func GetActivityStats(ctx context.Context, repo *repo_model.Repository, timeFrom
|
|||||||
|
|
||||||
// GetActivityStatsTopAuthors returns top author stats for git commits for all branches
|
// GetActivityStatsTopAuthors returns top author stats for git commits for all branches
|
||||||
func GetActivityStatsTopAuthors(ctx context.Context, repo *repo_model.Repository, timeFrom time.Time, count int) ([]*ActivityAuthorData, error) {
|
func GetActivityStatsTopAuthors(ctx context.Context, repo *repo_model.Repository, timeFrom time.Time, count int) ([]*ActivityAuthorData, error) {
|
||||||
gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, repo)
|
authors, err := git.GetCodeActivityAuthors(ctx, repo, timeFrom)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("OpenRepository: %w", err)
|
|
||||||
}
|
|
||||||
defer closer.Close()
|
|
||||||
|
|
||||||
code, err := gitRepo.GetCodeActivityStats(ctx, timeFrom, "")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("FillFromGit: %w", err)
|
return nil, fmt.Errorf("FillFromGit: %w", err)
|
||||||
}
|
}
|
||||||
if code.Authors == nil {
|
if authors == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
users := make(map[int64]*ActivityAuthorData)
|
users := make(map[int64]*ActivityAuthorData)
|
||||||
var unknownUserID int64
|
var unknownUserID int64
|
||||||
unknownUserAvatarLink := user_model.NewGhostUser().AvatarLink(ctx)
|
unknownUserAvatarLink := user_model.NewGhostUser().AvatarLink(ctx)
|
||||||
for _, v := range code.Authors {
|
for _, v := range authors {
|
||||||
if len(v.Email) == 0 {
|
if len(v.Email) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
|
"gitea.dev/modules/consts"
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
@@ -135,7 +136,7 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
|
|||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
// Note: chmod command does not support in Windows.
|
// Note: chmod command does not support in Windows.
|
||||||
if !setting.IsWindows {
|
if !consts.IsWindows {
|
||||||
fi, err := f.Stat()
|
fi, err := f.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ var (
|
|||||||
UserTwoFactorRegenerate = define("user:twofactor:regenerate", "Regenerated two-factor authentication secret for user {scope}.")
|
UserTwoFactorRegenerate = define("user:twofactor:regenerate", "Regenerated two-factor authentication secret for user {scope}.")
|
||||||
UserTwoFactorDisable = define("user:twofactor:disable", "Disabled two-factor authentication for user {scope}.")
|
UserTwoFactorDisable = define("user:twofactor:disable", "Disabled two-factor authentication for user {scope}.")
|
||||||
UserWebAuthAdd = define("user:webauth:add", "Added WebAuthn key {credential} for user {scope}.")
|
UserWebAuthAdd = define("user:webauth:add", "Added WebAuthn key {credential} for user {scope}.")
|
||||||
|
UserWebAuthRename = define("user:webauth:rename", "Renamed WebAuthn key {previous_credential} of user {scope} to {credential}.")
|
||||||
UserWebAuthRemove = define("user:webauth:remove", "Removed WebAuthn key {credential} from user {scope}.")
|
UserWebAuthRemove = define("user:webauth:remove", "Removed WebAuthn key {credential} from user {scope}.")
|
||||||
UserExternalLoginAdd = define("user:externallogin:add", "Added external login {external_id} for user {scope} using provider {provider}.")
|
UserExternalLoginAdd = define("user:externallogin:add", "Added external login {external_id} for user {scope} using provider {provider}.")
|
||||||
UserExternalLoginRemove = define("user:externallogin:remove", "Removed external login from authentication source {auth_source_id} for user {scope}.")
|
UserExternalLoginRemove = define("user:externallogin:remove", "Removed external login from authentication source {auth_source_id} for user {scope}.")
|
||||||
|
|||||||
+29
-72
@@ -5,7 +5,6 @@ package auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
"gitea.dev/modules/timeutil"
|
"gitea.dev/modules/timeutil"
|
||||||
@@ -13,49 +12,46 @@ import (
|
|||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Session represents a session compatible for go-chi session
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
Key string `xorm:"pk CHAR(16)"` // has to be Key to match with go-chi/session
|
Key string `xorm:"pk CHAR(16)"` // the limit is from legacy go-chi/session
|
||||||
Data []byte `xorm:"BLOB"` // on MySQL this has a maximum size of 64Kb - this may need to be increased
|
Data []byte `xorm:"BLOB"` // on MySQL this has a maximum size of 64Kb
|
||||||
Expiry timeutil.TimeStamp // has to be Expiry to match with go-chi/session
|
LastAccessTime timeutil.TimeStamp `xorm:"expiry"` // last access time, the field name is from legacy go-chi/session, we don't want to change it at the moment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DbSessionLastAccessTime = "expiry" // maybe we can make a deeper clean up in the future, just keep this PR focused
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
db.RegisterModel(new(Session))
|
db.RegisterModel(new(Session))
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateSession updates the session with provided id
|
// UpdateSession stores the data of the session with provided id, creating the session only if create is set
|
||||||
func UpdateSession(ctx context.Context, key string, data []byte) error {
|
func UpdateSession(ctx context.Context, key string, data []byte, create bool) error {
|
||||||
_, err := db.GetEngine(ctx).ID(key).Update(&Session{
|
session := &Session{Key: key, Data: data, LastAccessTime: timeutil.TimeStampNow()}
|
||||||
Data: data,
|
update := func() (int64, error) {
|
||||||
Expiry: timeutil.TimeStampNow(),
|
return db.GetEngine(ctx).ID(key).Cols("data", DbSessionLastAccessTime).Update(session)
|
||||||
})
|
}
|
||||||
|
if updated, err := update(); err != nil || updated > 0 || !create {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
insertErr := db.Insert(ctx, session)
|
||||||
|
if insertErr == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// the row exists if a concurrent request inserted it, or if MySQL reported an unchanged row as not updated
|
||||||
|
if exist, err := db.Exist[Session](ctx, builder.Eq{"`key`": key}); err != nil || !exist {
|
||||||
|
return insertErr
|
||||||
|
}
|
||||||
|
_, err := update()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadSession reads the data for the provided session
|
func UpdateSessionLastAccessTime(ctx context.Context, key string) error {
|
||||||
func ReadSession(ctx context.Context, key string) (*Session, error) {
|
_, err := db.GetEngine(ctx).ID(key).Cols(DbSessionLastAccessTime).Update(&Session{LastAccessTime: timeutil.TimeStampNow()})
|
||||||
return db.WithTx2(ctx, func(ctx context.Context) (*Session, error) {
|
return err
|
||||||
session, exist, err := db.Get[Session](ctx, builder.Eq{"`key`": key})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if !exist {
|
|
||||||
session = &Session{
|
|
||||||
Key: key,
|
|
||||||
Expiry: timeutil.TimeStampNow(),
|
|
||||||
}
|
|
||||||
if err := db.Insert(ctx, session); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return session, nil
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExistSession checks if a session exists
|
func GetSession(ctx context.Context, key string) (*Session, bool, error) {
|
||||||
func ExistSession(ctx context.Context, key string) (bool, error) {
|
return db.Get[Session](ctx, builder.Eq{"`key`": key})
|
||||||
return db.Exist[Session](ctx, builder.Eq{"`key`": key})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DestroySession destroys a session
|
// DestroySession destroys a session
|
||||||
@@ -66,47 +62,8 @@ func DestroySession(ctx context.Context, key string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegenerateSession regenerates a session from the old id
|
|
||||||
func RegenerateSession(ctx context.Context, oldKey, newKey string) (*Session, error) {
|
|
||||||
return db.WithTx2(ctx, func(ctx context.Context) (*Session, error) {
|
|
||||||
if has, err := db.Exist[Session](ctx, builder.Eq{"`key`": newKey}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if has {
|
|
||||||
return nil, fmt.Errorf("session Key: %s already exists", newKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
if has, err := db.Exist[Session](ctx, builder.Eq{"`key`": oldKey}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if !has {
|
|
||||||
if err := db.Insert(ctx, &Session{
|
|
||||||
Key: oldKey,
|
|
||||||
Expiry: timeutil.TimeStampNow(),
|
|
||||||
}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := db.Exec(ctx, "UPDATE `session` SET `key` = ? WHERE `key`=?", newKey, oldKey); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s, _, err := db.Get[Session](ctx, builder.Eq{"`key`": newKey})
|
|
||||||
if err != nil {
|
|
||||||
// is not exist, it should be impossible
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// CountSessions returns the number of sessions
|
|
||||||
func CountSessions(ctx context.Context) (int64, error) {
|
|
||||||
return db.GetEngine(ctx).Count(&Session{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// CleanupSessions cleans up expired sessions
|
// CleanupSessions cleans up expired sessions
|
||||||
func CleanupSessions(ctx context.Context, maxLifetime int64) error {
|
func CleanupSessions(ctx context.Context, maxLifetime int64) error {
|
||||||
_, err := db.GetEngine(ctx).Where("expiry <= ?", timeutil.TimeStampNow().Add(-maxLifetime)).Delete(&Session{})
|
_, err := db.GetEngine(ctx).Where(DbSessionLastAccessTime+" <= ?", timeutil.TimeStampNow().Add(-maxLifetime)).Delete(&Session{})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
+22
-4
@@ -150,9 +150,9 @@ func GetWebAuthnCredentialByName(ctx context.Context, uid int64, name string) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetWebAuthnCredentialByID returns WebAuthn credential by id
|
// GetWebAuthnCredentialByID returns WebAuthn credential by id
|
||||||
func GetWebAuthnCredentialByID(ctx context.Context, id int64) (*WebAuthnCredential, error) {
|
func GetWebAuthnCredentialByID(ctx context.Context, uid, id int64) (*WebAuthnCredential, error) {
|
||||||
cred := new(WebAuthnCredential)
|
cred := new(WebAuthnCredential)
|
||||||
if found, err := db.GetEngine(ctx).ID(id).Get(cred); err != nil {
|
if found, err := db.GetEngine(ctx).Where("user_id = ?", uid).ID(id).Get(cred); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if !found {
|
} else if !found {
|
||||||
return nil, ErrWebAuthnCredentialNotExist{ID: id}
|
return nil, ErrWebAuthnCredentialNotExist{ID: id}
|
||||||
@@ -195,8 +195,26 @@ func CreateCredential(ctx context.Context, userID int64, name string, cred *weba
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RenameCredential renames the user's WebAuthnCredential, names are unique per user regardless of letter case
|
||||||
|
func RenameCredential(ctx context.Context, uid, id int64, name string) (bool, error) {
|
||||||
|
used, err := db.GetEngine(ctx).Where("user_id = ? AND lower_name = ? AND id != ?", uid, strings.ToLower(name), id).Exist(&WebAuthnCredential{})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
} else if used {
|
||||||
|
return false, util.ErrorWrapTranslatable(
|
||||||
|
util.NewAlreadyExistErrorf("WebAuthn credential name already exists [uid: %d, name: %s]", uid, name),
|
||||||
|
"settings.webauthn_nickname_been_used",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
updated, err := db.GetEngine(ctx).ID(id).Where("user_id=? AND `name`<>?", uid, name).Cols("name", "lower_name").Update(&WebAuthnCredential{
|
||||||
|
Name: name,
|
||||||
|
LowerName: strings.ToLower(name),
|
||||||
|
})
|
||||||
|
return updated > 0, err
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteCredential will delete WebAuthnCredential
|
// DeleteCredential will delete WebAuthnCredential
|
||||||
func DeleteCredential(ctx context.Context, id, userID int64) (bool, error) {
|
func DeleteCredential(ctx context.Context, uid, id int64) (bool, error) {
|
||||||
had, err := db.GetEngine(ctx).ID(id).Where("user_id = ?", userID).Delete(&WebAuthnCredential{})
|
had, err := db.GetEngine(ctx).ID(id).Where("user_id = ?", uid).Delete(&WebAuthnCredential{})
|
||||||
return had > 0, err
|
return had > 0, err
|
||||||
}
|
}
|
||||||
@@ -16,11 +16,15 @@ import (
|
|||||||
func TestGetWebAuthnCredentialByID(t *testing.T) {
|
func TestGetWebAuthnCredentialByID(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
res, err := auth_model.GetWebAuthnCredentialByID(t.Context(), 1)
|
res, err := auth_model.GetWebAuthnCredentialByID(t.Context(), 32, 1)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "WebAuthn credential", res.Name)
|
assert.Equal(t, "WebAuthn credential", res.Name)
|
||||||
|
|
||||||
_, err = auth_model.GetWebAuthnCredentialByID(t.Context(), 342432)
|
_, err = auth_model.GetWebAuthnCredentialByID(t.Context(), 99999, 1)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.True(t, auth_model.IsErrWebAuthnCredentialNotExist(err))
|
||||||
|
|
||||||
|
_, err = auth_model.GetWebAuthnCredentialByID(t.Context(), 32, 99999)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.True(t, auth_model.IsErrWebAuthnCredentialNotExist(err))
|
assert.True(t, auth_model.IsErrWebAuthnCredentialNotExist(err))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,8 +99,8 @@ func (protectBranch *ProtectedBranch) loadGlob() {
|
|||||||
var err error
|
var err error
|
||||||
protectBranch.globRule, err = glob.Compile(protectBranch.RuleName, '/')
|
protectBranch.globRule, err = glob.Compile(protectBranch.RuleName, '/')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Invalid glob rule for ProtectedBranch[%d]: %s %v", protectBranch.ID, protectBranch.RuleName, err)
|
log.Debug("Invalid glob rule for ProtectedBranch[%d]: %s %v", protectBranch.ID, protectBranch.RuleName, err)
|
||||||
protectBranch.globRule = glob.MustCompile(glob.QuoteMeta(protectBranch.RuleName), '/')
|
protectBranch.globRule, _ = glob.Compile(glob.QuoteMeta(protectBranch.RuleName), '/')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +110,9 @@ func (protectBranch *ProtectedBranch) Match(branchName string) bool {
|
|||||||
if protectBranch.isPlainName {
|
if protectBranch.isPlainName {
|
||||||
return strings.EqualFold(protectBranch.RuleName, branchName)
|
return strings.EqualFold(protectBranch.RuleName, branchName)
|
||||||
}
|
}
|
||||||
|
if protectBranch.globRule == nil {
|
||||||
|
return false // in case of invalid glob rule, we don't match anything
|
||||||
|
}
|
||||||
return protectBranch.globRule.Match(branchName)
|
return protectBranch.globRule.Match(branchName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
"gitea.dev/modules/glob"
|
"gitea.dev/modules/glob"
|
||||||
|
"gitea.dev/modules/log"
|
||||||
"gitea.dev/modules/optional"
|
"gitea.dev/modules/optional"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -55,10 +56,17 @@ func FindRepoProtectedBranchRules(ctx context.Context, repoID int64) (ProtectedB
|
|||||||
// FindAllMatchedBranches find all matched branches
|
// FindAllMatchedBranches find all matched branches
|
||||||
func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string) ([]string, error) {
|
func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string) ([]string, error) {
|
||||||
results := make([]string, 0, 10)
|
results := make([]string, 0, 10)
|
||||||
|
rule, err := glob.Compile(ruleName)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to compile rule %s: %v", ruleName, err)
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageSize = 100
|
||||||
for page := 1; ; page++ {
|
for page := 1; ; page++ {
|
||||||
brancheNames, err := FindBranchNames(ctx, FindBranchOptions{
|
branchNames, err := FindBranchNames(ctx, FindBranchOptions{
|
||||||
ListOptions: db.ListOptions{
|
ListOptions: db.ListOptions{
|
||||||
PageSize: 100,
|
PageSize: pageSize,
|
||||||
Page: page,
|
Page: page,
|
||||||
},
|
},
|
||||||
RepoID: repoID,
|
RepoID: repoID,
|
||||||
@@ -67,14 +75,12 @@ func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
rule := glob.MustCompile(ruleName)
|
for _, branch := range branchNames {
|
||||||
|
|
||||||
for _, branch := range brancheNames {
|
|
||||||
if rule.Match(branch) {
|
if rule.Match(branch) {
|
||||||
results = append(results, branch)
|
results = append(results, branch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(brancheNames) < 100 {
|
if len(branchNames) < pageSize {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -433,12 +433,13 @@ func GetLabelsByRepoID(ctx context.Context, repoID int64, sortType string, listO
|
|||||||
case "reversealphabetically":
|
case "reversealphabetically":
|
||||||
sess.Desc("name")
|
sess.Desc("name")
|
||||||
case "leastissues":
|
case "leastissues":
|
||||||
sess.Asc("num_issues")
|
sess.OrderBy("num_issues - num_closed_issues ASC")
|
||||||
case "mostissues":
|
case "mostissues":
|
||||||
sess.Desc("num_issues")
|
sess.OrderBy("num_issues - num_closed_issues DESC")
|
||||||
default:
|
default:
|
||||||
sess.Asc("name")
|
sess.Asc("name")
|
||||||
}
|
}
|
||||||
|
sess.Asc("id")
|
||||||
|
|
||||||
if listOptions.Page > 0 {
|
if listOptions.Page > 0 {
|
||||||
db.SetSessionPagination(sess, &listOptions)
|
db.SetSessionPagination(sess, &listOptions)
|
||||||
@@ -508,12 +509,13 @@ func GetLabelsByOrgID(ctx context.Context, orgID int64, sortType string, listOpt
|
|||||||
case "reversealphabetically":
|
case "reversealphabetically":
|
||||||
sess.Desc("name")
|
sess.Desc("name")
|
||||||
case "leastissues":
|
case "leastissues":
|
||||||
sess.Asc("num_issues")
|
sess.OrderBy("num_issues - num_closed_issues ASC")
|
||||||
case "mostissues":
|
case "mostissues":
|
||||||
sess.Desc("num_issues")
|
sess.OrderBy("num_issues - num_closed_issues DESC")
|
||||||
default:
|
default:
|
||||||
sess.Asc("name")
|
sess.Asc("name")
|
||||||
}
|
}
|
||||||
|
sess.Asc("id")
|
||||||
|
|
||||||
if listOptions.Page > 0 {
|
if listOptions.Page > 0 {
|
||||||
db.SetSessionPagination(sess, &listOptions)
|
db.SetSessionPagination(sess, &listOptions)
|
||||||
|
|||||||
@@ -191,6 +191,8 @@ func TestGetLabelsInRepoByIDs(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetLabelsByRepoID(t *testing.T) {
|
func TestGetLabelsByRepoID(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
_, err := db.GetEngine(t.Context()).ID(2).Cols("num_issues", "num_closed_issues").Update(&issues_model.Label{NumIssues: 3, NumClosedIssues: 3})
|
||||||
|
assert.NoError(t, err)
|
||||||
testSuccess := func(repoID int64, sortType string, expectedIssueIDs []int64) {
|
testSuccess := func(repoID int64, sortType string, expectedIssueIDs []int64) {
|
||||||
labels, err := issues_model.GetLabelsByRepoID(t.Context(), repoID, sortType, db.ListOptions{})
|
labels, err := issues_model.GetLabelsByRepoID(t.Context(), repoID, sortType, db.ListOptions{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -258,6 +260,8 @@ func TestGetLabelsInOrgByIDs(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetLabelsByOrgID(t *testing.T) {
|
func TestGetLabelsByOrgID(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
_, err := db.GetEngine(t.Context()).ID(3).Cols("num_issues", "num_closed_issues").Update(&issues_model.Label{NumIssues: 3, NumClosedIssues: 3})
|
||||||
|
assert.NoError(t, err)
|
||||||
testSuccess := func(orgID int64, sortType string, expectedIssueIDs []int64) {
|
testSuccess := func(orgID int64, sortType string, expectedIssueIDs []int64) {
|
||||||
labels, err := issues_model.GetLabelsByOrgID(t.Context(), orgID, sortType, db.ListOptions{})
|
labels, err := issues_model.GetLabelsByOrgID(t.Context(), orgID, sortType, db.ListOptions{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -271,7 +275,7 @@ func TestGetLabelsByOrgID(t *testing.T) {
|
|||||||
testSuccess(3, "reversealphabetically", []int64{4, 3})
|
testSuccess(3, "reversealphabetically", []int64{4, 3})
|
||||||
testSuccess(3, "default", []int64{3, 4})
|
testSuccess(3, "default", []int64{3, 4})
|
||||||
|
|
||||||
_, err := issues_model.GetLabelsByOrgID(t.Context(), 0, "leastissues", db.ListOptions{})
|
_, err = issues_model.GetLabelsByOrgID(t.Context(), 0, "leastissues", db.ListOptions{})
|
||||||
assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
|
assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
|
||||||
|
|
||||||
_, err = issues_model.GetLabelsByOrgID(t.Context(), -1, "leastissues", db.ListOptions{})
|
_, err = issues_model.GetLabelsByOrgID(t.Context(), -1, "leastissues", db.ListOptions{})
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func (c *commitChecker) IsCommitIDExisting(commitID string) bool {
|
|||||||
c.gitRepo, c.gitRepoCloser = r, closer
|
c.gitRepo, c.gitRepoCloser = r, closer
|
||||||
}
|
}
|
||||||
|
|
||||||
exist = c.gitRepo.IsReferenceExist(c.ctx, commitID) // Don't use IsObjectExist since it doesn't support short hashes with gogit edition.
|
exist = c.gitRepo.IsReferenceExist(c.ctx, commitID)
|
||||||
c.commitCache[commitID] = exist
|
c.commitCache[commitID] = exist
|
||||||
return exist
|
return exist
|
||||||
}
|
}
|
||||||
@@ -78,13 +78,13 @@ func (m *PushMirror) GetRemoteName() string {
|
|||||||
return m.RemoteName
|
return m.RemoteName
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePushMirror updates the push-mirror
|
// UpdatePushMirrorSyncStatus updates the sync status (last update time and last error) of the push-mirror
|
||||||
func UpdatePushMirror(ctx context.Context, m *PushMirror) error {
|
func UpdatePushMirrorSyncStatus(ctx context.Context, m *PushMirror) error {
|
||||||
_, err := db.GetEngine(ctx).ID(m.ID).AllCols().Update(m)
|
_, err := db.GetEngine(ctx).ID(m.ID).Cols("last_update", "last_error").Update(m)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePushMirrorInterval updates the push-mirror
|
// UpdatePushMirrorInterval updates the sync interval of the push-mirror
|
||||||
func UpdatePushMirrorInterval(ctx context.Context, m *PushMirror) error {
|
func UpdatePushMirrorInterval(ctx context.Context, m *PushMirror) error {
|
||||||
_, err := db.GetEngine(ctx).ID(m.ID).Cols("interval").Update(m)
|
_, err := db.GetEngine(ctx).ID(m.ID).Cols("interval").Update(m)
|
||||||
return err
|
return err
|
||||||
|
|||||||
+10
-1
@@ -616,6 +616,15 @@ func ComposeHTTPSCloneURL(ctx context.Context, owner, repo string) string {
|
|||||||
|
|
||||||
// ComposeSSHCloneURL returns SSH clone URL based on the given owner and repository name.
|
// ComposeSSHCloneURL returns SSH clone URL based on the given owner and repository name.
|
||||||
func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) string {
|
func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) string {
|
||||||
|
return composeSSHCloneURL(doer, ownerName, repoName, setting.Repository.UseCompatSSHURI)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComposeSSHCloneURI is like ComposeSSHCloneURL but always returns the "ssh://" form, because "go get" rejects scp-style addresses
|
||||||
|
func ComposeSSHCloneURI(doer *user_model.User, ownerName, repoName string) string {
|
||||||
|
return composeSSHCloneURL(doer, ownerName, repoName, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func composeSSHCloneURL(doer *user_model.User, ownerName, repoName string, useURI bool) string {
|
||||||
sshUser := setting.SSH.User
|
sshUser := setting.SSH.User
|
||||||
sshDomain := setting.SSH.Domain
|
sshDomain := setting.SSH.Domain
|
||||||
|
|
||||||
@@ -642,7 +651,7 @@ func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) strin
|
|||||||
if ip := net.ParseIP(sshHost); ip != nil && ip.To4() == nil {
|
if ip := net.ParseIP(sshHost); ip != nil && ip.To4() == nil {
|
||||||
sshHost = "[" + sshHost + "]" // for IPv6 address, wrap it with brackets
|
sshHost = "[" + sshHost + "]" // for IPv6 address, wrap it with brackets
|
||||||
}
|
}
|
||||||
if setting.Repository.UseCompatSSHURI {
|
if useURI {
|
||||||
return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
|
return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
|
return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
|
||||||
|
|||||||
@@ -185,6 +185,8 @@ func TestComposeSSHCloneURL(t *testing.T) {
|
|||||||
assert.Equal(t, "git@domain:user/repo.git", ComposeSSHCloneURL(&user_model.User{Name: "doer"}, "user", "repo"))
|
assert.Equal(t, "git@domain:user/repo.git", ComposeSSHCloneURL(&user_model.User{Name: "doer"}, "user", "repo"))
|
||||||
setting.Repository.UseCompatSSHURI = true
|
setting.Repository.UseCompatSSHURI = true
|
||||||
assert.Equal(t, "ssh://git@domain/user/repo.git", ComposeSSHCloneURL(&user_model.User{Name: "doer"}, "user", "repo"))
|
assert.Equal(t, "ssh://git@domain/user/repo.git", ComposeSSHCloneURL(&user_model.User{Name: "doer"}, "user", "repo"))
|
||||||
|
setting.Repository.UseCompatSSHURI = false
|
||||||
|
assert.Equal(t, "ssh://git@domain/user/repo.git", ComposeSSHCloneURI(nil, "user", "repo"))
|
||||||
// test SSH_DOMAIN while use non-standard SSH port
|
// test SSH_DOMAIN while use non-standard SSH port
|
||||||
setting.SSH.Port = 123
|
setting.SSH.Port = 123
|
||||||
setting.Repository.UseCompatSSHURI = false
|
setting.Repository.UseCompatSSHURI = false
|
||||||
|
|||||||
+13
-18
@@ -65,20 +65,15 @@ func (err ErrSecretNotFound) Unwrap() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InsertEncryptedSecret Creates, encrypts, and validates a new secret with yet unencrypted data and insert into database
|
// InsertEncryptedSecret Creates, encrypts, and validates a new secret with yet unencrypted data and insert into database
|
||||||
func InsertEncryptedSecret(ctx context.Context, ownerID, repoID int64, name, data, description string) (*Secret, error) {
|
func InsertEncryptedSecret(ctx context.Context, scope actions_model.BelongingScope, name, data, description string) (*Secret, error) {
|
||||||
if ownerID != 0 && repoID != 0 {
|
|
||||||
// It's trying to create a secret that belongs to a repository, but OwnerID has been set accidentally.
|
|
||||||
// Remove OwnerID to avoid confusion; it's not worth returning an error here.
|
|
||||||
ownerID = 0
|
|
||||||
}
|
|
||||||
if ownerID == 0 && repoID == 0 {
|
|
||||||
return nil, fmt.Errorf("%w: ownerID and repoID cannot be both zero, global secrets are not supported", util.ErrInvalidArgument)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(data) > SecretDataMaxLength {
|
if len(data) > SecretDataMaxLength {
|
||||||
return nil, util.NewInvalidArgumentErrorf("data too long")
|
return nil, util.NewInvalidArgumentErrorf("data too long")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ownerID, repoID := scope.GetOwnerRepoIDs()
|
||||||
|
if ownerID == 0 && repoID == 0 {
|
||||||
|
return nil, fmt.Errorf("%w: ownerID and repoID cannot be both zero, global secrets are not supported", util.ErrInvalidArgument)
|
||||||
|
}
|
||||||
description = util.TruncateRunes(description, SecretDescriptionMaxLength)
|
description = util.TruncateRunes(description, SecretDescriptionMaxLength)
|
||||||
|
|
||||||
encrypted, err := secret_module.EncryptSecret(setting.SecretKey, data)
|
encrypted, err := secret_module.EncryptSecret(setting.SecretKey, data)
|
||||||
@@ -102,10 +97,10 @@ func init() {
|
|||||||
|
|
||||||
type FindSecretsOptions struct {
|
type FindSecretsOptions struct {
|
||||||
db.ListOptions
|
db.ListOptions
|
||||||
RepoID int64
|
|
||||||
OwnerID int64 // it will be ignored if RepoID is set
|
|
||||||
SecretID int64
|
SecretID int64
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
|
BelongingScope actions_model.BelongingScope
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts FindSecretsOptions) ToOrders() string {
|
func (opts FindSecretsOptions) ToOrders() string {
|
||||||
@@ -114,13 +109,13 @@ func (opts FindSecretsOptions) ToOrders() string {
|
|||||||
|
|
||||||
func (opts FindSecretsOptions) ToConds() builder.Cond {
|
func (opts FindSecretsOptions) ToConds() builder.Cond {
|
||||||
cond := builder.NewCond()
|
cond := builder.NewCond()
|
||||||
|
ownerID, repoID := opts.BelongingScope.GetOwnerRepoIDs()
|
||||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
cond = cond.And(builder.Eq{"repo_id": repoID})
|
||||||
if opts.RepoID != 0 { // if RepoID is set
|
if repoID != 0 { // if RepoID is set
|
||||||
// ignore OwnerID and treat it as 0
|
// ignore OwnerID and treat it as 0
|
||||||
cond = cond.And(builder.Eq{"owner_id": 0})
|
cond = cond.And(builder.Eq{"owner_id": 0})
|
||||||
} else {
|
} else {
|
||||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
cond = cond.And(builder.Eq{"owner_id": ownerID})
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.SecretID != 0 {
|
if opts.SecretID != 0 {
|
||||||
@@ -170,12 +165,12 @@ func GetSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) (map[
|
|||||||
return baseSecrets, nil
|
return baseSecrets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ownerSecrets, err := db.Find[Secret](ctx, FindSecretsOptions{OwnerID: task.Job.Run.Repo.OwnerID})
|
ownerSecrets, err := db.Find[Secret](ctx, FindSecretsOptions{BelongingScope: actions_model.BelongingScopeOwner(task.Job.Run.Repo.OwnerID)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("find secrets of owner %v: %v", task.Job.Run.Repo.OwnerID, err)
|
log.Error("find secrets of owner %v: %v", task.Job.Run.Repo.OwnerID, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
repoSecrets, err := db.Find[Secret](ctx, FindSecretsOptions{RepoID: task.Job.Run.RepoID})
|
repoSecrets, err := db.Find[Secret](ctx, FindSecretsOptions{BelongingScope: actions_model.BelongingScopeRepo(task.Job.Run.RepoID)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("find secrets of repo %v: %v", task.Job.Run.RepoID, err)
|
log.Error("find secrets of repo %v: %v", task.Job.Run.RepoID, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ func SyncDirs(srcPath, destPath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// the keep file is used to keep the directory in a git repository, it doesn't need to be synced
|
// the keep file is used to keep the directory in a git repository, it doesn't need to be synced
|
||||||
// and go-git doesn't work with the ".keep" file (it would report errors like "ref is empty")
|
|
||||||
const keepFile = ".keep"
|
const keepFile = ".keep"
|
||||||
|
|
||||||
// find and delete all untracked files
|
// find and delete all untracked files
|
||||||
|
|||||||
@@ -151,16 +151,28 @@ func TestListEmails(t *testing.T) {
|
|||||||
|
|
||||||
func TestEmailAddressValidate(t *testing.T) {
|
func TestEmailAddressValidate(t *testing.T) {
|
||||||
cases := map[string]bool{
|
cases := map[string]bool{
|
||||||
"": false,
|
"": false,
|
||||||
"root@localhost": true,
|
"@a": false,
|
||||||
"user@[192.168.1.2]": true,
|
|
||||||
"@a": false,
|
// "_" shouldn't appear in domain but can appear in hostname, since we can't stop site admins from doing so, just accept it
|
||||||
"abc@gmail.com": true,
|
"root@local_host": true,
|
||||||
"abc@gmail.com\n": false,
|
"root@localhost": true,
|
||||||
|
"root@LOCALHOST": true,
|
||||||
|
|
||||||
|
"user@[192.168.1.2]": true,
|
||||||
|
"user@[IPv6:FFff::1]": true,
|
||||||
|
|
||||||
|
"abc@gmail.com": true,
|
||||||
|
"abc@gmail.com.": false,
|
||||||
|
"abc@gmail.com-": false,
|
||||||
|
"abc@gmail.com\n": false,
|
||||||
|
"abc@gmail..com": false,
|
||||||
|
"abc@gmail com": false,
|
||||||
|
"abc@gmail*com": false,
|
||||||
|
|
||||||
"Foo <foo@bar.com>": false,
|
"Foo <foo@bar.com>": false,
|
||||||
"abc@gmail.com (x)": false,
|
"abc@gmail.com (x)": false,
|
||||||
"jürgen@example.com": false,
|
"jürgen@example.com": false, // utf8 address is not supported yet
|
||||||
"a@foo_bar.com": false,
|
|
||||||
}
|
}
|
||||||
for tc, isValid := range cases {
|
for tc, isValid := range cases {
|
||||||
t.Run(tc, func(t *testing.T) {
|
t.Run(tc, func(t *testing.T) {
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ type SearchUserOptions struct {
|
|||||||
Keyword string
|
Keyword string
|
||||||
Types []UserType
|
Types []UserType
|
||||||
UID int64
|
UID int64
|
||||||
LoginName string // this option should be used only for admin user
|
LoginName string // this option should be used only for admin user
|
||||||
SourceID int64 // this option should be used only for admin user
|
SourceID optional.Option[int64] // this option should be used only for admin user, Some(0) means local users
|
||||||
OrderBy db.SearchOrderBy
|
OrderBy db.SearchOrderBy
|
||||||
Visible []structs.VisibleType
|
Visible []structs.VisibleType
|
||||||
Actor *User // The user doing the search
|
Actor *User // The user doing the search
|
||||||
@@ -106,8 +106,8 @@ func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) db.Session
|
|||||||
cond = cond.And(builder.Eq{"id": opts.UID})
|
cond = cond.And(builder.Eq{"id": opts.UID})
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.SourceID > 0 {
|
if opts.SourceID.Has() {
|
||||||
cond = cond.And(builder.Eq{"login_source": opts.SourceID})
|
cond = cond.And(builder.Eq{"login_source": opts.SourceID.Value()})
|
||||||
}
|
}
|
||||||
if opts.LoginName != "" {
|
if opts.LoginName != "" {
|
||||||
cond = cond.And(builder.Eq{"login_name": opts.LoginName})
|
cond = cond.And(builder.Eq{"login_name": opts.LoginName})
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.dev/actionslib/pkg/expreval"
|
"gitea.dev/actionslib/pkg/expreval"
|
||||||
@@ -96,7 +95,6 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) {
|
|||||||
}
|
}
|
||||||
results[id] = &JobResult{
|
results[id] = &JobResult{
|
||||||
Needs: job.Needs(),
|
Needs: job.Needs(),
|
||||||
Result: pc.jobResults[id],
|
|
||||||
Outputs: nil, // resolved at expansion time, not at plan time
|
Outputs: nil, // resolved at expansion time, not at plan time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,9 +126,9 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Keep accepting empty exclude mappings for workflow compatibility, although GitHub rejects them.
|
// Keep accepting empty exclude mappings for workflow compatibility, although GitHub rejects them.
|
||||||
matrixes, err := (&model.Job{Strategy: job.Strategy.actStrategy()}).GetMatrixes()
|
matrixes, err := (&model.Job{Strategy: job.Strategy.actStrategy()}).MatrixCombinations()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("getMatrixes: %w", err)
|
return nil, fmt.Errorf("Parse: %w", err)
|
||||||
}
|
}
|
||||||
if combos, err = buildMatrixCombos(id, job, matrixes, pc.gitContext, results, pc.vars, pc.inputs); err != nil {
|
if combos, err = buildMatrixCombos(id, job, matrixes, pc.gitContext, results, pc.vars, pc.inputs); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -139,7 +137,7 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) {
|
|||||||
for _, combo := range combos {
|
for _, combo := range combos {
|
||||||
swf := workflow.CloneHeader()
|
swf := workflow.CloneHeader()
|
||||||
if err := swf.SetJob(id, combo); err != nil {
|
if err := swf.SetJob(id, combo); err != nil {
|
||||||
return nil, fmt.Errorf("SetJob: %w", err)
|
return nil, fmt.Errorf("Parse: %w", err)
|
||||||
}
|
}
|
||||||
ret = append(ret, swf)
|
ret = append(ret, swf)
|
||||||
}
|
}
|
||||||
@@ -169,9 +167,9 @@ func ExpandMatrixWithNeeds(jobID string, job *Job, gitCtx *model.GithubContext,
|
|||||||
if err := job.Strategy.resolve(expreval.New(NewInterpeter(jobID, nil, nil, gitCtx, results, vars, inputs).Evaluate)); err != nil {
|
if err := job.Strategy.resolve(expreval.New(NewInterpeter(jobID, nil, nil, gitCtx, results, vars, inputs).Evaluate)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
matrixes, err := (&model.Job{Strategy: job.Strategy.actStrategy()}).GetMatrixes()
|
matrixes, err := (&model.Job{Strategy: job.Strategy.actStrategy()}).MatrixCombinations()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("getMatrixes: %w", err)
|
return nil, fmt.Errorf("ExpandMatrixWithNeeds: %w", err)
|
||||||
}
|
}
|
||||||
if len(matrixes) > maxCombinations {
|
if len(matrixes) > maxCombinations {
|
||||||
return nil, fmt.Errorf("matrix expands to %d combinations, exceeding the limit of %d", len(matrixes), maxCombinations)
|
return nil, fmt.Errorf("matrix expands to %d combinations, exceeding the limit of %d", len(matrixes), maxCombinations)
|
||||||
@@ -224,34 +222,24 @@ func replaceScalars(node *yaml.Node, replace func(string) string) {
|
|||||||
|
|
||||||
// buildMatrixCombos builds one Job per matrix combination from src, baking the combination into the
|
// buildMatrixCombos builds one Job per matrix combination from src, baking the combination into the
|
||||||
// strategy and interpolating the name, runs-on and continue-on-error with it.
|
// strategy and interpolating the name, runs-on and continue-on-error with it.
|
||||||
func buildMatrixCombos(jobID string, src *Job, matrixes []map[string]any, gitCtx *model.GithubContext, results map[string]*JobResult, vars map[string]string, inputs map[string]any) ([]*Job, error) {
|
func buildMatrixCombos(jobID string, src *Job, matrixes []model.MatrixCombination, gitCtx *model.GithubContext, results map[string]*JobResult, vars map[string]string, inputs map[string]any) ([]*Job, error) {
|
||||||
srcRunsOn := model.RunsOnFromNode(src.RawRunsOn)
|
|
||||||
order, names := make([]int, len(matrixes)), make([]string, len(matrixes))
|
order, names := make([]int, len(matrixes)), make([]string, len(matrixes))
|
||||||
for index, matrix := range matrixes {
|
for index, matrix := range matrixes {
|
||||||
order[index], names[index] = index, matrixName(matrix)
|
order[index], names[index] = index, matrixName(matrix.NameValues)
|
||||||
}
|
}
|
||||||
slices.SortStableFunc(order, func(a, b int) int { return strings.Compare(names[a], names[b]) })
|
slices.SortStableFunc(order, func(a, b int) int { return strings.Compare(names[a], names[b]) })
|
||||||
combos := make([]*Job, 0, len(matrixes))
|
combos := make([]*Job, 0, len(matrixes))
|
||||||
var err error
|
var err error
|
||||||
for _, index := range order {
|
for _, index := range order {
|
||||||
matrix := matrixes[index]
|
matrix := matrixes[index].Values
|
||||||
combo := src.Clone()
|
combo := src.Clone()
|
||||||
if combo.Name == "" {
|
|
||||||
combo.Name = jobID
|
|
||||||
}
|
|
||||||
combo.Strategy.RawMatrix = encodeMatrix(matrix)
|
combo.Strategy.RawMatrix = encodeMatrix(matrix)
|
||||||
replaceScalars(&combo.Strategy.RawMatrix, escapeExpressions)
|
replaceScalars(&combo.Strategy.RawMatrix, escapeExpressions)
|
||||||
if src.Strategy.RawMatrix.Kind != 0 {
|
if src.Strategy.RawMatrix.Kind != 0 {
|
||||||
combo.Strategy.JobIndex, combo.Strategy.JobTotal = index, len(matrixes)
|
combo.Strategy.JobIndex, combo.Strategy.JobTotal = index, len(matrixes)
|
||||||
}
|
}
|
||||||
evaluator := expreval.New(NewInterpeter(jobID, &combo.Strategy, matrix, gitCtx, results, vars, inputs).Evaluate)
|
evaluator := expreval.New(NewInterpeter(jobID, &combo.Strategy, matrix, gitCtx, results, vars, inputs).Evaluate)
|
||||||
if len(matrix) == 0 && gitCtx != nil {
|
if combo.Name, err = jobName(combo.Name, jobID, names[index], evaluator, len(matrix) > 0 || gitCtx != nil); err != nil {
|
||||||
combo.Name, err = evaluator.Interpolate(combo.Name)
|
|
||||||
combo.Name = escapeExpressions(combo.Name)
|
|
||||||
} else {
|
|
||||||
combo.Name, err = nameWithMatrix(combo.Name, matrix, evaluator)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("interpolate name for job %q: %w", jobID, err)
|
return nil, fmt.Errorf("interpolate name for job %q: %w", jobID, err)
|
||||||
}
|
}
|
||||||
if gitCtx != nil { // callers without one don't read runs-on
|
if gitCtx != nil { // callers without one don't read runs-on
|
||||||
@@ -259,14 +247,15 @@ func buildMatrixCombos(jobID string, src *Job, matrixes []map[string]any, gitCtx
|
|||||||
if err := evaluator.EvaluateYamlNode(&rawRunsOn); err != nil {
|
if err := evaluator.EvaluateYamlNode(&rawRunsOn); err != nil {
|
||||||
return nil, fmt.Errorf("interpolate runs-on for job %q: %w", jobID, err)
|
return nil, fmt.Errorf("interpolate runs-on for job %q: %w", jobID, err)
|
||||||
}
|
}
|
||||||
runsOn := model.RunsOnFromNode(rawRunsOn)
|
if rawRunsOn.Kind != 0 && runsOnProblem(&rawRunsOn) != "" {
|
||||||
if len(runsOn) == 0 && len(srcRunsOn) > 0 { // match no runner rather than every runner
|
combo.RawRunsOn = rawRunsOn
|
||||||
runsOn = []string{""}
|
} else {
|
||||||
|
runsOn := model.RunsOnFromNode(rawRunsOn)
|
||||||
|
for i := range runsOn {
|
||||||
|
runsOn[i] = escapeExpressions(runsOn[i])
|
||||||
|
}
|
||||||
|
combo.RawRunsOn = model.RunsOnNode(runsOn, "")
|
||||||
}
|
}
|
||||||
for i := range runsOn {
|
|
||||||
runsOn[i] = escapeExpressions(runsOn[i])
|
|
||||||
}
|
|
||||||
combo.RawRunsOn = model.RunsOnNode(runsOn, "")
|
|
||||||
}
|
}
|
||||||
if err := evaluator.EvaluateYamlNode(&combo.RawContinueOnError); err != nil {
|
if err := evaluator.EvaluateYamlNode(&combo.RawContinueOnError); err != nil {
|
||||||
return nil, fmt.Errorf("evaluate continue-on-error for job %q: %w", jobID, err)
|
return nil, fmt.Errorf("evaluate continue-on-error for job %q: %w", jobID, err)
|
||||||
@@ -303,7 +292,6 @@ func WithInputs(inputs map[string]any) ParseOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type parseContext struct {
|
type parseContext struct {
|
||||||
jobResults map[string]string
|
|
||||||
gitContext *model.GithubContext
|
gitContext *model.GithubContext
|
||||||
vars map[string]string
|
vars map[string]string
|
||||||
inputs map[string]any
|
inputs map[string]any
|
||||||
@@ -324,29 +312,36 @@ func encodeMatrix(matrix map[string]any) yaml.Node {
|
|||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func nameWithMatrix(name string, m map[string]any, evaluator expreval.Evaluator) (string, error) {
|
// jobName trims names, gives plain text and lone string literals the suffix, and blank names the job ID
|
||||||
if len(m) == 0 {
|
func jobName(name, jobID, suffix string, evaluator expreval.Evaluator, evaluate bool) (string, error) {
|
||||||
|
name = strings.TrimSpace(name)
|
||||||
|
if literal, ok := expreval.Literal(name); ok {
|
||||||
|
if literal == "" {
|
||||||
|
literal = jobID
|
||||||
|
}
|
||||||
|
return escapeExpressions(literal + suffix), nil
|
||||||
|
}
|
||||||
|
if !evaluate {
|
||||||
return name, nil
|
return name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(name, "${{") || !strings.Contains(name, "}}") {
|
|
||||||
return escapeExpressions(name + " " + matrixName(m)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
name, err := evaluator.Interpolate(name)
|
name, err := evaluator.Interpolate(name)
|
||||||
|
if name = strings.TrimSpace(name); name == "" {
|
||||||
|
name = jobID
|
||||||
|
}
|
||||||
return escapeExpressions(name), err
|
return escapeExpressions(name), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func matrixName(m map[string]any) string {
|
// matrixName formats the name suffix, skipping null and empty values
|
||||||
ks := make([]string, 0, len(m))
|
func matrixName(values []any) string {
|
||||||
for k := range m {
|
var names []string
|
||||||
ks = append(ks, k)
|
for _, value := range values {
|
||||||
|
if name := exprparser.CoerceToString(value); name != "" {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sort.Strings(ks)
|
if len(names) == 0 {
|
||||||
vs := make([]string, 0, len(m))
|
return ""
|
||||||
for _, v := range ks {
|
|
||||||
vs = append(vs, fmt.Sprint(m[v]))
|
|
||||||
}
|
}
|
||||||
|
return " (" + strings.Join(names, ", ") + ")"
|
||||||
return fmt.Sprintf("(%s)", strings.Join(vs, ", "))
|
|
||||||
}
|
}
|
||||||
@@ -290,17 +290,68 @@ func TestParseInterpolatesRunName(t *testing.T) {
|
|||||||
assert.Empty(t, result[0].RunName)
|
assert.Empty(t, result[0].RunName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseRunsOnFromJSONArray(t *testing.T) {
|
func TestParseRunsOnFromJSONKeepsWhatGitHubRejectsForTheJobToFail(t *testing.T) {
|
||||||
content := []byte("on: push\njobs:\n build:\n runs-on: ${{ fromJSON(vars.RUNNER) }}\n steps: [{run: echo}]\n")
|
content := []byte("on: push\njobs:\n build:\n runs-on: ${{ fromJSON(vars.RUNNER) }}\n steps: [{run: echo}]\n")
|
||||||
_, err := Parse(content)
|
_, err := Parse(content)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
for runner, want := range map[string][]string{`["self-hosted", "linux"]`: {"self-hosted", "linux"}, "[]": {""}} {
|
for runner, want := range map[string][]string{`["self-hosted", "linux"]`: {"self-hosted", "linux"}, "[]": {}, "{}": {}} {
|
||||||
result, err := Parse(content, WithGitContext(&model.GithubContext{}), WithVars(map[string]string{"RUNNER": runner}))
|
result, err := Parse(content, WithGitContext(&model.GithubContext{}), WithVars(map[string]string{"RUNNER": runner}))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, result, 1)
|
require.Len(t, result, 1)
|
||||||
_, job := result[0].Job()
|
_, job := result[0].Job()
|
||||||
assert.Equal(t, want, job.RunsOn(), runner)
|
assert.Equal(t, want, job.RunsOn(), runner)
|
||||||
}
|
}
|
||||||
|
for runner, problem := range map[string]string{`["a"]`: "", `""`: "Unexpected value ''", `[["a"]]`: "A sequence was not expected"} {
|
||||||
|
result, err := Parse(content, WithGitContext(&model.GithubContext{}), WithVars(map[string]string{"RUNNER": runner}))
|
||||||
|
require.NoError(t, err)
|
||||||
|
payload, err := result[0].Marshal()
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, job, err := ParseRawSingleWorkflow(payload)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, problem, job.RunsOnProblem(), runner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseJobNames(t *testing.T) {
|
||||||
|
result, err := Parse([]byte(`on: push
|
||||||
|
jobs:
|
||||||
|
scalars:
|
||||||
|
strategy: {matrix: {value: [[1.0, true, false, 0, 1000000000000000, '', null, {os: linux}], ['', null]]}}
|
||||||
|
steps: [{run: echo}]
|
||||||
|
trimmed:
|
||||||
|
name: ' Trimmed '
|
||||||
|
strategy: {matrix: {v: [a]}}
|
||||||
|
steps: [{run: echo}]
|
||||||
|
blank:
|
||||||
|
name: ' '
|
||||||
|
steps: [{run: echo}]
|
||||||
|
folded:
|
||||||
|
name: "${{ ' Folded' }}"
|
||||||
|
strategy: {matrix: {v: [a]}}
|
||||||
|
steps: [{run: echo}]
|
||||||
|
computed:
|
||||||
|
name: ${{ format(' {0} ', matrix.missing) }}
|
||||||
|
strategy: {matrix: {v: [a]}}
|
||||||
|
steps: [{run: echo}]
|
||||||
|
padded:
|
||||||
|
name: ${{ format(' {0} ', matrix.v) }}
|
||||||
|
strategy: {matrix: {v: [a]}}
|
||||||
|
steps: [{run: echo}]
|
||||||
|
`), WithGitContext(&model.GithubContext{}))
|
||||||
|
require.NoError(t, err)
|
||||||
|
names := map[string][]string{}
|
||||||
|
for _, parsed := range result {
|
||||||
|
id, job := parsed.Job()
|
||||||
|
names[id] = append(names[id], job.DisplayName())
|
||||||
|
}
|
||||||
|
assert.Equal(t, map[string][]string{
|
||||||
|
"scalars": {"scalars", "scalars (1, true, false, 0, 1E+15, linux)"},
|
||||||
|
"trimmed": {"Trimmed (a)"},
|
||||||
|
"blank": {"blank"},
|
||||||
|
"folded": {" Folded (a)"},
|
||||||
|
"computed": {"computed"},
|
||||||
|
"padded": {"a"},
|
||||||
|
}, names)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJobFieldsWithoutMatrix(t *testing.T) {
|
func TestJobFieldsWithoutMatrix(t *testing.T) {
|
||||||
@@ -458,6 +509,37 @@ func TestReadWorkflowJobConditionContexts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateWorkflowStaticJobKindAndRunsOnLikeGitHub(t *testing.T) {
|
||||||
|
for job, want := range map[string]string{
|
||||||
|
"{runs-on: x, steps: [{run: echo}]}": "",
|
||||||
|
"{uses: o/r/.gitea/workflows/c.yml@main}": "",
|
||||||
|
"{runs-on: []}": "",
|
||||||
|
"{runs-on: {}}": "",
|
||||||
|
"{runs-on: {group: org/g, labels: [a, 1]}}": "",
|
||||||
|
"{runs-on: {group: '${{ vars.G }}'}}": "",
|
||||||
|
"{steps: [{run: echo}]}": "Required property is missing: runs-on",
|
||||||
|
"{with: {}}": "Required property is missing: uses",
|
||||||
|
"{runs-on: x, uses: o/r/.gitea/workflows/c.yml@main}": "Unexpected value 'uses'",
|
||||||
|
"{Runs-On: x}": "Unexpected value 'Runs-On'",
|
||||||
|
"{runs-on: ~}": "runs-on: Unexpected value ''",
|
||||||
|
"{runs-on: ['']}": "runs-on: Unexpected value ''",
|
||||||
|
"{runs-on: [[a]]}": "runs-on: A sequence was not expected",
|
||||||
|
"{runs-on: {labels: {a: b}}}": "runs-on: A mapping was not expected",
|
||||||
|
"{runs-on: {foo: x}}": "runs-on: Unexpected value 'foo'",
|
||||||
|
"{runs-on: {group: org/}}": "runs-on: Invalid runs-on group name 'org/'.",
|
||||||
|
"{runs-on: {group: a/b/c}}": "runs-on: Invalid runs-on group name 'a/b/c'. Please use 'organization/' or 'enterprise/' prefix to target a single runner group.",
|
||||||
|
"{if: true}": "There's not enough info to determine what you meant. Add one of these properties: " +
|
||||||
|
"cancel-timeout-minutes, container, continue-on-error, defaults, env, environment, outputs, runs-on, secrets, services, snapshot, steps, timeout-minutes, uses, with",
|
||||||
|
} {
|
||||||
|
_, err := ValidateWorkflowStatic([]byte("on: push\njobs:\n build: " + job + "\n"))
|
||||||
|
if want == "" {
|
||||||
|
assert.NoError(t, err, job)
|
||||||
|
} else {
|
||||||
|
assert.EqualError(t, err, "job build: "+want, job)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRejectsUnevaluatedMatrixFilters(t *testing.T) {
|
func TestRejectsUnevaluatedMatrixFilters(t *testing.T) {
|
||||||
for _, filter := range []string{"include", "exclude"} {
|
for _, filter := range []string{"include", "exclude"} {
|
||||||
t.Run(filter, func(t *testing.T) {
|
t.Run(filter, func(t *testing.T) {
|
||||||
|
|||||||
@@ -174,6 +174,14 @@ func (j *Job) EraseNeeds() *Job {
|
|||||||
return j
|
return j
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunsOnProblem returns github.com's error for the job's runs-on, "" if valid.
|
||||||
|
func (j *Job) RunsOnProblem() string {
|
||||||
|
if j.RawRunsOn.Kind == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return runsOnProblem(&j.RawRunsOn)
|
||||||
|
}
|
||||||
|
|
||||||
// RunsOn returns the labels Gitea matches runners against, unescaped like DisplayName.
|
// RunsOn returns the labels Gitea matches runners against, unescaped like DisplayName.
|
||||||
func (j *Job) RunsOn() []string {
|
func (j *Job) RunsOn() []string {
|
||||||
runsOn := model.RunsOnFromNode(j.RawRunsOn)
|
runsOn := model.RunsOnFromNode(j.RawRunsOn)
|
||||||
@@ -302,10 +310,6 @@ func (evt *Event) Acts() map[string][]string {
|
|||||||
return evt.acts
|
return evt.acts
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evt *Event) Schedules() []map[string]string {
|
|
||||||
return evt.schedules
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadWorkflowRawConcurrency(content []byte) (*model.RawConcurrency, error) {
|
func ReadWorkflowRawConcurrency(content []byte) (*model.RawConcurrency, error) {
|
||||||
w, err := ReadWorkflow(content)
|
w, err := ReadWorkflow(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -7,10 +7,13 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gitea.dev/actionslib/pkg/expreval"
|
"gitea.dev/actionslib/pkg/expreval"
|
||||||
"gitea.dev/actionslib/pkg/exprparser"
|
"gitea.dev/actionslib/pkg/exprparser"
|
||||||
"gitea.dev/actionslib/pkg/model"
|
"gitea.dev/actionslib/pkg/model"
|
||||||
|
|
||||||
|
"go.yaml.in/yaml/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
// jobConditionContexts are what github.com gives `jobs.<job_id>.if`, which it decides before the matrix, plus the `gitea` alias.
|
// jobConditionContexts are what github.com gives `jobs.<job_id>.if`, which it decides before the matrix, plus the `gitea` alias.
|
||||||
@@ -21,7 +24,7 @@ func ValidateWorkflowStatic(content []byte) ([]*Event, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Keep unknown and case-distinct keys accepted for existing Gitea workflows.
|
// Keep unknown and case-distinct keys outside of jobs accepted for existing Gitea workflows.
|
||||||
workflow, err := readWorkflowDoc(doc)
|
workflow, err := readWorkflowDoc(doc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -33,6 +36,9 @@ func ValidateWorkflowStatic(content []byte) ([]*Event, error) {
|
|||||||
if err := validateWorkflowStructure(workflow); err != nil {
|
if err := validateWorkflowStructure(workflow); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := validateJobKinds(doc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
var header struct {
|
var header struct {
|
||||||
RunName string `yaml:"run-name"`
|
RunName string `yaml:"run-name"`
|
||||||
}
|
}
|
||||||
@@ -76,7 +82,6 @@ func validateWorkflowStructure(workflow *model.Workflow) error {
|
|||||||
if job == nil {
|
if job == nil {
|
||||||
return fmt.Errorf("job %q has no configuration", id)
|
return fmt.Errorf("job %q has no configuration", id)
|
||||||
}
|
}
|
||||||
// a job without runs-on is accepted and runs on any runner, github.com rejects it
|
|
||||||
for _, dependency := range job.Needs() {
|
for _, dependency := range job.Needs() {
|
||||||
if _, ok := workflow.Jobs[dependency]; !ok {
|
if _, ok := workflow.Jobs[dependency]; !ok {
|
||||||
return fmt.Errorf("job %q needs unknown job %q", id, dependency)
|
return fmt.Errorf("job %q needs unknown job %q", id, dependency)
|
||||||
@@ -110,3 +115,116 @@ func validateWorkflowStructure(workflow *model.Workflow) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// job keys of github.com's workflow schema, by the kind of job allowing them
|
||||||
|
var (
|
||||||
|
stepsJobKeys = []string{"cancel-timeout-minutes", "container", "continue-on-error", "defaults", "env", "environment", "outputs", "runs-on", "services", "snapshot", "steps", "timeout-minutes"}
|
||||||
|
callerJobKeys = []string{"secrets", "uses", "with"}
|
||||||
|
sharedJobKeys = []string{"concurrency", "if", "name", "needs", "permissions", "strategy"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// validateJobKinds applies github.com's job kinds, decided by the first kind-specific key.
|
||||||
|
func validateJobKinds(doc *yaml.Node) error {
|
||||||
|
jobs := mappingValue(doc.Content[0], "jobs")
|
||||||
|
for i := 0; i+1 < len(jobs.Content); i += 2 {
|
||||||
|
id, job := jobs.Content[i].Value, jobs.Content[i+1]
|
||||||
|
var required string
|
||||||
|
for j := 0; j+1 < len(job.Content); j += 2 {
|
||||||
|
key := job.Content[j].Value
|
||||||
|
isStepsKey, isCallerKey := slices.Contains(stepsJobKeys, key), slices.Contains(callerJobKeys, key)
|
||||||
|
switch {
|
||||||
|
case required == "runs-on" && isCallerKey, required == "uses" && isStepsKey, !isStepsKey && !isCallerKey && !slices.Contains(sharedJobKeys, key):
|
||||||
|
return fmt.Errorf("job %s: Unexpected value '%s'", id, key)
|
||||||
|
case required == "" && isStepsKey:
|
||||||
|
required = "runs-on"
|
||||||
|
case required == "" && isCallerKey:
|
||||||
|
required = "uses"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if required == "" {
|
||||||
|
keys := slices.Concat(stepsJobKeys, callerJobKeys)
|
||||||
|
slices.Sort(keys)
|
||||||
|
return fmt.Errorf("job %s: There's not enough info to determine what you meant. Add one of these properties: %s", id, strings.Join(keys, ", "))
|
||||||
|
}
|
||||||
|
value := mappingValue(job, required)
|
||||||
|
if value == nil {
|
||||||
|
return fmt.Errorf("job %s: Required property is missing: %s", id, required)
|
||||||
|
}
|
||||||
|
if required != "runs-on" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if problem := runsOnProblem(value); problem != "" {
|
||||||
|
return fmt.Errorf("job %s: runs-on: %s", id, problem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runsOnProblem returns github.com's schema error for a runs-on, "" if valid.
|
||||||
|
func runsOnProblem(node *yaml.Node) string {
|
||||||
|
if node.Kind != yaml.MappingNode {
|
||||||
|
return runsOnLabelsProblem(node)
|
||||||
|
}
|
||||||
|
for i := 0; i+1 < len(node.Content); i += 2 {
|
||||||
|
var problem string
|
||||||
|
switch key := node.Content[i].Value; key {
|
||||||
|
case "labels":
|
||||||
|
problem = runsOnLabelsProblem(node.Content[i+1])
|
||||||
|
case "group":
|
||||||
|
problem = runsOnGroupProblem(node.Content[i+1])
|
||||||
|
default:
|
||||||
|
problem = fmt.Sprintf("Unexpected value '%s'", key)
|
||||||
|
}
|
||||||
|
if problem != "" {
|
||||||
|
return problem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func runsOnLabelsProblem(node *yaml.Node) string {
|
||||||
|
if node.Kind != yaml.SequenceNode {
|
||||||
|
return nonEmptyStringProblem(node)
|
||||||
|
}
|
||||||
|
for _, label := range node.Content {
|
||||||
|
if problem := nonEmptyStringProblem(label); problem != "" {
|
||||||
|
return problem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func runsOnGroupProblem(node *yaml.Node) string {
|
||||||
|
if problem := nonEmptyStringProblem(node); problem != "" || hasExpression(node.Value) {
|
||||||
|
return problem
|
||||||
|
}
|
||||||
|
switch prefix, name, found := strings.Cut(node.Value, "/"); {
|
||||||
|
case found && name == "":
|
||||||
|
return fmt.Sprintf("Invalid runs-on group name '%s'.", node.Value)
|
||||||
|
case found && (strings.Contains(name, "/") || !slices.Contains([]string{"org", "organization", "ent", "enterprise"}, prefix)):
|
||||||
|
return fmt.Sprintf("Invalid runs-on group name '%s'. Please use 'organization/' or 'enterprise/' prefix to target a single runner group.", node.Value)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// nonEmptyStringProblem mirrors github.com's non-empty-string, which also accepts non-string scalars.
|
||||||
|
func nonEmptyStringProblem(node *yaml.Node) string {
|
||||||
|
switch {
|
||||||
|
case node.Kind == yaml.SequenceNode:
|
||||||
|
return "A sequence was not expected"
|
||||||
|
case node.Kind == yaml.MappingNode:
|
||||||
|
return "A mapping was not expected"
|
||||||
|
case node.Value == "" || node.ShortTag() == "!!null":
|
||||||
|
return "Unexpected value ''"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func mappingValue(node *yaml.Node, key string) *yaml.Node {
|
||||||
|
for i := 0; i+1 < len(node.Content); i += 2 {
|
||||||
|
if node.Content[i].Value == key {
|
||||||
|
return node.Content[i+1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -532,7 +532,7 @@ func matchIssuesEvent(issuePayload *api.IssuePayload, evt *jobparser.Event) bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, val := range vals {
|
for _, val := range vals {
|
||||||
if slices.ContainsFunc(actions, glob.MustCompile(val, '/').Match) {
|
if matchActivityType(val, actions...) {
|
||||||
matchTimes++
|
matchTimes++
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -575,7 +575,7 @@ func matchPullRequestEvent(ctx context.Context, gitRepo *git.Repository, commit
|
|||||||
}
|
}
|
||||||
log.Trace("matching pull_request %s with %v", action, vals)
|
log.Trace("matching pull_request %s with %v", action, vals)
|
||||||
for _, val := range vals {
|
for _, val := range vals {
|
||||||
if glob.MustCompile(val, '/').Match(string(action)) {
|
if matchActivityType(val, string(action)) {
|
||||||
activityTypeMatched = true
|
activityTypeMatched = true
|
||||||
matchTimes++
|
matchTimes++
|
||||||
break
|
break
|
||||||
@@ -678,7 +678,7 @@ func matchIssueCommentEvent(issueCommentPayload *api.IssueCommentPayload, evt *j
|
|||||||
// NONE
|
// NONE
|
||||||
|
|
||||||
for _, val := range vals {
|
for _, val := range vals {
|
||||||
if glob.MustCompile(val, '/').Match(string(issueCommentPayload.Action)) {
|
if matchActivityType(val, string(issueCommentPayload.Action)) {
|
||||||
matchTimes++
|
matchTimes++
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -718,7 +718,7 @@ func matchPullRequestReviewEvent(prPayload *api.PullRequestPayload, evt *jobpars
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, val := range vals {
|
for _, val := range vals {
|
||||||
if slices.ContainsFunc(actions, glob.MustCompile(val, '/').Match) {
|
if matchActivityType(val, actions...) {
|
||||||
matchTimes++
|
matchTimes++
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -758,7 +758,7 @@ func matchPullRequestReviewCommentEvent(prPayload *api.PullRequestPayload, evt *
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, val := range vals {
|
for _, val := range vals {
|
||||||
if slices.ContainsFunc(actions, glob.MustCompile(val, '/').Match) {
|
if matchActivityType(val, actions...) {
|
||||||
matchTimes++
|
matchTimes++
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -795,7 +795,7 @@ func matchReleaseEvent(payload *api.ReleasePayload, evt *jobparser.Event) bool {
|
|||||||
action = "edited"
|
action = "edited"
|
||||||
}
|
}
|
||||||
for _, val := range vals {
|
for _, val := range vals {
|
||||||
if glob.MustCompile(val, '/').Match(string(action)) {
|
if matchActivityType(val, string(action)) {
|
||||||
matchTimes++
|
matchTimes++
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -832,7 +832,7 @@ func matchPackageEvent(payload *api.PackagePayload, evt *jobparser.Event) bool {
|
|||||||
action = "published"
|
action = "published"
|
||||||
}
|
}
|
||||||
for _, val := range vals {
|
for _, val := range vals {
|
||||||
if glob.MustCompile(val, '/').Match(string(action)) {
|
if matchActivityType(val, string(action)) {
|
||||||
matchTimes++
|
matchTimes++
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -857,7 +857,7 @@ func matchWorkflowRunEvent(payload *api.WorkflowRunPayload, evt *jobparser.Event
|
|||||||
case "types":
|
case "types":
|
||||||
action := payload.Action
|
action := payload.Action
|
||||||
for _, val := range vals {
|
for _, val := range vals {
|
||||||
if glob.MustCompile(val, '/').Match(action) {
|
if matchActivityType(val, action) {
|
||||||
matchTimes++
|
matchTimes++
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -893,3 +893,13 @@ func matchWorkflowRunEvent(payload *api.WorkflowRunPayload, evt *jobparser.Event
|
|||||||
}
|
}
|
||||||
return matchTimes == len(evt.Acts())
|
return matchTimes == len(evt.Acts())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// matchActivityType treats an invalid user-provided pattern as no match instead of panicking
|
||||||
|
func matchActivityType(pattern string, actions ...string) bool {
|
||||||
|
g, err := glob.Compile(pattern, '/')
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("invalid activity type pattern %q: %v", pattern, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return slices.ContainsFunc(actions, g.Match)
|
||||||
|
}
|
||||||
@@ -30,18 +30,23 @@ jobs:
|
|||||||
|
|
||||||
func TestReadWorkflowEventsStaticErrors(t *testing.T) {
|
func TestReadWorkflowEventsStaticErrors(t *testing.T) {
|
||||||
for content, static := range map[string]bool{
|
for content, static := range map[string]bool{
|
||||||
"on: push\njobs: {}": true,
|
"on: push\njobs: {}": true,
|
||||||
"on: push\njobs: {test: {needs: absent}}": true,
|
"on: push\njobs: {test: {runs-on: x, needs: absent}}": true,
|
||||||
"on: push\njobs: {one: {needs: two}, two: {needs: one}}": true,
|
"on: push\njobs: {one: {runs-on: x, needs: two}, two: {runs-on: x, needs: one}}": true,
|
||||||
"on: push\njobs: {test: {strategy: {matrix: {os: []}}}}": true,
|
"on: push\njobs: {test: {runs-on: x, strategy: {matrix: {os: []}}}}": true,
|
||||||
"on: push\nrun-name: ${{ secrets.TOKEN }}\njobs: {test: {}}": true,
|
"on: push\nrun-name: ${{ secrets.TOKEN }}\njobs: {test: {runs-on: x}}": true,
|
||||||
"on: push\nrun-name: ${{ fromJSON(inputs.x) }}\njobs: {test: {steps: [{run: echo}]}}": false,
|
"on: push\njobs: {test: {steps: [{run: echo}]}}": true,
|
||||||
|
"on: push\nrun-name: ${{ fromJSON(inputs.x) }}\njobs: {test: {runs-on: x, steps: [{run: echo}]}}": false,
|
||||||
} {
|
} {
|
||||||
_, gotStatic, err := readWorkflowEvents([]byte(content))
|
_, gotStatic, err := readWorkflowEvents([]byte(content))
|
||||||
require.Error(t, err, content)
|
require.Error(t, err, content)
|
||||||
assert.Equal(t, static, gotStatic, content)
|
assert.Equal(t, static, gotStatic, content)
|
||||||
}
|
}
|
||||||
for _, content := range []string{"on: push\njobs: {test: {steps: [{run: echo}]}}", "on: push\nrun-name: ${{ github.ref }}\njobs: {test: {}}"} {
|
for _, content := range []string{
|
||||||
|
"on: push\njobs: {test: {runs-on: x, steps: [{run: echo}]}}",
|
||||||
|
"on: push\nrun-name: ${{ github.ref }}\njobs: {test: {runs-on: x}}",
|
||||||
|
"on: push\njobs: {call: {uses: ./.gitea/workflows/called.yml}}",
|
||||||
|
} {
|
||||||
_, _, err := readWorkflowEvents([]byte(content))
|
_, _, err := readWorkflowEvents([]byte(content))
|
||||||
assert.NoError(t, err, content)
|
assert.NoError(t, err, content)
|
||||||
}
|
}
|
||||||
@@ -205,6 +210,13 @@ func TestDetectMatched(t *testing.T) {
|
|||||||
yamlOn: "on:\n release:\n types: [published]",
|
yamlOn: "on:\n release:\n types: [published]",
|
||||||
expected: detectMatched,
|
expected: detectMatched,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "HookEventRelease(release) doesn't match an invalid activity type pattern",
|
||||||
|
triggedEvent: webhook_module.HookEventRelease,
|
||||||
|
payload: &api.ReleasePayload{Action: api.HookReleasePublished},
|
||||||
|
yamlOn: "on:\n release:\n types: [\"[\"]",
|
||||||
|
expected: detectNotApplicable,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "HookEventPackage(package) `created` action doesn't match GithubEventRegistryPackage(registry_package) with `updated` activity type",
|
desc: "HookEventPackage(package) `created` action doesn't match GithubEventRegistryPackage(registry_package) with `updated` activity type",
|
||||||
triggedEvent: webhook_module.HookEventPackage,
|
triggedEvent: webhook_module.HookEventPackage,
|
||||||
|
|||||||
@@ -102,11 +102,11 @@ func NewEmbeddedFS(data []byte) fs.ReadDirFS {
|
|||||||
efs := &embeddedFS{data: data, files: make(map[string]*embeddedFileInfo)}
|
efs := &embeddedFS{data: data, files: make(map[string]*embeddedFileInfo)}
|
||||||
efs.meta = sync.OnceValue(func() *EmbeddedMeta {
|
efs.meta = sync.OnceValue(func() *EmbeddedMeta {
|
||||||
var meta EmbeddedMeta
|
var meta EmbeddedMeta
|
||||||
p := bytes.LastIndexByte(data, '\n')
|
_, metaJSON, ok := bytes.CutLast(data, []byte{'\n'})
|
||||||
if p < 0 {
|
if !ok {
|
||||||
return &meta
|
return &meta
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(data[p+1:], &meta); err != nil {
|
if err := json.Unmarshal(metaJSON, &meta); err != nil {
|
||||||
panic("embedded file is not valid")
|
panic("embedded file is not valid")
|
||||||
}
|
}
|
||||||
return &meta
|
return &meta
|
||||||
|
|||||||
Vendored
+1
-3
@@ -13,8 +13,6 @@ import (
|
|||||||
|
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
|
|
||||||
_ "gitea.com/go-chi/cache/memcache" //nolint:depguard // memcache plugin for cache, it is required for config "ADAPTER=memcache"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultCache StringCache
|
var defaultCache StringCache
|
||||||
@@ -83,7 +81,7 @@ func GetCache() StringCache {
|
|||||||
|
|
||||||
// GetString returns the key value from cache with callback when no key exists in cache
|
// GetString returns the key value from cache with callback when no key exists in cache
|
||||||
func GetString(key string, getFunc func() (string, error)) (string, error) {
|
func GetString(key string, getFunc func() (string, error)) (string, error) {
|
||||||
if defaultCache == nil || setting.CacheService.TTL == 0 {
|
if defaultCache == nil || setting.CacheService.TTL <= 0 {
|
||||||
return getFunc()
|
return getFunc()
|
||||||
}
|
}
|
||||||
cached, exist := defaultCache.Get(key)
|
cached, exist := defaultCache.Get(key)
|
||||||
|
|||||||
Vendored
+70
@@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bradfitz/gomemcache/memcache"
|
||||||
|
)
|
||||||
|
|
||||||
|
const memcacheMaxRelativeTTL = 30 * 24 * 60 * 60
|
||||||
|
|
||||||
|
type memcacheCache struct {
|
||||||
|
client *memcache.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMemcacheCache(conn string) (backend, error) {
|
||||||
|
if conn == "" {
|
||||||
|
return nil, errors.New("cache adapter memcache requires [cache] HOST, e.g. 127.0.0.1:11211")
|
||||||
|
}
|
||||||
|
servers := &memcache.ServerList{}
|
||||||
|
if err := servers.SetServers(strings.Split(conn, ";")...); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid memcache HOST %q: %w", conn, err)
|
||||||
|
}
|
||||||
|
return &memcacheCache{client: memcache.NewFromSelector(servers)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// memcacheExpiration converts a TTL beyond memcached's 30-day relative limit into an absolute unix time
|
||||||
|
func memcacheExpiration(ttl int64) int32 {
|
||||||
|
if ttl > memcacheMaxRelativeTTL {
|
||||||
|
ttl += timeNow().Unix()
|
||||||
|
}
|
||||||
|
return int32(min(ttl, math.MaxInt32))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *memcacheCache) Get(key string) (string, bool) {
|
||||||
|
item, err := c.client.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return string(item.Value), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *memcacheCache) Put(key, value string, ttl int64) error {
|
||||||
|
return c.client.Set(&memcache.Item{Key: key, Value: []byte(value), Expiration: memcacheExpiration(ttl)})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *memcacheCache) Delete(key string) error {
|
||||||
|
if err := c.client.Delete(key); err != nil && !errors.Is(err, memcache.ErrCacheMiss) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *memcacheCache) IsExist(key string) bool {
|
||||||
|
_, err := c.client.Get(key)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *memcacheCache) Ping() error {
|
||||||
|
const key = "__gitea_cache_ping"
|
||||||
|
if err := c.Put(key, "ping", 10); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.Delete(key)
|
||||||
|
}
|
||||||
Vendored
+19
-134
@@ -4,159 +4,44 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.dev/modules/graceful"
|
"gitea.dev/modules/graceful"
|
||||||
"gitea.dev/modules/nosql"
|
"gitea.dev/modules/nosql"
|
||||||
|
|
||||||
"gitea.com/go-chi/cache" //nolint:depguard // we wrap this package here
|
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RedisCacher represents a redis cache adapter implementation.
|
type redisCache struct {
|
||||||
type RedisCacher struct {
|
client redis.UniversalClient
|
||||||
c redis.UniversalClient
|
prefix string
|
||||||
prefix string
|
|
||||||
hsetName string
|
|
||||||
occupyMode bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// toStr convert string/int/int64 interface to string. it's only used by the RedisCacher.Put internally
|
func newRedisCache(conn string) backend {
|
||||||
func toStr(v any) string {
|
uri := nosql.ToRedisURI(conn)
|
||||||
if v == nil {
|
return &redisCache{
|
||||||
return ""
|
client: nosql.GetManager().GetRedisClient(uri.String()),
|
||||||
}
|
prefix: uri.Query().Get("prefix"),
|
||||||
switch v := v.(type) {
|
|
||||||
case string:
|
|
||||||
return v
|
|
||||||
case []byte:
|
|
||||||
return string(v)
|
|
||||||
case int:
|
|
||||||
return strconv.FormatInt(int64(v), 10)
|
|
||||||
case int64:
|
|
||||||
return strconv.FormatInt(v, 10)
|
|
||||||
default:
|
|
||||||
return fmt.Sprint(v) // as what the old com.ToStr does in most cases
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put puts value (string type) into cache with key and expire time.
|
func (c *redisCache) Get(key string) (string, bool) {
|
||||||
// If expired is 0, it lives forever.
|
value, err := c.client.Get(graceful.GetManager().HammerContext(), c.prefix+key).Result()
|
||||||
func (c *RedisCacher) Put(key string, val any, expire int64) error {
|
return value, err == nil
|
||||||
// this function is not well-designed, it only puts string values into cache
|
|
||||||
key = c.prefix + key
|
|
||||||
if expire == 0 {
|
|
||||||
if err := c.c.Set(graceful.GetManager().HammerContext(), key, toStr(val), 0).Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dur := time.Duration(expire) * time.Second
|
|
||||||
if err := c.c.Set(graceful.GetManager().HammerContext(), key, toStr(val), dur).Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.occupyMode {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return c.c.HSet(graceful.GetManager().HammerContext(), c.hsetName, key, "0").Err()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets cached value by given key.
|
func (c *redisCache) Put(key, value string, ttl int64) error {
|
||||||
func (c *RedisCacher) Get(key string) any {
|
return c.client.Set(graceful.GetManager().HammerContext(), c.prefix+key, value, time.Duration(ttl)*time.Second).Err()
|
||||||
val, err := c.c.Get(graceful.GetManager().HammerContext(), c.prefix+key).Result()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes cached value by given key.
|
func (c *redisCache) Delete(key string) error {
|
||||||
func (c *RedisCacher) Delete(key string) error {
|
return c.client.Del(graceful.GetManager().HammerContext(), c.prefix+key).Err()
|
||||||
key = c.prefix + key
|
|
||||||
if err := c.c.Del(graceful.GetManager().HammerContext(), key).Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.occupyMode {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return c.c.HDel(graceful.GetManager().HammerContext(), c.hsetName, key).Err()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Incr increases cached int-type value by given key as a counter.
|
func (c *redisCache) IsExist(key string) bool {
|
||||||
func (c *RedisCacher) Incr(key string) error {
|
return c.client.Exists(graceful.GetManager().HammerContext(), c.prefix+key).Val() == 1
|
||||||
if !c.IsExist(key) {
|
|
||||||
return fmt.Errorf("key '%s' not exist", key)
|
|
||||||
}
|
|
||||||
return c.c.Incr(graceful.GetManager().HammerContext(), c.prefix+key).Err()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decr decreases cached int-type value by given key as a counter.
|
func (c *redisCache) Ping() error {
|
||||||
func (c *RedisCacher) Decr(key string) error {
|
return c.client.Ping(graceful.GetManager().HammerContext()).Err()
|
||||||
if !c.IsExist(key) {
|
|
||||||
return fmt.Errorf("key '%s' not exist", key)
|
|
||||||
}
|
|
||||||
return c.c.Decr(graceful.GetManager().HammerContext(), c.prefix+key).Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsExist returns true if cached value exists.
|
|
||||||
func (c *RedisCacher) IsExist(key string) bool {
|
|
||||||
if c.c.Exists(graceful.GetManager().HammerContext(), c.prefix+key).Val() == 1 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.occupyMode {
|
|
||||||
c.c.HDel(graceful.GetManager().HammerContext(), c.hsetName, c.prefix+key)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush deletes all cached data.
|
|
||||||
func (c *RedisCacher) Flush() error {
|
|
||||||
if c.occupyMode {
|
|
||||||
return c.c.FlushDB(graceful.GetManager().HammerContext()).Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
keys, err := c.c.HKeys(graceful.GetManager().HammerContext(), c.hsetName).Result()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = c.c.Del(graceful.GetManager().HammerContext(), keys...).Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return c.c.Del(graceful.GetManager().HammerContext(), c.hsetName).Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartAndGC starts GC routine based on config string settings.
|
|
||||||
// AdapterConfig: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180,hset_name=MacaronCache,prefix=cache:
|
|
||||||
func (c *RedisCacher) StartAndGC(opts cache.Options) error {
|
|
||||||
c.hsetName = "MacaronCache"
|
|
||||||
c.occupyMode = opts.OccupyMode
|
|
||||||
|
|
||||||
uri := nosql.ToRedisURI(opts.AdapterConfig)
|
|
||||||
|
|
||||||
c.c = nosql.GetManager().GetRedisClient(uri.String())
|
|
||||||
|
|
||||||
for k, v := range uri.Query() {
|
|
||||||
switch k {
|
|
||||||
case "hset_name":
|
|
||||||
c.hsetName = v[0]
|
|
||||||
case "prefix":
|
|
||||||
c.prefix = v[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.c.Ping(graceful.GetManager().HammerContext()).Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ping tests if the cache is alive.
|
|
||||||
func (c *RedisCacher) Ping() error {
|
|
||||||
return c.c.Ping(graceful.GetManager().HammerContext()).Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
cache.Register("redis", &RedisCacher{})
|
|
||||||
}
|
}
|
||||||
Vendored
+95
@@ -8,9 +8,12 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gitea.dev/modules/nosql"
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
|
"gitea.dev/modules/test"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func createTestCache() {
|
func createTestCache() {
|
||||||
@@ -32,6 +35,98 @@ func TestNewContext(t *testing.T) {
|
|||||||
})
|
})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Nil(t, con)
|
assert.Nil(t, con)
|
||||||
|
|
||||||
|
_, err = NewStringCache(setting.Cache{Adapter: "memcache"})
|
||||||
|
assert.ErrorContains(t, err, "requires [cache] HOST")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringCacheAdapters(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
defer test.MockVariableValue(&timeNow, func() time.Time { return now })()
|
||||||
|
cases := []struct {
|
||||||
|
adapter string
|
||||||
|
conns func(t *testing.T) (string, string)
|
||||||
|
}{
|
||||||
|
{adapter: "memory", conns: func(*testing.T) (string, string) { return "", "" }},
|
||||||
|
{adapter: "twoqueue", conns: func(*testing.T) (string, string) { return "100", `{"size":100}` }},
|
||||||
|
{adapter: "redis", conns: func(t *testing.T) (string, string) {
|
||||||
|
conn := test.PrepareTestRedis(t) + "?prefix=gitea-test-cache-"
|
||||||
|
return conn + "first:", conn + "second:"
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.adapter, func(t *testing.T) {
|
||||||
|
firstConn, secondConn := tc.conns(t)
|
||||||
|
first, err := NewStringCache(setting.Cache{Adapter: tc.adapter, Conn: firstConn, Interval: -1})
|
||||||
|
require.NoError(t, err)
|
||||||
|
second, err := NewStringCache(setting.Cache{Adapter: tc.adapter, Conn: secondConn, Interval: -1})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, first.Ping())
|
||||||
|
|
||||||
|
require.NoError(t, first.Put("key", "value", 0))
|
||||||
|
value, ok := first.Get("key")
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "value", value)
|
||||||
|
assert.True(t, first.IsExist("key"))
|
||||||
|
assert.False(t, second.IsExist("key"))
|
||||||
|
|
||||||
|
require.NoError(t, first.Delete("key"))
|
||||||
|
_, ok = first.Get("key")
|
||||||
|
assert.False(t, ok)
|
||||||
|
assert.False(t, first.IsExist("key"))
|
||||||
|
require.NoError(t, first.Delete("key"))
|
||||||
|
|
||||||
|
require.NoError(t, first.Put("expiring", "value", 10))
|
||||||
|
if tc.adapter == "redis" {
|
||||||
|
uri := nosql.ToRedisURI(firstConn)
|
||||||
|
ttl := nosql.GetManager().GetRedisClient(uri.String()).TTL(t.Context(), uri.Query().Get("prefix")+"expiring").Val()
|
||||||
|
assert.Positive(t, ttl)
|
||||||
|
assert.LessOrEqual(t, ttl, 10*time.Second)
|
||||||
|
require.NoError(t, first.Delete("expiring"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
now = now.Add(9 * time.Second)
|
||||||
|
assert.True(t, first.IsExist("expiring"))
|
||||||
|
now = now.Add(time.Second)
|
||||||
|
assert.False(t, first.IsExist("expiring"))
|
||||||
|
_, ok = first.Get("expiring")
|
||||||
|
assert.False(t, ok)
|
||||||
|
require.NoError(t, first.Put("expiring", "renewed", 0))
|
||||||
|
value, ok = first.Get("expiring")
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "renewed", value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNegativeItemTTLDisablesOnlyItemTTLCaching(t *testing.T) {
|
||||||
|
createTestCache()
|
||||||
|
defer test.MockVariableValue(&setting.CacheService.TTL, -1)()
|
||||||
|
|
||||||
|
require.NoError(t, defaultCache.Put("key", "stale", 0))
|
||||||
|
require.NoError(t, defaultCache.Put("key", "value", setting.CacheService.TTLSeconds()))
|
||||||
|
assert.False(t, defaultCache.IsExist("key"))
|
||||||
|
|
||||||
|
calls := 0
|
||||||
|
for range 2 {
|
||||||
|
data, err := GetString("key", func() (string, error) {
|
||||||
|
calls++
|
||||||
|
return "value", nil
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "value", data)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 2, calls)
|
||||||
|
assert.False(t, defaultCache.IsExist("key"))
|
||||||
|
|
||||||
|
require.NoError(t, defaultCache.Put("captcha", "value", 600))
|
||||||
|
assert.True(t, defaultCache.IsExist("captcha"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemcacheExpiration(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&timeNow, func() time.Time { return time.Unix(1000, 0) })()
|
||||||
|
assert.EqualValues(t, memcacheMaxRelativeTTL, memcacheExpiration(memcacheMaxRelativeTTL))
|
||||||
|
assert.EqualValues(t, 1000+memcacheMaxRelativeTTL+1, memcacheExpiration(memcacheMaxRelativeTTL+1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTest(t *testing.T) {
|
func TestTest(t *testing.T) {
|
||||||
|
|||||||
Vendored
+89
-171
@@ -4,205 +4,123 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.dev/modules/json"
|
"gitea.dev/modules/json"
|
||||||
|
|
||||||
mc "gitea.com/go-chi/cache" //nolint:depguard // we wrap this package here
|
|
||||||
lru "github.com/hashicorp/golang-lru/v2"
|
lru "github.com/hashicorp/golang-lru/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TwoQueueCache represents a LRU 2Q cache adapter implementation
|
const twoQueueDefaultSize = 50000
|
||||||
type TwoQueueCache struct {
|
|
||||||
lock sync.Mutex
|
type twoQueueCache struct {
|
||||||
cache *lru.TwoQueueCache[string, any]
|
cache *lru.TwoQueueCache[string, memoryItem] // wrap the existing thread-safe 2Q (two-queue) cache directly
|
||||||
interval int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TwoQueueCacheConfig describes the configuration for TwoQueueCache
|
type twoQueueCacheConfig struct {
|
||||||
type TwoQueueCacheConfig struct {
|
Size int `json:"size"`
|
||||||
Size int `ini:"SIZE" json:"size"`
|
RecentRatio float64 `json:"recent_ratio"`
|
||||||
RecentRatio float64 `ini:"RECENT_RATIO" json:"recent_ratio"`
|
GhostRatio float64 `json:"ghost_ratio"`
|
||||||
GhostRatio float64 `ini:"GHOST_RATIO" json:"ghost_ratio"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MemoryItem represents a memory cache item.
|
func newTwoQueueCache(conn string, gcInterval time.Duration) (backend, error) {
|
||||||
type MemoryItem struct {
|
lruCache, err := newTwoQueueLRU(conn)
|
||||||
Val any
|
if err != nil {
|
||||||
Created int64
|
return nil, err
|
||||||
Timeout int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (item *MemoryItem) hasExpired() bool {
|
|
||||||
return item.Timeout > 0 &&
|
|
||||||
(time.Now().Unix()-item.Created) >= item.Timeout
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ mc.Cache = &TwoQueueCache{}
|
|
||||||
|
|
||||||
// Put puts value into cache with key and expire time.
|
|
||||||
func (c *TwoQueueCache) Put(key string, val any, timeout int64) error {
|
|
||||||
item := &MemoryItem{
|
|
||||||
Val: val,
|
|
||||||
Created: time.Now().Unix(),
|
|
||||||
Timeout: timeout,
|
|
||||||
}
|
}
|
||||||
c.lock.Lock()
|
cache := &twoQueueCache{cache: lruCache}
|
||||||
defer c.lock.Unlock()
|
startGC(gcInterval, cache.deleteExpired)
|
||||||
|
return cache, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTwoQueueLRU(conn string) (*lru.TwoQueueCache[string, memoryItem], error) {
|
||||||
|
if conn == "" {
|
||||||
|
return lru.New2Q[string, memoryItem](twoQueueDefaultSize)
|
||||||
|
}
|
||||||
|
if size, err := strconv.Atoi(conn); err == nil {
|
||||||
|
return lru.New2Q[string, memoryItem](size)
|
||||||
|
}
|
||||||
|
if !json.Valid([]byte(conn)) {
|
||||||
|
return nil, fmt.Errorf("invalid two-queue cache HOST %q, expected a size or a JSON config", conn)
|
||||||
|
}
|
||||||
|
config := twoQueueCacheConfig{
|
||||||
|
Size: twoQueueDefaultSize,
|
||||||
|
RecentRatio: lru.Default2QRecentRatio,
|
||||||
|
GhostRatio: lru.Default2QGhostEntries,
|
||||||
|
}
|
||||||
|
_ = json.Unmarshal([]byte(conn), &config)
|
||||||
|
return lru.New2QParams[string, memoryItem](config.Size, config.RecentRatio, config.GhostRatio)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *twoQueueCache) Get(key string) (string, bool) {
|
||||||
|
item, ok := c.cache.Get(key)
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
if item.expired(timeNow()) {
|
||||||
|
c.cache.Remove(key)
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return item.value, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *twoQueueCache) Put(key, value string, ttl int64) error {
|
||||||
|
item := newMemoryItem(value, ttl)
|
||||||
c.cache.Add(key, item)
|
c.cache.Add(key, item)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets cached value by given key.
|
func (c *twoQueueCache) Delete(key string) error {
|
||||||
func (c *TwoQueueCache) Get(key string) any {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
cached, ok := c.cache.Get(key)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
item, ok := cached.(*MemoryItem)
|
|
||||||
|
|
||||||
if !ok || item.hasExpired() {
|
|
||||||
c.cache.Remove(key)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return item.Val
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete deletes cached value by given key.
|
|
||||||
func (c *TwoQueueCache) Delete(key string) error {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
c.cache.Remove(key)
|
c.cache.Remove(key)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Incr increases cached int-type value by given key as a counter.
|
func (c *twoQueueCache) IsExist(key string) bool {
|
||||||
func (c *TwoQueueCache) Incr(key string) error {
|
item, ok := c.cache.Peek(key)
|
||||||
c.lock.Lock()
|
return ok && !item.expired(timeNow())
|
||||||
defer c.lock.Unlock()
|
|
||||||
cached, ok := c.cache.Get(key)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
item, ok := cached.(*MemoryItem)
|
|
||||||
|
|
||||||
if !ok || item.hasExpired() {
|
|
||||||
c.cache.Remove(key)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
item.Val, err = mc.Incr(item.Val)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decr decreases cached int-type value by given key as a counter.
|
func (c *twoQueueCache) Ping() error {
|
||||||
func (c *TwoQueueCache) Decr(key string) error {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
cached, ok := c.cache.Get(key)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
item, ok := cached.(*MemoryItem)
|
|
||||||
|
|
||||||
if !ok || item.hasExpired() {
|
|
||||||
c.cache.Remove(key)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
item.Val, err = mc.Decr(item.Val)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsExist returns true if cached value exists.
|
|
||||||
func (c *TwoQueueCache) IsExist(key string) bool {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
cached, ok := c.cache.Peek(key)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
item, ok := cached.(*MemoryItem)
|
|
||||||
if !ok || item.hasExpired() {
|
|
||||||
c.cache.Remove(key)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush deletes all cached data.
|
|
||||||
func (c *TwoQueueCache) Flush() error {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
c.cache.Purge()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TwoQueueCache) checkAndInvalidate(key string) {
|
func (c *twoQueueCache) deleteExpired() {
|
||||||
c.lock.Lock()
|
now := timeNow()
|
||||||
defer c.lock.Unlock()
|
|
||||||
cached, ok := c.cache.Peek(key)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
item, ok := cached.(*MemoryItem)
|
|
||||||
if !ok || item.hasExpired() {
|
|
||||||
c.cache.Remove(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TwoQueueCache) startGC() {
|
|
||||||
if c.interval < 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, key := range c.cache.Keys() {
|
for _, key := range c.cache.Keys() {
|
||||||
c.checkAndInvalidate(key)
|
if item, ok := c.cache.Peek(key); ok && item.expired(now) {
|
||||||
}
|
// here might be a slight data-race: the item might have been removed or updated by another goroutine between the Peek and Remove calls,
|
||||||
time.AfterFunc(time.Duration(c.interval)*time.Second, c.startGC)
|
// but it's acceptable since the cache is not guaranteed to be 100% accurate and any item can be evicted at any time
|
||||||
}
|
c.cache.Remove(key)
|
||||||
|
|
||||||
// StartAndGC starts GC routine based on config string settings.
|
|
||||||
func (c *TwoQueueCache) StartAndGC(opts mc.Options) error {
|
|
||||||
var err error
|
|
||||||
size := 50000
|
|
||||||
if opts.AdapterConfig != "" {
|
|
||||||
size, err = strconv.Atoi(opts.AdapterConfig)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if !json.Valid([]byte(opts.AdapterConfig)) {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cfg := &TwoQueueCacheConfig{
|
type memoryItem struct {
|
||||||
Size: 50000,
|
value string
|
||||||
RecentRatio: lru.Default2QRecentRatio,
|
expiresAt time.Time
|
||||||
GhostRatio: lru.Default2QGhostEntries,
|
}
|
||||||
|
|
||||||
|
func newMemoryItem(value string, ttl int64) memoryItem {
|
||||||
|
item := memoryItem{value: value}
|
||||||
|
if ttl > 0 {
|
||||||
|
item.expiresAt = timeNow().Add(time.Duration(ttl) * time.Second)
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
func (item memoryItem) expired(now time.Time) bool {
|
||||||
|
return !item.expiresAt.IsZero() && !now.Before(item.expiresAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startGC(interval time.Duration, deleteExpired func()) {
|
||||||
|
if interval <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for range time.Tick(interval) {
|
||||||
|
deleteExpired()
|
||||||
}
|
}
|
||||||
_ = json.Unmarshal([]byte(opts.AdapterConfig), cfg)
|
}()
|
||||||
c.cache, err = lru.New2QParams[string, any](cfg.Size, cfg.RecentRatio, cfg.GhostRatio)
|
|
||||||
} else {
|
|
||||||
c.cache, err = lru.New2Q[string, any](size)
|
|
||||||
}
|
|
||||||
c.interval = opts.Interval
|
|
||||||
if c.interval > 0 {
|
|
||||||
go c.startGC()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ping tests if the cache is alive.
|
|
||||||
func (c *TwoQueueCache) Ping() error {
|
|
||||||
return mc.GenericPing(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
mc.Register("twoqueue", &TwoQueueCache{})
|
|
||||||
}
|
}
|
||||||
Vendored
+34
-37
@@ -5,13 +5,14 @@ package cache
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gitea.dev/modules/json"
|
"gitea.dev/modules/json"
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
|
|
||||||
chi_cache "gitea.com/go-chi/cache" //nolint:depguard // we wrap this package here
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type GetJSONError struct {
|
type GetJSONError struct {
|
||||||
@@ -30,57 +31,57 @@ type StringCache interface {
|
|||||||
Ping() error
|
Ping() error
|
||||||
|
|
||||||
Get(key string) (string, bool)
|
Get(key string) (string, bool)
|
||||||
Put(key, value string, ttl int64) error
|
Put(key, value string, ttl int64) error // ttl in seconds, 0 never expires, negative removes the key
|
||||||
Delete(key string) error
|
Delete(key string) error
|
||||||
IsExist(key string) bool
|
IsExist(key string) bool
|
||||||
|
|
||||||
PutJSON(key string, v any, ttl int64) error
|
PutJSON(key string, v any, ttl int64) error
|
||||||
GetJSON(key string, ptr any) (exist bool, err *GetJSONError)
|
GetJSON(key string, ptr any) (exist bool, err *GetJSONError)
|
||||||
|
}
|
||||||
|
|
||||||
ChiCache() chi_cache.Cache
|
type backend interface {
|
||||||
|
Get(key string) (string, bool)
|
||||||
|
Put(key, value string, ttl int64) error
|
||||||
|
Delete(key string) error
|
||||||
|
IsExist(key string) bool
|
||||||
|
Ping() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type stringCache struct {
|
type stringCache struct {
|
||||||
chiCache chi_cache.Cache
|
backend
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStringCache(cacheConfig setting.Cache) (StringCache, error) {
|
func NewStringCache(cacheConfig setting.Cache) (StringCache, error) {
|
||||||
adapter := util.IfZero(cacheConfig.Adapter, "memory")
|
cacheBackend, err := newBackend(cacheConfig)
|
||||||
interval := util.IfZero(cacheConfig.Interval, 60)
|
|
||||||
cc, err := chi_cache.NewCacher(chi_cache.Options{
|
|
||||||
Adapter: adapter,
|
|
||||||
AdapterConfig: cacheConfig.Conn,
|
|
||||||
Interval: interval,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &stringCache{chiCache: cc}, nil
|
return &stringCache{backend: cacheBackend}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *stringCache) Ping() error {
|
func newBackend(cacheConfig setting.Cache) (backend, error) {
|
||||||
return sc.chiCache.Ping()
|
gcInterval := time.Duration(util.IfZero(cacheConfig.Interval, 60)) * time.Second
|
||||||
}
|
switch adapter := util.IfZero(cacheConfig.Adapter, "memory"); adapter {
|
||||||
|
case "memory":
|
||||||
func (sc *stringCache) Get(key string) (string, bool) {
|
// the old "memory" adapter doesn't have a limit, which can lead to OOM
|
||||||
v := sc.chiCache.Get(key)
|
// now, use two-queue cache for in-memory cache with items limit, at most a few GB of memory will be used
|
||||||
if v == nil {
|
return newTwoQueueCache(strconv.FormatInt(10*1024*1024, 10), gcInterval)
|
||||||
return "", false
|
case "twoqueue":
|
||||||
|
return newTwoQueueCache(cacheConfig.Conn, gcInterval)
|
||||||
|
case "redis":
|
||||||
|
return newRedisCache(cacheConfig.Conn), nil
|
||||||
|
case "memcache":
|
||||||
|
return newMemcacheCache(cacheConfig.Conn)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown cache adapter %q", adapter)
|
||||||
}
|
}
|
||||||
s, ok := v.(string)
|
|
||||||
return s, ok
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *stringCache) Put(key, value string, ttl int64) error {
|
func (sc *stringCache) Put(key, value string, ttl int64) error {
|
||||||
return sc.chiCache.Put(key, value, ttl)
|
if ttl < 0 {
|
||||||
}
|
return sc.backend.Delete(key)
|
||||||
|
}
|
||||||
func (sc *stringCache) Delete(key string) error {
|
return sc.backend.Put(key, value, ttl)
|
||||||
return sc.chiCache.Delete(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *stringCache) IsExist(key string) bool {
|
|
||||||
return sc.chiCache.IsExist(key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cachedErrorPrefix = "<CACHED-ERROR>:"
|
const cachedErrorPrefix = "<CACHED-ERROR>:"
|
||||||
@@ -97,7 +98,7 @@ func (sc *stringCache) PutJSON(key string, v any, ttl int64) error {
|
|||||||
}
|
}
|
||||||
s = util.UnsafeBytesToString(b)
|
s = util.UnsafeBytesToString(b)
|
||||||
}
|
}
|
||||||
return sc.chiCache.Put(key, s, ttl)
|
return sc.Put(key, s, ttl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *stringCache) GetJSON(key string, ptr any) (exist bool, getErr *GetJSONError) {
|
func (sc *stringCache) GetJSON(key string, ptr any) (exist bool, getErr *GetJSONError) {
|
||||||
@@ -114,7 +115,3 @@ func (sc *stringCache) GetJSON(key string, ptr any) (exist bool, getErr *GetJSON
|
|||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *stringCache) ChiCache() chi_cache.Cache {
|
|
||||||
return sc.chiCache
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,517 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors.
|
||||||
|
// Copyright 2018-2024 The Ruby Citation File Format Developers. Licensed under the Apache License, Version 2.0
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Package citation formats CITATION.cff files, ported from the formatters of ruby-cff 1.3.0
|
||||||
|
package citation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmp"
|
||||||
|
"maps"
|
||||||
|
"regexp"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"gitea.dev/modules/util"
|
||||||
|
|
||||||
|
"go.yaml.in/yaml/v4"
|
||||||
|
"golang.org/x/text/runes"
|
||||||
|
"golang.org/x/text/transform"
|
||||||
|
"golang.org/x/text/unicode/norm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type date struct{ time.Time }
|
||||||
|
|
||||||
|
var dateSeparators = strings.NewReplacer("/", "-", ". ", " ", ".", "-", ",", "")
|
||||||
|
|
||||||
|
func (d *date) UnmarshalYAML(node *yaml.Node) error {
|
||||||
|
value := dateSeparators.Replace(node.Value)
|
||||||
|
for _, layout := range []string{"2006-1-2", "2-1-2006", "2 Jan 2006", "2 January 2006", "Jan 2 2006", "January 2 2006"} {
|
||||||
|
if parsed, err := time.Parse(layout, value); err == nil {
|
||||||
|
d.Time = parsed
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type license string
|
||||||
|
|
||||||
|
func (l *license) UnmarshalYAML(node *yaml.Node) error {
|
||||||
|
*l = license(node.Value)
|
||||||
|
if node.Kind != yaml.ScalarNode {
|
||||||
|
*l = license(inspectNode(node))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type actor struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Alias string `yaml:"alias"`
|
||||||
|
FamilyNames string `yaml:"family-names"`
|
||||||
|
GivenNames string `yaml:"given-names"`
|
||||||
|
NameParticle string `yaml:"name-particle"`
|
||||||
|
NameSuffix string `yaml:"name-suffix"`
|
||||||
|
Affiliation string `yaml:"affiliation"`
|
||||||
|
City string `yaml:"city"`
|
||||||
|
Region string `yaml:"region"`
|
||||||
|
Country string `yaml:"country"`
|
||||||
|
DateStart date `yaml:"date-start"`
|
||||||
|
DateEnd date `yaml:"date-end"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *actor) UnmarshalYAML(node *yaml.Node) error {
|
||||||
|
switch node.Kind {
|
||||||
|
case yaml.ScalarNode:
|
||||||
|
a.Name = node.Value
|
||||||
|
return nil
|
||||||
|
case yaml.SequenceNode:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
type plainActor actor
|
||||||
|
return node.Load((*plainActor)(a), yaml.WithUniqueKeys(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
type metadata struct {
|
||||||
|
Type string `yaml:"type"`
|
||||||
|
Title string `yaml:"title"`
|
||||||
|
Authors []actor `yaml:"authors"`
|
||||||
|
Version string `yaml:"version"`
|
||||||
|
DOI string `yaml:"doi"`
|
||||||
|
URL string `yaml:"url"`
|
||||||
|
RepositoryCode string `yaml:"repository-code"`
|
||||||
|
License license `yaml:"license"`
|
||||||
|
DateReleased date `yaml:"date-released"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type reference struct {
|
||||||
|
metadata `yaml:",inline"`
|
||||||
|
isTopLevel bool
|
||||||
|
Editors []actor `yaml:"editors"`
|
||||||
|
EditorsSeries []actor `yaml:"editors-series"`
|
||||||
|
DatePublished date `yaml:"date-published"`
|
||||||
|
Year string `yaml:"year"`
|
||||||
|
Month string `yaml:"month"`
|
||||||
|
Status string `yaml:"status"`
|
||||||
|
Journal string `yaml:"journal"`
|
||||||
|
Volume string `yaml:"volume"`
|
||||||
|
Issue string `yaml:"issue"`
|
||||||
|
Start string `yaml:"start"`
|
||||||
|
End string `yaml:"end"`
|
||||||
|
ISBN string `yaml:"isbn"`
|
||||||
|
Notes string `yaml:"notes"`
|
||||||
|
CollectionTitle string `yaml:"collection-title"`
|
||||||
|
ThesisType string `yaml:"thesis-type"`
|
||||||
|
Publisher actor `yaml:"publisher"`
|
||||||
|
Institution *actor `yaml:"institution"`
|
||||||
|
Conference actor `yaml:"conference"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
MaxContentSize = 256 * 1024 // parsing takes up to ~1000x the input, largest real-world file found is 80 KiB
|
||||||
|
maxAliasExpansion = 64 * 1024 // nodes plus value bytes aliases may add
|
||||||
|
)
|
||||||
|
|
||||||
|
// FormatCFF returns the APA and BibTeX citations of a CITATION.cff file, both empty if it has no title or authors
|
||||||
|
func FormatCFF(content string) (apa, bibtex string) {
|
||||||
|
var node yaml.Node
|
||||||
|
// the parser copies %TAG prefixes into every node
|
||||||
|
if len(content) > MaxContentSize || strings.Contains(content, "%TAG") || yaml.Unmarshal([]byte(content), &node) != nil || aliasExpansion(&node) > maxAliasExpansion {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
retagTimestamps(&node)
|
||||||
|
var file struct {
|
||||||
|
TopLevel metadata `yaml:",inline"`
|
||||||
|
PreferredCitation *reference `yaml:"preferred-citation"`
|
||||||
|
}
|
||||||
|
if node.Load(&file, yaml.WithUniqueKeys(false)) != nil {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
ref := file.PreferredCitation
|
||||||
|
if ref == nil {
|
||||||
|
ref = &reference{metadata: file.TopLevel, isTopLevel: true}
|
||||||
|
}
|
||||||
|
if ref.Title == "" || len(ref.Authors) == 0 {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
return ref.formatAPA(), ref.formatBibTeX()
|
||||||
|
}
|
||||||
|
|
||||||
|
func retagTimestamps(node *yaml.Node) {
|
||||||
|
if node.ShortTag() == "!!timestamp" {
|
||||||
|
node.Tag = "!!str"
|
||||||
|
}
|
||||||
|
for _, child := range node.Content {
|
||||||
|
retagTimestamps(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func aliasExpansion(root *yaml.Node) int {
|
||||||
|
anchors := map[*yaml.Node]int{}
|
||||||
|
added := 0
|
||||||
|
var expandedSize func(node, parent *yaml.Node) int
|
||||||
|
expandedSize = func(node, parent *yaml.Node) int {
|
||||||
|
if node.Kind == yaml.AliasNode {
|
||||||
|
size, walked := anchors[node.Alias]
|
||||||
|
if !walked && (node.Alias != parent || parent.Kind != yaml.SequenceNode) { // decoders never expand a sequence listing itself
|
||||||
|
size = maxAliasExpansion + 1
|
||||||
|
}
|
||||||
|
added = min(added+size, maxAliasExpansion+1)
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
size := 1 + len(node.Value)
|
||||||
|
for _, child := range node.Content {
|
||||||
|
size = min(size+expandedSize(child, node), maxAliasExpansion+1)
|
||||||
|
}
|
||||||
|
if node.Anchor != "" {
|
||||||
|
anchors[node] = size
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
expandedSize(root, nil)
|
||||||
|
return added
|
||||||
|
}
|
||||||
|
|
||||||
|
func inspectNode(node *yaml.Node) string {
|
||||||
|
var parts []string
|
||||||
|
switch node.Kind {
|
||||||
|
case yaml.AliasNode:
|
||||||
|
if node.Alias.Kind == yaml.ScalarNode { // collection aliases can be recursive
|
||||||
|
return inspectNode(node.Alias)
|
||||||
|
}
|
||||||
|
case yaml.SequenceNode:
|
||||||
|
for _, child := range node.Content {
|
||||||
|
parts = append(parts, inspectNode(child))
|
||||||
|
}
|
||||||
|
return "[" + strings.Join(parts, ", ") + "]"
|
||||||
|
case yaml.MappingNode:
|
||||||
|
for i := 0; i < len(node.Content); i += 2 {
|
||||||
|
parts = append(parts, inspectNode(node.Content[i])+" => "+inspectNode(node.Content[i+1]))
|
||||||
|
}
|
||||||
|
return "{" + strings.Join(parts, ", ") + "}"
|
||||||
|
}
|
||||||
|
if node.ShortTag() == "!!null" {
|
||||||
|
return "nil"
|
||||||
|
}
|
||||||
|
return strconv.Quote(node.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
var statusNotes = map[string]string{
|
||||||
|
"advance-online": "Advance online publication",
|
||||||
|
"in-preparation": "Manuscript in preparation.",
|
||||||
|
"submitted": "Manuscript submitted for publication.",
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinNonEmpty(sep string, parts ...string) string {
|
||||||
|
return strings.Join(util.SliceRemoveAll(parts, ""), sep)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reference) conferenceDates() (start, end date) {
|
||||||
|
if r.Type == "conference-paper" {
|
||||||
|
return r.Conference.DateStart, r.Conference.DateEnd
|
||||||
|
}
|
||||||
|
return date{}, date{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reference) monthAndYear() (month, year string) {
|
||||||
|
when, _ := r.conferenceDates()
|
||||||
|
if when.IsZero() {
|
||||||
|
if r.Status == "in-press" {
|
||||||
|
return "", "in press"
|
||||||
|
}
|
||||||
|
if r.Year != "" {
|
||||||
|
return r.Month, r.Year
|
||||||
|
}
|
||||||
|
when = cmp.Or(r.DateReleased, r.DatePublished)
|
||||||
|
}
|
||||||
|
if when.IsZero() {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
return strconv.Itoa(int(when.Month())), strconv.Itoa(when.Year())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reference) pages(dash string) string {
|
||||||
|
if r.Start == "" || r.End == "" || r.Start == r.End {
|
||||||
|
return r.Start
|
||||||
|
}
|
||||||
|
return r.Start + dash + r.End
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reference) volume() string {
|
||||||
|
if r.Volume == "" || r.Issue == "" {
|
||||||
|
return r.Volume
|
||||||
|
}
|
||||||
|
return r.Volume + "(" + r.Issue + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reference) institution() string {
|
||||||
|
if r.Institution == nil {
|
||||||
|
return r.Authors[0].Affiliation
|
||||||
|
}
|
||||||
|
return r.Institution.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reference) formatAPA() string {
|
||||||
|
authors := make([]string, 0, len(r.Authors))
|
||||||
|
for _, author := range r.Authors {
|
||||||
|
authors = append(authors, apaAuthor(author))
|
||||||
|
}
|
||||||
|
date := r.apaDate()
|
||||||
|
if date != "" {
|
||||||
|
date = "(" + date + ")"
|
||||||
|
}
|
||||||
|
version := ""
|
||||||
|
if r.Version != "" {
|
||||||
|
version = " (Version " + r.Version + ")"
|
||||||
|
}
|
||||||
|
url := cmp.Or(r.RepositoryCode, r.URL)
|
||||||
|
if r.DOI != "" {
|
||||||
|
url = "https://doi.org/" + r.DOI
|
||||||
|
}
|
||||||
|
return joinNonEmpty(". ", combineAuthors(authors), date, r.Title+version+r.apaTypeLabel(), r.apaPublicationData(), url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func apaAuthor(author actor) string {
|
||||||
|
if author.Name != "" {
|
||||||
|
return author.Name
|
||||||
|
}
|
||||||
|
name := cmp.Or(author.FamilyNames, author.GivenNames, author.Alias)
|
||||||
|
if author.FamilyNames != "" && author.GivenNames != "" {
|
||||||
|
name += ", " + initials(author.GivenNames) + "."
|
||||||
|
}
|
||||||
|
if author.NameParticle != "" {
|
||||||
|
name = author.NameParticle + " " + name
|
||||||
|
}
|
||||||
|
if author.NameSuffix != "" {
|
||||||
|
name += ", " + author.NameSuffix
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func initials(names string) string {
|
||||||
|
parts := splitWords(names)
|
||||||
|
for i, part := range parts {
|
||||||
|
first, _ := utf8.DecodeRuneInString(part)
|
||||||
|
parts[i] = util.ToTitleCase(string(first))
|
||||||
|
}
|
||||||
|
return strings.Join(parts, ". ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitWords(text string) []string {
|
||||||
|
return strings.FieldsFunc(text, func(char rune) bool { return char <= unicode.MaxASCII && unicode.IsSpace(char) })
|
||||||
|
}
|
||||||
|
|
||||||
|
func combineAuthors(authors []string) string {
|
||||||
|
if len(authors) == 1 {
|
||||||
|
return strings.TrimSuffix(authors[0], ".")
|
||||||
|
}
|
||||||
|
return strings.TrimSuffix(strings.Join(authors[:len(authors)-1], ", ")+", & "+authors[len(authors)-1], ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reference) apaDate() string {
|
||||||
|
start, end := r.conferenceDates()
|
||||||
|
if start.IsZero() || end.IsZero() || !start.Before(end.Time) {
|
||||||
|
_, year := r.monthAndYear()
|
||||||
|
return year
|
||||||
|
}
|
||||||
|
endLayout := "2"
|
||||||
|
if end.Month() != start.Month() {
|
||||||
|
endLayout = "January 2"
|
||||||
|
}
|
||||||
|
if end.Year() != start.Year() {
|
||||||
|
endLayout = "2006, " + endLayout
|
||||||
|
}
|
||||||
|
return start.Format("2006, January 2") + "–" + end.Format(endLayout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reference) apaTypeLabel() string {
|
||||||
|
switch {
|
||||||
|
case strings.Contains(r.Type, "data"):
|
||||||
|
return " [Data set]"
|
||||||
|
case strings.Contains(r.Type, "conference"):
|
||||||
|
return " [Conference paper]"
|
||||||
|
case !r.isTopLevel && !strings.Contains(r.Type, "software"):
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return " [Computer software]"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reference) apaPublicationData() string {
|
||||||
|
switch r.Type {
|
||||||
|
case "article":
|
||||||
|
return joinNonEmpty(", ", r.Journal, r.volume(), r.pages("–"), statusNotes[r.Status])
|
||||||
|
case "book":
|
||||||
|
return r.Publisher.Name
|
||||||
|
case "conference-paper":
|
||||||
|
return joinNonEmpty(", ", r.CollectionTitle, r.volume(), r.pages("–"))
|
||||||
|
case "report":
|
||||||
|
return r.institution()
|
||||||
|
case "phdthesis":
|
||||||
|
return "[" + cmp.Or(r.ThesisType, "Doctoral dissertation") + ", " + r.institution() + "]"
|
||||||
|
case "mastersthesis":
|
||||||
|
return "[" + cmp.Or(r.ThesisType, "Master's thesis") + ", " + r.institution() + "]"
|
||||||
|
case "unpublished":
|
||||||
|
return statusNotes[r.Status]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var bibtexTypeFields = map[string][]string{
|
||||||
|
"article": {"journal", "note", "number", "pages", "volume"},
|
||||||
|
"book": {"address", "editor", "isbn", "number", "pages", "publisher", "volume"},
|
||||||
|
"booklet": {"address"},
|
||||||
|
"inproceedings": {"address", "booktitle", "editor", "pages", "publisher", "series"},
|
||||||
|
"manual": {"address"},
|
||||||
|
"mastersthesis": {"address", "school", "type"},
|
||||||
|
"misc": {"pages"},
|
||||||
|
"phdthesis": {"address", "school", "type"},
|
||||||
|
"proceedings": {"address", "booktitle", "editor", "pages", "publisher", "series"},
|
||||||
|
"software": {"license", "version"},
|
||||||
|
"techreport": {"address", "institution", "number"},
|
||||||
|
"unpublished": {"note"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
|
bibtexEscaper *strings.Replacer
|
||||||
|
keyLetters *strings.Replacer
|
||||||
|
keyUnsafeChars *regexp.Regexp
|
||||||
|
bibtexPattern *regexp.Regexp
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
ret.bibtexEscaper = strings.NewReplacer("&", `\&`, "%", `\%`, "$", `\$`, "#", `\#`, "_", `\_`, "{", `\{`, "}", `\}`)
|
||||||
|
ret.keyLetters = strings.NewReplacer(
|
||||||
|
"Æ", "AE", "æ", "ae", "Ð", "D", "ð", "d", "Ø", "O", "ø", "o", "Þ", "Th", "þ", "th", "ß", "ss", "×", "x",
|
||||||
|
"Đ", "D", "đ", "d", "Ħ", "H", "ħ", "h", "ı", "i", "IJ", "IJ", "ij", "ij", "ĸ", "k", "Ŀ", "L", "ŀ", "l",
|
||||||
|
"Ł", "L", "ł", "l", "ʼn", "'n", "Ŋ", "NG", "ŋ", "ng", "Œ", "OE", "œ", "oe", "Ŧ", "T", "ŧ", "t",
|
||||||
|
)
|
||||||
|
ret.keyUnsafeChars = regexp.MustCompile(`[^a-zA-Z0-9-]+`)
|
||||||
|
|
||||||
|
// https://www.acm.org/publications/authors/bibtex-formatting
|
||||||
|
ret.bibtexPattern = regexp.MustCompile(`(?m)^\s*@?\w+\s*{`) // a simple and quick check, no need to be strict
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
|
func keyToASCII() transform.Transformer {
|
||||||
|
return transform.Chain(
|
||||||
|
runes.Remove(runes.Predicate(func(char rune) bool {
|
||||||
|
return char > unicode.MaxASCII && (char < 'À' || char > 'ž') && char != 'ệ'
|
||||||
|
})),
|
||||||
|
norm.NFD,
|
||||||
|
runes.Remove(runes.Predicate(func(char rune) bool { return char > unicode.MaxASCII })),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reference) formatBibTeX() string {
|
||||||
|
place := r.Publisher
|
||||||
|
if r.Type == "conference-paper" {
|
||||||
|
place = r.Conference
|
||||||
|
}
|
||||||
|
editors := r.Editors
|
||||||
|
if len(editors) == 0 {
|
||||||
|
editors = r.EditorsSeries
|
||||||
|
}
|
||||||
|
bibtexEscaper := globalVars().bibtexEscaper
|
||||||
|
typeFields := map[string]string{
|
||||||
|
"address": joinNonEmpty(", ", place.City, place.Region, place.Country),
|
||||||
|
"booktitle": bibtexEscaper.Replace(r.CollectionTitle),
|
||||||
|
"editor": bibtexActors(editors),
|
||||||
|
"institution": bibtexEscaper.Replace(r.institution()),
|
||||||
|
"isbn": bibtexEscaper.Replace(r.ISBN),
|
||||||
|
"journal": bibtexEscaper.Replace(r.Journal),
|
||||||
|
"license": bibtexEscaper.Replace(string(r.License)),
|
||||||
|
"note": statusNotes[r.Status],
|
||||||
|
"number": r.Issue,
|
||||||
|
"pages": r.pages("--"),
|
||||||
|
"publisher": bibtexEscaper.Replace(r.Publisher.Name),
|
||||||
|
"school": bibtexEscaper.Replace(r.institution()),
|
||||||
|
"series": bibtexEscaper.Replace(r.Conference.Name),
|
||||||
|
"type": r.ThesisType,
|
||||||
|
"version": bibtexEscaper.Replace(r.Version),
|
||||||
|
"volume": bibtexEscaper.Replace(r.Volume),
|
||||||
|
}
|
||||||
|
entryType := bibtexType(r.Type)
|
||||||
|
fields := map[string]string{
|
||||||
|
"author": bibtexActors(r.Authors),
|
||||||
|
"title": "{" + bibtexEscaper.Replace(r.Title) + "}",
|
||||||
|
"doi": bibtexEscaper.Replace(r.DOI),
|
||||||
|
}
|
||||||
|
for _, name := range bibtexTypeFields[entryType] {
|
||||||
|
fields[name] = typeFields[name]
|
||||||
|
}
|
||||||
|
month, year := r.monthAndYear()
|
||||||
|
if num, _ := strconv.Atoi(month); num >= 1 && num <= 12 {
|
||||||
|
fields["month"] = strings.ToLower(time.Month(num).String()[:3])
|
||||||
|
}
|
||||||
|
fields["year"] = year
|
||||||
|
fields["url"] = cmp.Or(r.RepositoryCode, r.URL)
|
||||||
|
fields["note"] = cmp.Or(fields["note"], r.Notes)
|
||||||
|
maps.DeleteFunc(fields, func(_, value string) bool { return value == "" })
|
||||||
|
|
||||||
|
lines := []string{bibtexKey(fields)}
|
||||||
|
for _, name := range slices.Sorted(maps.Keys(fields)) {
|
||||||
|
value := fields[name]
|
||||||
|
if name != "month" {
|
||||||
|
value = "{" + value + "}"
|
||||||
|
}
|
||||||
|
lines = append(lines, name+" = "+value)
|
||||||
|
}
|
||||||
|
return "@" + entryType + "{" + strings.Join(lines, ",\n") + "\n}"
|
||||||
|
}
|
||||||
|
|
||||||
|
func bibtexType(cffType string) string {
|
||||||
|
if cffType == "" || strings.Contains(cffType, "software") {
|
||||||
|
return "software"
|
||||||
|
}
|
||||||
|
switch cffType {
|
||||||
|
case "article", "book", "manual", "unpublished", "phdthesis", "mastersthesis":
|
||||||
|
return cffType
|
||||||
|
case "conference", "proceedings":
|
||||||
|
return "proceedings"
|
||||||
|
case "conference-paper":
|
||||||
|
return "inproceedings"
|
||||||
|
case "magazine-article", "newspaper-article":
|
||||||
|
return "article"
|
||||||
|
case "pamphlet":
|
||||||
|
return "booklet"
|
||||||
|
case "report":
|
||||||
|
return "techreport"
|
||||||
|
}
|
||||||
|
return "misc"
|
||||||
|
}
|
||||||
|
|
||||||
|
func bibtexActors(actors []actor) string {
|
||||||
|
bibtexEscaper := globalVars().bibtexEscaper
|
||||||
|
names := make([]string, 0, len(actors))
|
||||||
|
for _, entry := range actors {
|
||||||
|
switch {
|
||||||
|
case entry.Name != "":
|
||||||
|
names = append(names, "{"+bibtexEscaper.Replace(entry.Name)+"}")
|
||||||
|
case entry.FamilyNames == "" && entry.GivenNames == "":
|
||||||
|
names = append(names, bibtexEscaper.Replace(entry.Alias))
|
||||||
|
default:
|
||||||
|
family := entry.FamilyNames
|
||||||
|
if entry.NameParticle != "" {
|
||||||
|
family = entry.NameParticle + " " + family
|
||||||
|
}
|
||||||
|
names = append(names, joinNonEmpty(", ", family, entry.NameSuffix, entry.GivenNames))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(names, " and ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func bibtexKey(fields map[string]string) string {
|
||||||
|
author, _, _ := strings.Cut(fields["author"], ",")
|
||||||
|
titleWords := splitWords(fields["title"])
|
||||||
|
key := joinNonEmpty("_", author, strings.Join(titleWords[:min(3, len(titleWords))], "_"), fields["year"])
|
||||||
|
key, _, _ = transform.String(keyToASCII(), globalVars().keyLetters.Replace(key))
|
||||||
|
return strings.Trim(globalVars().keyUnsafeChars.ReplaceAllString(key, "_"), "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsLikelyBibTeX(content string) bool {
|
||||||
|
return globalVars().bibtexPattern.MatchString(content)
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package citation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFormatCFF(t *testing.T) {
|
||||||
|
cases := []struct{ cff, apa, bibtex string }{
|
||||||
|
{
|
||||||
|
cff: `title: Overridden
|
||||||
|
message: &mit MIT
|
||||||
|
authors: [{family-names: Haines, given-names: Robert}, {name: "The {curly_braces} Collective"}]
|
||||||
|
title: "Software that uses the following symbols: &, %, $, #"
|
||||||
|
version: 2024-01-16
|
||||||
|
license: [*mit, Apache-2.0]
|
||||||
|
date-released: 2024-01-16
|
||||||
|
`,
|
||||||
|
apa: "Haines, R., & The {curly_braces} Collective. (2024). Software that uses the following symbols: &, %, $, # (Version 2024-01-16) [Computer software]",
|
||||||
|
bibtex: `@software{Haines_Software_that_uses_2024,
|
||||||
|
author = {Haines, Robert and {The \{curly\_braces\} Collective}},
|
||||||
|
license = {["MIT", "Apache-2.0"]},
|
||||||
|
month = jan,
|
||||||
|
title = {{Software that uses the following symbols: \&, \%, \$, \#}},
|
||||||
|
version = {2024-01-16},
|
||||||
|
year = {2024}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cff: `authors:
|
||||||
|
- family-names: Smith
|
||||||
|
given-names: Arfon M.
|
||||||
|
title: "Software citation principles"
|
||||||
|
license: MIT
|
||||||
|
preferred-citation:
|
||||||
|
authors:
|
||||||
|
- family-names: Smith
|
||||||
|
given-names: A. M.
|
||||||
|
- name: "FORCE11 Software Citation Working Group"
|
||||||
|
doi: "10.7717/peerj-cs.86"
|
||||||
|
journal: "PeerJ Computer Science"
|
||||||
|
month: 9
|
||||||
|
start: e86
|
||||||
|
title: "Software citation principles"
|
||||||
|
type: article
|
||||||
|
volume: 2
|
||||||
|
issue: 123
|
||||||
|
year: 2016
|
||||||
|
publisher:
|
||||||
|
- name: The Open Journal
|
||||||
|
`,
|
||||||
|
apa: "Smith, A. M., & FORCE11 Software Citation Working Group. (2016). Software citation principles. PeerJ Computer Science, 2(123), e86. https://doi.org/10.7717/peerj-cs.86",
|
||||||
|
bibtex: `@article{Smith_Software_citation_principles_2016,
|
||||||
|
author = {Smith, A. M. and {FORCE11 Software Citation Working Group}},
|
||||||
|
doi = {10.7717/peerj-cs.86},
|
||||||
|
journal = {PeerJ Computer Science},
|
||||||
|
month = sep,
|
||||||
|
number = {123},
|
||||||
|
pages = {e86},
|
||||||
|
title = {{Software citation principles}},
|
||||||
|
volume = {2},
|
||||||
|
year = {2016}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cff: `preferred-citation:
|
||||||
|
type: conference-paper
|
||||||
|
version: 1.10
|
||||||
|
title: "Über tools"
|
||||||
|
authors:
|
||||||
|
- family-names: Ørsted
|
||||||
|
given-names: ǰan christian
|
||||||
|
name-particle: van
|
||||||
|
name-suffix: Jr.
|
||||||
|
collection-title: "Proceedings of X & Y"
|
||||||
|
conference:
|
||||||
|
name: "Conf_2020"
|
||||||
|
city: Berlin
|
||||||
|
country: DE
|
||||||
|
date-start: 30.06.2020
|
||||||
|
date-end: 2020-07-02
|
||||||
|
start: 10
|
||||||
|
end: 20
|
||||||
|
editors:
|
||||||
|
- affiliation: Press
|
||||||
|
editors-series:
|
||||||
|
- family-names: Editor
|
||||||
|
given-names: Eve
|
||||||
|
`,
|
||||||
|
apa: "van Ørsted, J̌. C., Jr. (2020, June 30–July 2). Über tools (Version 1.10) [Conference paper]. Proceedings of X & Y, 10–20",
|
||||||
|
bibtex: `@inproceedings{van_Orsted_Uber_tools_2020,
|
||||||
|
address = {Berlin, DE},
|
||||||
|
author = {van Ørsted, Jr., ǰan christian},
|
||||||
|
booktitle = {Proceedings of X \& Y},
|
||||||
|
month = jun,
|
||||||
|
pages = {10--20},
|
||||||
|
series = {Conf\_2020},
|
||||||
|
title = {{Über tools}},
|
||||||
|
year = {2020}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cff: `thesis: &thesis {type: phdthesis, title: Thesis}
|
||||||
|
preferred-citation:
|
||||||
|
<<: *thesis
|
||||||
|
authors:
|
||||||
|
- family-names: Nguyễn
|
||||||
|
given-names: Sam
|
||||||
|
affiliation: "Uni_A"
|
||||||
|
institution: {city: Hanoi}
|
||||||
|
license: &loop [*loop]
|
||||||
|
status: in-press
|
||||||
|
notes: A note
|
||||||
|
`,
|
||||||
|
apa: "Nguyễn, S. (in press). Thesis. [Doctoral dissertation, ]",
|
||||||
|
bibtex: `@phdthesis{Nguyn_Thesis_in_press,
|
||||||
|
author = {Nguyễn, Sam},
|
||||||
|
note = {A note},
|
||||||
|
title = {{Thesis}},
|
||||||
|
year = {in press}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{cff: "title: No authors\nauthor:\n - name: Typo\n"},
|
||||||
|
{cff: "title: T\nauthors: [{name: A}]\nmessage: " + strings.Repeat("x", MaxContentSize)},
|
||||||
|
{cff: "title: T\nauthors: [{name: A}]\nmessage: &s " + strings.Repeat("x", maxAliasExpansion/2) + "\nlicense: [*s, *s]\n"},
|
||||||
|
{cff: "%TAG !e! tag:example.com,2000:\n---\ntitle: T\nauthors: [{name: A}]\n"},
|
||||||
|
{cff: "preferred-citation: &m {title: T, name: A, authors: [*m]}\n"},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run("", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
apa, bibtex := FormatCFF(tc.cff)
|
||||||
|
assert.Equal(t, tc.apa, apa)
|
||||||
|
assert.Equal(t, tc.bibtex, bibtex)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsLikelyBibTeX(t *testing.T) {
|
||||||
|
assert.True(t, IsLikelyBibTeX("@article{key, title={Title}}"))
|
||||||
|
assert.True(t, IsLikelyBibTeX("Inproceedings\n{\n}\n"))
|
||||||
|
assert.True(t, IsLikelyBibTeX("% comment\n\n@misc{key}\n% comment\n"))
|
||||||
|
assert.False(t, IsLikelyBibTeX("not bib {}"))
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package consts
|
||||||
|
|
||||||
|
import "runtime"
|
||||||
|
|
||||||
|
const IsWindows = runtime.GOOS == "windows"
|
||||||
@@ -16,8 +16,8 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"gitea.dev/modules/consts"
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
"gitea.dev/modules/setting"
|
|
||||||
"gitea.dev/modules/timeutil"
|
"gitea.dev/modules/timeutil"
|
||||||
|
|
||||||
"github.com/mholt/archives"
|
"github.com/mholt/archives"
|
||||||
@@ -184,7 +184,7 @@ func (dumper *Dumper) Close() error {
|
|||||||
|
|
||||||
func (dumper *Dumper) normalizeFilePath(absPath string) string {
|
func (dumper *Dumper) normalizeFilePath(absPath string) string {
|
||||||
absPath = filepath.Clean(absPath)
|
absPath = filepath.Clean(absPath)
|
||||||
if setting.IsWindows {
|
if consts.IsWindows {
|
||||||
absPath = strings.ToLower(absPath)
|
absPath = strings.ToLower(absPath)
|
||||||
}
|
}
|
||||||
return absPath
|
return absPath
|
||||||
|
|||||||
@@ -15,11 +15,6 @@ func TestReadingBlameOutputSha256(t *testing.T) {
|
|||||||
setting.AppDataPath = t.TempDir()
|
setting.AppDataPath = t.TempDir()
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
|
|
||||||
if DefaultFeatures().UsingGogit {
|
|
||||||
t.Skip("Skipping test since gogit does not support sha256")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("Without .git-blame-ignore-revs", func(t *testing.T) {
|
t.Run("Without .git-blame-ignore-revs", func(t *testing.T) {
|
||||||
storage := mockRepository("repo5_pulls_sha256")
|
storage := mockRepository("repo5_pulls_sha256")
|
||||||
repo, err := OpenRepository(ctx, storage)
|
repo, err := OpenRepository(ctx, storage)
|
||||||
|
|||||||
+96
-2
@@ -12,11 +12,10 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"gitea.dev/modules/log"
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This file contains common functions between the gogit and !gogit variants for git Blobs
|
|
||||||
|
|
||||||
// Name returns name of the tree entry this blob object was created from (or empty string)
|
// Name returns name of the tree entry this blob object was created from (or empty string)
|
||||||
func (b *Blob) Name() string {
|
func (b *Blob) Name() string {
|
||||||
return b.name
|
return b.name
|
||||||
@@ -114,3 +113,98 @@ loop:
|
|||||||
_ = encoder.Close()
|
_ = encoder.Close()
|
||||||
return base64buf.String(), nil
|
return base64buf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Blob represents a Git object.
|
||||||
|
type Blob struct {
|
||||||
|
ID ObjectID
|
||||||
|
|
||||||
|
gotSize bool
|
||||||
|
size int64
|
||||||
|
name string
|
||||||
|
repo *Repository
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataAsync gets a ReadCloser for the contents of a blob without reading it all.
|
||||||
|
// Calling the Close function on the result will discard all unread output.
|
||||||
|
func (b *Blob) DataAsync(ctx context.Context) (_ io.ReadCloser, retErr error) {
|
||||||
|
batch, cancel, err := b.repo.CatFileBatch()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
// if there was an error, cancel the batch right away,
|
||||||
|
// otherwise let the caller close it
|
||||||
|
if retErr != nil {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
info, contentReader, err := batch.QueryContent(b.ID.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.gotSize = true
|
||||||
|
b.size = info.Size
|
||||||
|
return &blobReader{
|
||||||
|
rd: contentReader,
|
||||||
|
n: info.Size,
|
||||||
|
cancel: cancel,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the uncompressed size of the blob
|
||||||
|
func (b *Blob) Size(ctx context.Context) int64 {
|
||||||
|
if b.gotSize {
|
||||||
|
return b.size
|
||||||
|
}
|
||||||
|
|
||||||
|
batch, cancel, err := b.repo.CatFileBatch()
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repo.LogString(), err)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
defer cancel()
|
||||||
|
info, err := batch.QueryInfo(b.ID.String())
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repo.LogString(), err)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
b.gotSize = true
|
||||||
|
b.size = info.Size
|
||||||
|
return b.size
|
||||||
|
}
|
||||||
|
|
||||||
|
type blobReader struct {
|
||||||
|
rd BufferedReader
|
||||||
|
n int64
|
||||||
|
cancel func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *blobReader) Read(p []byte) (n int, err error) {
|
||||||
|
if b.n <= 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
if int64(len(p)) > b.n {
|
||||||
|
p = p[0:b.n]
|
||||||
|
}
|
||||||
|
n, err = b.rd.Read(p)
|
||||||
|
b.n -= int64(n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements io.Closer
|
||||||
|
func (b *blobReader) Close() error {
|
||||||
|
if b.rd == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
defer b.cancel()
|
||||||
|
|
||||||
|
if err := DiscardFull(b.rd, b.n+1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.rd = nil
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
//go:build gogit
|
|
||||||
|
|
||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"gitea.dev/modules/log"
|
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Blob represents a Git object.
|
|
||||||
type Blob struct {
|
|
||||||
ID ObjectID
|
|
||||||
repo *Repository
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Blob) gogitEncodedObj() (plumbing.EncodedObject, error) {
|
|
||||||
return b.repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, plumbing.Hash(b.ID.RawValue()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// DataAsync gets a ReadCloser for the contents of a blob without reading it all.
|
|
||||||
// Calling the Close function on the result will discard all unread output.
|
|
||||||
func (b *Blob) DataAsync(_ context.Context) (io.ReadCloser, error) {
|
|
||||||
obj, err := b.gogitEncodedObj()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return obj.Reader()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the uncompressed size of the blob
|
|
||||||
func (b *Blob) Size(_ context.Context) int64 {
|
|
||||||
obj, err := b.gogitEncodedObj()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error getting gogit encoded object for blob %s(%s): %v", b.name, b.ID.String(), err)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return obj.Size()
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
//go:build !gogit
|
|
||||||
|
|
||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"gitea.dev/modules/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Blob represents a Git object.
|
|
||||||
type Blob struct {
|
|
||||||
ID ObjectID
|
|
||||||
|
|
||||||
gotSize bool
|
|
||||||
size int64
|
|
||||||
name string
|
|
||||||
repo *Repository
|
|
||||||
}
|
|
||||||
|
|
||||||
// DataAsync gets a ReadCloser for the contents of a blob without reading it all.
|
|
||||||
// Calling the Close function on the result will discard all unread output.
|
|
||||||
func (b *Blob) DataAsync(ctx context.Context) (_ io.ReadCloser, retErr error) {
|
|
||||||
batch, cancel, err := b.repo.CatFileBatch()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
// if there was an error, cancel the batch right away,
|
|
||||||
// otherwise let the caller close it
|
|
||||||
if retErr != nil {
|
|
||||||
cancel()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
info, contentReader, err := batch.QueryContent(b.ID.String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b.gotSize = true
|
|
||||||
b.size = info.Size
|
|
||||||
return &blobReader{
|
|
||||||
rd: contentReader,
|
|
||||||
n: info.Size,
|
|
||||||
cancel: cancel,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the uncompressed size of the blob
|
|
||||||
func (b *Blob) Size(ctx context.Context) int64 {
|
|
||||||
if b.gotSize {
|
|
||||||
return b.size
|
|
||||||
}
|
|
||||||
|
|
||||||
batch, cancel, err := b.repo.CatFileBatch()
|
|
||||||
if err != nil {
|
|
||||||
log.Debug("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repo.LogString(), err)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
defer cancel()
|
|
||||||
info, err := batch.QueryInfo(b.ID.String())
|
|
||||||
if err != nil {
|
|
||||||
log.Debug("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repo.LogString(), err)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
b.gotSize = true
|
|
||||||
b.size = info.Size
|
|
||||||
return b.size
|
|
||||||
}
|
|
||||||
|
|
||||||
type blobReader struct {
|
|
||||||
rd BufferedReader
|
|
||||||
n int64
|
|
||||||
cancel func()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *blobReader) Read(p []byte) (n int, err error) {
|
|
||||||
if b.n <= 0 {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
if int64(len(p)) > b.n {
|
|
||||||
p = p[0:b.n]
|
|
||||||
}
|
|
||||||
n, err = b.rd.Read(p)
|
|
||||||
b.n -= int64(n)
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements io.Closer
|
|
||||||
func (b *blobReader) Close() error {
|
|
||||||
if b.rd == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
defer b.cancel()
|
|
||||||
|
|
||||||
if err := DiscardFull(b.rd, b.n+1); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.rd = nil
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
//go:build gogit
|
|
||||||
|
|
||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5/plumbing/object"
|
|
||||||
)
|
|
||||||
|
|
||||||
func convertPGPSignature(c *object.Commit) *CommitSignature {
|
|
||||||
if c.PGPSignature == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var w strings.Builder
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if _, err = fmt.Fprintf(&w, "tree %s\n", c.TreeHash.String()); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, parent := range c.ParentHashes {
|
|
||||||
if _, err = fmt.Fprintf(&w, "parent %s\n", parent.String()); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = fmt.Fprint(&w, "author "); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = c.Author.Encode(&w); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = fmt.Fprint(&w, "\ncommitter "); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = c.Committer.Encode(&w); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Encoding != "" && c.Encoding != "UTF-8" {
|
|
||||||
if _, err = fmt.Fprintf(&w, "\nencoding %s\n", c.Encoding); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = fmt.Fprintf(&w, "\n\n%s", c.Message); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &CommitSignature{
|
|
||||||
Signature: c.PGPSignature,
|
|
||||||
Payload: w.String(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertCommit(c *object.Commit) *Commit {
|
|
||||||
return &Commit{
|
|
||||||
ID: ParseGogitHash(c.Hash),
|
|
||||||
TreeID: ParseGogitHash(c.TreeHash),
|
|
||||||
CommitMessage: CommitMessage{MessageRaw: c.Message},
|
|
||||||
Committer: &c.Committer,
|
|
||||||
Author: &c.Author,
|
|
||||||
Signature: convertPGPSignature(c),
|
|
||||||
Parents: ParseGogitHashArray(c.ParentHashes),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -113,3 +113,36 @@ func getLastCommitForPathsByCache(ctx context.Context, commitID, treePath string
|
|||||||
|
|
||||||
return results, unHitEntryPaths, nil
|
return results, unHitEntryPaths, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetLastCommitForPaths returns last commit information
|
||||||
|
func GetLastCommitForPaths(ctx context.Context, gitRepo *Repository, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) {
|
||||||
|
// We read backwards from the commit to obtain all of the commits
|
||||||
|
revs, err := walkGitLog(ctx, gitRepo, commit, treePath, paths...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
commitsMap := map[string]*Commit{}
|
||||||
|
commitsMap[commit.ID.String()] = commit
|
||||||
|
|
||||||
|
commitCommits := map[string]*Commit{}
|
||||||
|
for path, commitID := range revs {
|
||||||
|
if len(commitID) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
c, ok := commitsMap[commitID]
|
||||||
|
if ok {
|
||||||
|
commitCommits[path] = c
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := gitRepo.GetCommit(ctx, commitID) // Ensure the commit exists in the repository
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
commitCommits[path] = c
|
||||||
|
}
|
||||||
|
|
||||||
|
return commitCommits, nil
|
||||||
|
}
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
//go:build gogit
|
|
||||||
|
|
||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/emirpasic/gods/trees/binaryheap"
|
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
|
||||||
"github.com/go-git/go-git/v5/plumbing/object"
|
|
||||||
cgobject "github.com/go-git/go-git/v5/plumbing/object/commitgraph"
|
|
||||||
)
|
|
||||||
|
|
||||||
type commitAndPaths struct {
|
|
||||||
commit cgobject.CommitNode
|
|
||||||
// Paths that are still on the branch represented by commit
|
|
||||||
paths []string
|
|
||||||
// Set of hashes for the paths
|
|
||||||
hashes map[string]plumbing.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCommitTree(c cgobject.CommitNode, treePath string) (*object.Tree, error) {
|
|
||||||
tree, err := c.Tree()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optimize deep traversals by focusing only on the specific tree
|
|
||||||
if treePath != "" {
|
|
||||||
tree, err = tree.Tree(treePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tree, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFileHashes(c cgobject.CommitNode, treePath string, paths []string) (map[string]plumbing.Hash, error) {
|
|
||||||
tree, err := getCommitTree(c, treePath)
|
|
||||||
if err == object.ErrDirectoryNotFound {
|
|
||||||
// The whole tree didn't exist, so return empty map
|
|
||||||
return make(map[string]plumbing.Hash), nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hashes := make(map[string]plumbing.Hash)
|
|
||||||
for _, path := range paths {
|
|
||||||
if path != "" {
|
|
||||||
entry, err := tree.FindEntry(path)
|
|
||||||
if err == nil {
|
|
||||||
hashes[path] = entry.Hash
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
hashes[path] = tree.Hash
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return hashes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLastCommitForPaths returns last commit information
|
|
||||||
func GetLastCommitForPaths(ctx context.Context, gitRepo *Repository, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) {
|
|
||||||
commitNodeIndex, closer := gitRepo.CommitNodeIndex()
|
|
||||||
defer closer()
|
|
||||||
|
|
||||||
c, err := commitNodeIndex.Get(plumbing.Hash(commit.ID.RawValue()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return getLastCommitForPathsByCommitNode(ctx, gitRepo, c, treePath, paths)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLastCommitForPathsByCommitNode(ctx context.Context, gitRepo *Repository, c cgobject.CommitNode, treePath string, paths []string) (map[string]*Commit, error) {
|
|
||||||
refSha := c.ID().String()
|
|
||||||
|
|
||||||
// We do a tree traversal with nodes sorted by commit time
|
|
||||||
heap := binaryheap.NewWith(func(a, b any) int {
|
|
||||||
if a.(*commitAndPaths).commit.CommitTime().Before(b.(*commitAndPaths).commit.CommitTime()) { //nolint:forcetypeassert // this heap only ever holds *commitAndPaths
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
})
|
|
||||||
|
|
||||||
resultNodes := make(map[string]cgobject.CommitNode)
|
|
||||||
initialHashes, err := getFileHashes(c, treePath, paths)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start search from the root commit and with full set of paths
|
|
||||||
heap.Push(&commitAndPaths{c, paths, initialHashes})
|
|
||||||
heaploop:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
if ctx.Err() == context.DeadlineExceeded {
|
|
||||||
break heaploop
|
|
||||||
}
|
|
||||||
return nil, ctx.Err()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
cIn, ok := heap.Pop()
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
current := cIn.(*commitAndPaths) //nolint:forcetypeassert // this heap only ever holds *commitAndPaths
|
|
||||||
|
|
||||||
// Load the parent commits for the one we are currently examining
|
|
||||||
numParents := current.commit.NumParents()
|
|
||||||
var parents []cgobject.CommitNode
|
|
||||||
for i := range numParents {
|
|
||||||
parent, err := current.commit.ParentNode(i)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
parents = append(parents, parent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Examine the current commit and set of interesting paths
|
|
||||||
pathUnchanged := make([]bool, len(current.paths))
|
|
||||||
parentHashes := make([]map[string]plumbing.Hash, len(parents))
|
|
||||||
for j, parent := range parents {
|
|
||||||
parentHashes[j], err = getFileHashes(parent, treePath, current.paths)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, path := range current.paths {
|
|
||||||
if parentHashes[j][path] == current.hashes[path] {
|
|
||||||
pathUnchanged[i] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var remainingPaths []string
|
|
||||||
for i, pth := range current.paths {
|
|
||||||
// The results could already contain some newer change for the same path,
|
|
||||||
// so don't override that and bail out on the file early.
|
|
||||||
if resultNodes[pth] == nil {
|
|
||||||
if pathUnchanged[i] {
|
|
||||||
// The path existed with the same hash in at least one parent so it could
|
|
||||||
// not have been changed in this commit directly.
|
|
||||||
remainingPaths = append(remainingPaths, pth)
|
|
||||||
} else {
|
|
||||||
// There are few possible cases how can we get here:
|
|
||||||
// - The path didn't exist in any parent, so it must have been created by
|
|
||||||
// this commit.
|
|
||||||
// - The path did exist in the parent commit, but the hash of the file has
|
|
||||||
// changed.
|
|
||||||
// - We are looking at a merge commit and the hash of the file doesn't
|
|
||||||
// match any of the hashes being merged. This is more common for directories,
|
|
||||||
// but it can also happen if a file is changed through conflict resolution.
|
|
||||||
resultNodes[pth] = current.commit
|
|
||||||
if err := gitRepo.LastCommitCache.Put(refSha, path.Join(treePath, pth), current.commit.ID().String()); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(remainingPaths) > 0 {
|
|
||||||
// Add the parent nodes along with remaining paths to the heap for further
|
|
||||||
// processing.
|
|
||||||
for j, parent := range parents {
|
|
||||||
// Combine remainingPath with paths available on the parent branch
|
|
||||||
// and make union of them
|
|
||||||
remainingPathsForParent := make([]string, 0, len(remainingPaths))
|
|
||||||
newRemainingPaths := make([]string, 0, len(remainingPaths))
|
|
||||||
for _, path := range remainingPaths {
|
|
||||||
if parentHashes[j][path] == current.hashes[path] {
|
|
||||||
remainingPathsForParent = append(remainingPathsForParent, path)
|
|
||||||
} else {
|
|
||||||
newRemainingPaths = append(newRemainingPaths, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if remainingPathsForParent != nil {
|
|
||||||
heap.Push(&commitAndPaths{parent, remainingPathsForParent, parentHashes[j]})
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(newRemainingPaths) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
remainingPaths = newRemainingPaths
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post-processing
|
|
||||||
result := make(map[string]*Commit)
|
|
||||||
for path, commitNode := range resultNodes {
|
|
||||||
commit, err := commitNode.Commit()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result[path] = convertCommit(commit)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
//go:build !gogit
|
|
||||||
|
|
||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetLastCommitForPaths returns last commit information
|
|
||||||
func GetLastCommitForPaths(ctx context.Context, gitRepo *Repository, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) {
|
|
||||||
// We read backwards from the commit to obtain all of the commits
|
|
||||||
revs, err := walkGitLog(ctx, gitRepo, commit, treePath, paths...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
commitsMap := map[string]*Commit{}
|
|
||||||
commitsMap[commit.ID.String()] = commit
|
|
||||||
|
|
||||||
commitCommits := map[string]*Commit{}
|
|
||||||
for path, commitID := range revs {
|
|
||||||
if len(commitID) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
c, ok := commitsMap[commitID]
|
|
||||||
if ok {
|
|
||||||
commitCommits[path] = c
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := gitRepo.GetCommit(ctx, commitID) // Ensure the commit exists in the repository
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
commitCommits[path] = c
|
|
||||||
}
|
|
||||||
|
|
||||||
return commitCommits, nil
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
//go:build !gogit
|
|
||||||
|
|
||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gitea.dev/modules/git/gitrepo"
|
|
||||||
"gitea.dev/modules/test"
|
|
||||||
"gitea.dev/modules/util"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEntries_GetCommitsInfo_ContextErr(t *testing.T) {
|
|
||||||
repoPath, _ := filepath.Abs(filepath.Join(testReposDir, "repo1_bare"))
|
|
||||||
repo, err := OpenRepository(t.Context(), gitrepo.RepositoryManaged("dummy", repoPath))
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer repo.Close()
|
|
||||||
|
|
||||||
commit, err := repo.GetCommit(t.Context(), "feaf4ba6bc635fec442f46ddd4512416ec43c2c2")
|
|
||||||
require.NoError(t, err)
|
|
||||||
entries, err := commit.Tree().ListEntries(t.Context(), repo)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
countCommitInfosCommit := func(infos []CommitInfo) (nilCommits, nonNilCommits int) {
|
|
||||||
for _, info := range infos {
|
|
||||||
nilCommits += util.Iif(info.Commit == nil, 1, 0)
|
|
||||||
nonNilCommits += util.Iif(info.Commit != nil, 1, 0)
|
|
||||||
}
|
|
||||||
return nilCommits, nonNilCommits
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(t.Context())
|
|
||||||
defer test.MockVariableValue(&walkGitLogDebugBeforeNext)()
|
|
||||||
|
|
||||||
walkGitLogDebugBeforeNext = cancel
|
|
||||||
commitInfos, _, err := entries.GetCommitsInfo(ctx, time.Second, "/any/repo-link", repo, commit, "")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
nilCommits, nonNilCommits := countCommitInfosCommit(commitInfos)
|
|
||||||
assert.Equal(t, 0, nonNilCommits) // no commit info due to canceled (or deadline-exceeded) context
|
|
||||||
assert.Equal(t, 3, nilCommits)
|
|
||||||
|
|
||||||
walkGitLogDebugBeforeNext = nil
|
|
||||||
commitInfos, _, err = entries.GetCommitsInfo(t.Context(), time.Second, "/any/repo-link", repo, commit, "")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
nilCommits, nonNilCommits = countCommitInfosCommit(commitInfos)
|
|
||||||
assert.Equal(t, 3, nonNilCommits)
|
|
||||||
assert.Equal(t, 0, nilCommits)
|
|
||||||
}
|
|
||||||
@@ -4,11 +4,14 @@
|
|||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.dev/modules/git/gitrepo"
|
"gitea.dev/modules/git/gitrepo"
|
||||||
|
"gitea.dev/modules/test"
|
||||||
|
"gitea.dev/modules/util"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -164,3 +167,40 @@ func TestEntries_GetCommitsInfo(t *testing.T) {
|
|||||||
assert.Nil(t, cisf.SubmoduleWebLinkTree(t.Context()))
|
assert.Nil(t, cisf.SubmoduleWebLinkTree(t.Context()))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEntries_GetCommitsInfo_ContextErr(t *testing.T) {
|
||||||
|
repoPath, _ := filepath.Abs(filepath.Join(testReposDir, "repo1_bare"))
|
||||||
|
repo, err := OpenRepository(t.Context(), gitrepo.RepositoryManaged("dummy", repoPath))
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer repo.Close()
|
||||||
|
|
||||||
|
commit, err := repo.GetCommit(t.Context(), "feaf4ba6bc635fec442f46ddd4512416ec43c2c2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
entries, err := commit.Tree().ListEntries(t.Context(), repo)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
countCommitInfosCommit := func(infos []CommitInfo) (nilCommits, nonNilCommits int) {
|
||||||
|
for _, info := range infos {
|
||||||
|
nilCommits += util.Iif(info.Commit == nil, 1, 0)
|
||||||
|
nonNilCommits += util.Iif(info.Commit != nil, 1, 0)
|
||||||
|
}
|
||||||
|
return nilCommits, nonNilCommits
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
|
defer test.MockVariableValue(&walkGitLogDebugBeforeNext)()
|
||||||
|
|
||||||
|
walkGitLogDebugBeforeNext = cancel
|
||||||
|
commitInfos, _, err := entries.GetCommitsInfo(ctx, time.Second, "/any/repo-link", repo, commit, "")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
nilCommits, nonNilCommits := countCommitInfosCommit(commitInfos)
|
||||||
|
assert.Equal(t, 0, nonNilCommits) // no commit info due to canceled (or deadline-exceeded) context
|
||||||
|
assert.Equal(t, 3, nilCommits)
|
||||||
|
|
||||||
|
walkGitLogDebugBeforeNext = nil
|
||||||
|
commitInfos, _, err = entries.GetCommitsInfo(t.Context(), time.Second, "/any/repo-link", repo, commit, "")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
nilCommits, nonNilCommits = countCommitInfosCommit(commitInfos)
|
||||||
|
assert.Equal(t, 3, nonNilCommits)
|
||||||
|
assert.Equal(t, 0, nilCommits)
|
||||||
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//go:build !gogit
|
|
||||||
|
|
||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
+16
-26
@@ -8,9 +8,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"gitea.dev/modules/consts"
|
||||||
"gitea.dev/modules/git/gitcmd"
|
"gitea.dev/modules/git/gitcmd"
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
)
|
)
|
||||||
@@ -73,15 +73,9 @@ func syncGitConfig(ctx context.Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if DefaultFeatures().SupportProcReceive {
|
// set support for AGit flow
|
||||||
// set support for AGit flow
|
if err := configAddNonExist(ctx, "receive.procReceiveRefs", "refs/for"); err != nil {
|
||||||
if err := configAddNonExist(ctx, "receive.procReceiveRefs", "refs/for"); err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := configUnsetAll(ctx, "receive.procReceiveRefs", "refs/for"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Due to CVE-2022-24765, git now denies access to git directories which are not owned by current user.
|
// Due to CVE-2022-24765, git now denies access to git directories which are not owned by current user.
|
||||||
@@ -96,7 +90,7 @@ func syncGitConfig(ctx context.Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
if consts.IsWindows {
|
||||||
if err := configSet(ctx, "core.longpaths", "true"); err != nil {
|
if err := configSet(ctx, "core.longpaths", "true"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -125,6 +119,11 @@ func syncGitConfig(ctx context.Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gitea used to write "90", which git reads as a date in 1990
|
||||||
|
if err := configUnsetAll(ctx, "gc.reflogExpire", "90"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Apply user's git config options last so they take precedence over builtin defaults
|
// Apply user's git config options last so they take precedence over builtin defaults
|
||||||
for k, v := range setting.GitConfig.Options {
|
for k, v := range setting.GitConfig.Options {
|
||||||
if err = configSet(ctx, strings.ToLower(k), v); err != nil {
|
if err = configSet(ctx, strings.ToLower(k), v); err != nil {
|
||||||
@@ -162,7 +161,7 @@ func configSet(ctx context.Context, key, value string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, _, err = gitcmd.NewCommand("config", "--global").
|
if _, _, err = gitcmd.NewCommand("config", "--global", "--replace-all").
|
||||||
AddDynamicArguments(key, value).
|
AddDynamicArguments(key, value).
|
||||||
RunStdString(ctx); err != nil {
|
RunStdString(ctx); err != nil {
|
||||||
return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
|
return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
|
||||||
@@ -190,7 +189,7 @@ func configSetNonExist(ctx context.Context, key, value string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func configAddNonExist(ctx context.Context, key, value string) error {
|
func configAddNonExist(ctx context.Context, key, value string) error {
|
||||||
_, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(ctx)
|
_, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key, "^"+regexp.QuoteMeta(value)+"$").RunStdString(ctx)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// already exist
|
// already exist
|
||||||
return nil
|
return nil
|
||||||
@@ -207,18 +206,9 @@ func configAddNonExist(ctx context.Context, key, value string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func configUnsetAll(ctx context.Context, key, value string) error {
|
func configUnsetAll(ctx context.Context, key, value string) error {
|
||||||
_, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx)
|
_, _, err := gitcmd.NewCommand("config", "--global", "--unset-all").AddDynamicArguments(key, "^"+regexp.QuoteMeta(value)+"$").RunStdString(ctx)
|
||||||
if err == nil {
|
if err != nil && !gitcmd.IsErrorExitCode(err, 5) { // 5: no such key or no matching value
|
||||||
// exist, need to remove
|
return fmt.Errorf("failed to unset git global config %s, err: %w", key, err)
|
||||||
_, _, err = gitcmd.NewCommand("config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to unset git global config %s, err: %w", key, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
if gitcmd.IsErrorExitCode(err, 1) {
|
return nil
|
||||||
// not exist
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,9 @@ func TestGitConfig(t *testing.T) {
|
|||||||
assert.NoError(t, configSetNonExist(ctx, "test.key-a", "val-a-changed"))
|
assert.NoError(t, configSetNonExist(ctx, "test.key-a", "val-a-changed"))
|
||||||
assert.False(t, gitConfigContains("key-a = val-a-changed"))
|
assert.False(t, gitConfigContains("key-a = val-a-changed"))
|
||||||
|
|
||||||
|
assert.NoError(t, configAddNonExist(ctx, "test.key-a", "val-a2"))
|
||||||
assert.NoError(t, configSet(ctx, "test.key-a", "val-a-changed"))
|
assert.NoError(t, configSet(ctx, "test.key-a", "val-a-changed"))
|
||||||
|
assert.NoError(t, configUnsetAll(ctx, "test.key-a", "val-a"))
|
||||||
assert.True(t, gitConfigContains("key-a = val-a-changed"))
|
assert.True(t, gitConfigContains("key-a = val-a-changed"))
|
||||||
|
|
||||||
assert.NoError(t, configAddNonExist(ctx, "test.key-b", "val-b"))
|
assert.NoError(t, configAddNonExist(ctx, "test.key-b", "val-b"))
|
||||||
@@ -41,6 +43,8 @@ func TestGitConfig(t *testing.T) {
|
|||||||
assert.NoError(t, configAddNonExist(ctx, "test.key-b", "val-2b"))
|
assert.NoError(t, configAddNonExist(ctx, "test.key-b", "val-2b"))
|
||||||
assert.True(t, gitConfigContains("key-b = val-b"))
|
assert.True(t, gitConfigContains("key-b = val-b"))
|
||||||
assert.True(t, gitConfigContains("key-b = val-2b"))
|
assert.True(t, gitConfigContains("key-b = val-2b"))
|
||||||
|
assert.NoError(t, configAddNonExist(ctx, "test.key-b", "val"))
|
||||||
|
assert.True(t, gitConfigContains("key-b = val\n"))
|
||||||
|
|
||||||
assert.NoError(t, configUnsetAll(ctx, "test.key-b", "val-b"))
|
assert.NoError(t, configUnsetAll(ctx, "test.key-b", "val-b"))
|
||||||
assert.False(t, gitConfigContains("key-b = val-b"))
|
assert.False(t, gitConfigContains("key-b = val-b"))
|
||||||
@@ -60,8 +64,10 @@ func TestSyncGitConfig(t *testing.T) {
|
|||||||
defer test.MockVariableValue(&setting.GitConfig)()
|
defer test.MockVariableValue(&setting.GitConfig)()
|
||||||
|
|
||||||
assert.Empty(t, setting.GitConfig.Options)
|
assert.Empty(t, setting.GitConfig.Options)
|
||||||
|
assert.NoError(t, configSet(t.Context(), "gc.reflogExpire", "90"))
|
||||||
assert.NoError(t, syncGitConfig(t.Context()))
|
assert.NoError(t, syncGitConfig(t.Context()))
|
||||||
assert.True(t, gitConfigContains("commitGraph = true")) // builtin default config
|
assert.True(t, gitConfigContains("commitGraph = true")) // builtin default config
|
||||||
|
assert.False(t, gitConfigContains("reflogExpire"))
|
||||||
|
|
||||||
setting.GitConfig.Options["sync-test.cfg-key-a"] = "CfgValA"
|
setting.GitConfig.Options["sync-test.cfg-key-a"] = "CfgValA"
|
||||||
setting.GitConfig.Options["core.commitgraph"] = "false"
|
setting.GitConfig.Options["core.commitgraph"] = "false"
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func ForceFastImportWithInit(ctx context.Context, repoLocalPath string, commits
|
|||||||
dirEntries, err := os.ReadDir(repoLocalPath)
|
dirEntries, err := os.ReadDir(repoLocalPath)
|
||||||
if os.IsNotExist(err) || (err == nil && len(dirEntries) == 0) {
|
if os.IsNotExist(err) || (err == nil && len(dirEntries) == 0) {
|
||||||
_ = os.MkdirAll(repoLocalPath, 0o755)
|
_ = os.MkdirAll(repoLocalPath, 0o755)
|
||||||
err := InitRepositoryLocal(ctx, repoLocalPath, initOpt.Bare, util.IfZero(initOpt.ObjectFormat, "sha1"))
|
err := InitRepositoryLocal(ctx, repoLocalPath, initOpt.Bare, util.IfZero(initOpt.ObjectFormat, "sha1"), "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-15
@@ -10,20 +10,23 @@ import (
|
|||||||
"gitea.dev/modules/git/gitrepo"
|
"gitea.dev/modules/git/gitrepo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FetchRemoteCommit fetches a specific commit and its related objects from a remote
|
// FetchRemoteTempCommit fetches a specific commit and its related objects from a remote repository
|
||||||
// repository into the managed repository.
|
// into the managed repository for temporary use (e.g.: compare, check merge base, etc.).
|
||||||
//
|
//
|
||||||
// If no reference (branch, tag, or other ref) points to the fetched commit, it will
|
// If no reference (branch, tag, or other ref) points to the fetched commit, it will be treated as unreachable
|
||||||
// be treated as unreachable and cleaned up by `git gc` after the default prune
|
// and cleaned up by auto-gc (a future push) or Gitea's "git gc" cronjob after the default prune expiration period.
|
||||||
// expiration period (2 weeks). Ref: https://www.kernel.org/pub/software/scm/git/docs/git-gc.html
|
// Ref: https://www.kernel.org/pub/software/scm/git/docs/git-gc.html
|
||||||
//
|
func FetchRemoteTempCommit(ctx context.Context, repo, remoteRepo RepositoryFacade, commitID string) error {
|
||||||
// This behavior is sufficient for temporary operations, such as determining the
|
// since it is for temporary use, we don't need to update any ref or lock anything or maintain anything
|
||||||
// merge base between commits.
|
return fetchRemoteCommand(remoteRepo, commitID).WithRepo(repo).Run(ctx)
|
||||||
func FetchRemoteCommit(ctx context.Context, repo, remoteRepo RepositoryFacade, commitID string) error {
|
}
|
||||||
return LockWriteAndDo(ctx, repo, func(ctx context.Context) error {
|
|
||||||
return gitcmd.NewCommand("fetch", "--no-tags").
|
// FetchRemoteCommitUpdateRef fetches a commit from a remote repository and points refName at it
|
||||||
AddDynamicArguments(gitrepo.RepoLocalPath(remoteRepo)).
|
func FetchRemoteCommitUpdateRef(ctx context.Context, repo, remoteRepo RepositoryFacade, commitish, refName string) error {
|
||||||
AddDynamicArguments(commitID).
|
return fetchRemoteCommand(remoteRepo, "+"+commitish+":"+refName).WithRepo(repo).RunWithStderr(ctx)
|
||||||
WithRepo(repo).Run(ctx)
|
}
|
||||||
})
|
|
||||||
|
func fetchRemoteCommand(remoteRepo RepositoryFacade, refspec string) *gitcmd.Command {
|
||||||
|
return gitcmd.NewCommand("fetch", "--no-tags", "--no-write-commit-graph", "--no-write-fetch-head", "--no-auto-maintenance").
|
||||||
|
AddDynamicArguments(gitrepo.RepoLocalPath(remoteRepo), refspec)
|
||||||
}
|
}
|
||||||
+3
-19
@@ -10,7 +10,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.dev/modules/cache"
|
"gitea.dev/modules/cache"
|
||||||
@@ -24,13 +23,11 @@ import (
|
|||||||
"github.com/hashicorp/go-version"
|
"github.com/hashicorp/go-version"
|
||||||
)
|
)
|
||||||
|
|
||||||
const RequiredVersion = "2.25.0" // the minimum Git version required
|
const RequiredVersion = "2.34.0" // the minimum Git version required
|
||||||
|
|
||||||
type Features struct {
|
type Features struct {
|
||||||
gitVersion *version.Version
|
gitVersion *version.Version
|
||||||
|
|
||||||
UsingGogit bool
|
|
||||||
SupportProcReceive bool // >= 2.29
|
|
||||||
SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’
|
SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’
|
||||||
SupportedObjectFormats []ObjectFormat // sha1, sha256
|
SupportedObjectFormats []ObjectFormat // sha1, sha256
|
||||||
SupportCheckAttrOnBare bool // >= 2.40
|
SupportCheckAttrOnBare bool // >= 2.40
|
||||||
@@ -79,9 +76,8 @@ func loadGitVersionFeatures() (*Features, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
features := &Features{gitVersion: ver, UsingGogit: isGogit}
|
features := &Features{gitVersion: ver}
|
||||||
features.SupportProcReceive = features.CheckVersionAtLeast("2.29")
|
features.SupportHashSha256 = features.CheckVersionAtLeast("2.42")
|
||||||
features.SupportHashSha256 = features.CheckVersionAtLeast("2.42") && !isGogit
|
|
||||||
features.SupportedObjectFormats = []ObjectFormat{Sha1ObjectFormat}
|
features.SupportedObjectFormats = []ObjectFormat{Sha1ObjectFormat}
|
||||||
if features.SupportHashSha256 {
|
if features.SupportHashSha256 {
|
||||||
features.SupportedObjectFormats = append(features.SupportedObjectFormats, Sha256ObjectFormat)
|
features.SupportedObjectFormats = append(features.SupportedObjectFormats, Sha256ObjectFormat)
|
||||||
@@ -129,16 +125,8 @@ func checkGitVersionCompatibility(gitVer *version.Version) error {
|
|||||||
func ensureGitVersion() error {
|
func ensureGitVersion() error {
|
||||||
if !DefaultFeatures().CheckVersionAtLeast(RequiredVersion) {
|
if !DefaultFeatures().CheckVersionAtLeast(RequiredVersion) {
|
||||||
moreHint := "get git: https://git-scm.com/downloads"
|
moreHint := "get git: https://git-scm.com/downloads"
|
||||||
if runtime.GOOS == "linux" {
|
|
||||||
// there are a lot of CentOS/RHEL users using old git, so we add a special hint for them
|
|
||||||
if _, err := os.Stat("/etc/redhat-release"); err == nil {
|
|
||||||
// ius.io is the recommended official(git-scm.com) method to install git
|
|
||||||
moreHint = "get git: https://git-scm.com/downloads/linux and https://ius.io"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", DefaultFeatures().gitVersion.Original(), RequiredVersion, moreHint)
|
return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", DefaultFeatures().gitVersion.Original(), RequiredVersion, moreHint)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := checkGitVersionCompatibility(DefaultFeatures().gitVersion); err != nil {
|
if err := checkGitVersionCompatibility(DefaultFeatures().gitVersion); err != nil {
|
||||||
return fmt.Errorf("installed git version %s has a known compatibility issue with Gitea: %w, please upgrade (or downgrade) git", DefaultFeatures().gitVersion.String(), err)
|
return fmt.Errorf("installed git version %s has a known compatibility issue with Gitea: %w, please upgrade (or downgrade) git", DefaultFeatures().gitVersion.String(), err)
|
||||||
}
|
}
|
||||||
@@ -209,7 +197,3 @@ func runGitTests(m interface{ Run() int }) int {
|
|||||||
func LockConfigAndDo(ctx context.Context, repo RepositoryFacade, fn func(ctx context.Context) error) error {
|
func LockConfigAndDo(ctx context.Context, repo RepositoryFacade, fn func(ctx context.Context) error) error {
|
||||||
return globallock.LockAndDo(ctx, "repo-config:"+repo.GitRepoManagedID(), fn)
|
return globallock.LockAndDo(ctx, "repo-config:"+repo.GitRepoManagedID(), fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LockWriteAndDo(ctx context.Context, repo RepositoryFacade, fn func(ctx context.Context) error) error {
|
|
||||||
return globallock.LockAndDo(ctx, "repo-write:"+repo.GitRepoManagedID(), fn)
|
|
||||||
}
|
|
||||||
@@ -94,8 +94,7 @@ const (
|
|||||||
StderrBadRevision StderrPrefix = "fatal: bad revision"
|
StderrBadRevision StderrPrefix = "fatal: bad revision"
|
||||||
StderrNoSuchPath StderrPrefix = "fatal: no such path"
|
StderrNoSuchPath StderrPrefix = "fatal: no such path"
|
||||||
|
|
||||||
StderrNoSuchRemote1 StderrPrefix = "fatal: no such remote" // git < 2.30, exit status 128
|
StderrNoSuchRemote StderrPrefix = "error: no such remote"
|
||||||
StderrNoSuchRemote2 StderrPrefix = "error: no such remote" // git >= 2.30. exit status 2
|
|
||||||
|
|
||||||
StderrAuthenticationFailed StderrPrefix = "fatal: Authentication failed for"
|
StderrAuthenticationFailed StderrPrefix = "fatal: Authentication failed for"
|
||||||
StderrCouldNotReadUsername StderrPrefix = "fatal: could not read Username"
|
StderrCouldNotReadUsername StderrPrefix = "fatal: could not read Username"
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
|
|
||||||
|
"gitea.dev/modules/consts"
|
||||||
"gitea.dev/modules/git/gitrepo"
|
"gitea.dev/modules/git/gitrepo"
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
@@ -156,7 +156,7 @@ func createDelegateHooks(hookDir string) (err error) {
|
|||||||
|
|
||||||
func checkExecutable(filename string) bool {
|
func checkExecutable(filename string) bool {
|
||||||
// windows has no concept of a executable bit
|
// windows has no concept of a executable bit
|
||||||
if runtime.GOOS == "windows" {
|
if consts.IsWindows {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
fileInfo, err := os.Stat(filename)
|
fileInfo, err := os.Stat(filename)
|
||||||
|
|||||||
@@ -25,14 +25,20 @@ type LastCommitCache struct {
|
|||||||
|
|
||||||
// Put puts the last commit id with commit and entry path
|
// Put puts the last commit id with commit and entry path
|
||||||
func (c *LastCommitCache) Put(ref, entryPath, commitID string) error {
|
func (c *LastCommitCache) Put(ref, entryPath, commitID string) error {
|
||||||
|
if c.ttlFn() < 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
log.Debug("LastCommitCache save: [%s:%s:%s]", ref, entryPath, commitID)
|
log.Debug("LastCommitCache save: [%s:%s:%s]", ref, entryPath, commitID)
|
||||||
return c.cache.Put(getCacheKey(c.repo, ref, entryPath), commitID, c.ttlFn())
|
return c.cache.Put(getCacheKey(c.repo, ref, entryPath), commitID, c.ttlFn())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets the last commit information by commit id and entry path
|
// Get gets the last commit information by commit id and entry path
|
||||||
func (c *LastCommitCache) Get(ctx context.Context, ref, entryPath string) (*Commit, error) {
|
func (c *LastCommitCache) Get(ctx context.Context, ref, entryPath string) (*Commit, error) {
|
||||||
lastCommitID, ok := c.cache.Get(getCacheKey(c.repo, ref, entryPath))
|
var lastCommitID string
|
||||||
if !ok || lastCommitID == "" {
|
if c.ttlFn() >= 0 {
|
||||||
|
lastCommitID, _ = c.cache.Get(getCacheKey(c.repo, ref, entryPath))
|
||||||
|
}
|
||||||
|
if lastCommitID == "" {
|
||||||
return nil, nil //nolint:nilnil // return nil when cache miss
|
return nil, nil //nolint:nilnil // return nil when cache miss
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,3 +78,43 @@ func (c *LastCommitCache) GetCommitByPath(ctx context.Context, entryCommitID Obj
|
|||||||
|
|
||||||
return lastCommit, nil
|
return lastCommit, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CacheCommit will cache the commit from the gitRepository
|
||||||
|
func (c *Commit) CacheCommit(ctx context.Context, gitRepo *Repository) error {
|
||||||
|
return c.recursiveCache(ctx, gitRepo, c.Tree(), "", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commit) recursiveCache(ctx context.Context, gitRepo *Repository, tree *Tree, treePath string, level int) error {
|
||||||
|
if level == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
entries, err := tree.ListEntries(ctx, gitRepo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
entryPaths := make([]string, len(entries))
|
||||||
|
for i, entry := range entries {
|
||||||
|
entryPaths[i] = entry.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = walkGitLog(ctx, gitRepo, c, treePath, entryPaths...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, treeEntry := range entries {
|
||||||
|
// entryMap won't contain "" therefore skip this.
|
||||||
|
if treeEntry.IsDir() {
|
||||||
|
subTree, err := tree.SubTree(ctx, gitRepo, treeEntry.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.recursiveCache(ctx, gitRepo, subTree, treeEntry.Name(), level-1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
//go:build gogit
|
|
||||||
|
|
||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
|
||||||
cgobject "github.com/go-git/go-git/v5/plumbing/object/commitgraph"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CacheCommit will cache the commit from the gitRepository
|
|
||||||
func (c *Commit) CacheCommit(ctx context.Context, gitRepo *Repository) error {
|
|
||||||
commitNodeIndex, closer := gitRepo.CommitNodeIndex()
|
|
||||||
defer closer()
|
|
||||||
|
|
||||||
index, err := commitNodeIndex.Get(plumbing.Hash(c.ID.RawValue()))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.recursiveCache(ctx, gitRepo, index, c.Tree(), "", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Commit) recursiveCache(ctx context.Context, gitRepo *Repository, index cgobject.CommitNode, tree *Tree, treePath string, level int) error {
|
|
||||||
if level == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
entries, err := tree.ListEntries(ctx, gitRepo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
entryPaths := make([]string, len(entries))
|
|
||||||
entryMap := make(map[string]*TreeEntry)
|
|
||||||
for i, entry := range entries {
|
|
||||||
entryPaths[i] = entry.Name()
|
|
||||||
entryMap[entry.Name()] = entry
|
|
||||||
}
|
|
||||||
|
|
||||||
commits, err := getLastCommitForPathsByCommitNode(ctx, gitRepo, index, treePath, entryPaths)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for entry := range commits {
|
|
||||||
if entryMap[entry].IsDir() {
|
|
||||||
subTree, err := tree.SubTree(ctx, gitRepo, entry)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := c.recursiveCache(ctx, gitRepo, index, subTree, entry, level-1); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
//go:build !gogit
|
|
||||||
|
|
||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CacheCommit will cache the commit from the gitRepository
|
|
||||||
func (c *Commit) CacheCommit(ctx context.Context, gitRepo *Repository) error {
|
|
||||||
return c.recursiveCache(ctx, gitRepo, c.Tree(), "", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Commit) recursiveCache(ctx context.Context, gitRepo *Repository, tree *Tree, treePath string, level int) error {
|
|
||||||
if level == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
entries, err := tree.ListEntries(ctx, gitRepo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
entryPaths := make([]string, len(entries))
|
|
||||||
for i, entry := range entries {
|
|
||||||
entryPaths[i] = entry.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = walkGitLog(ctx, gitRepo, c, treePath, entryPaths...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, treeEntry := range entries {
|
|
||||||
// entryMap won't contain "" therefore skip this.
|
|
||||||
if treeEntry.IsDir() {
|
|
||||||
subTree, err := tree.SubTree(ctx, gitRepo, treeEntry.Name())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := c.recursiveCache(ctx, gitRepo, subTree, treeEntry.Name(), level-1); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -38,8 +38,8 @@ func RenameRepository(ctx context.Context, repo, newRepo RepositoryFacade) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitRepository(ctx context.Context, repo RepositoryFacade, objectFormatName string) error {
|
func InitRepository(ctx context.Context, repo RepositoryFacade, objectFormatName, initialBranch string) error {
|
||||||
return InitRepositoryLocal(ctx, gitrepo.RepoLocalPath(repo), true, objectFormatName)
|
return InitRepositoryLocal(ctx, gitrepo.RepoLocalPath(repo), true, objectFormatName, initialBranch)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsRepoFileExist(ctx context.Context, repo RepositoryFacade, relativeFilePath string) (bool, error) {
|
func IsRepoFileExist(ctx context.Context, repo RepositoryFacade, relativeFilePath string) (bool, error) {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//go:build !gogit
|
|
||||||
|
|
||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -23,26 +21,17 @@ import (
|
|||||||
// logNameStatusRepo opens git log --raw in the provided repo and returns a parser
|
// logNameStatusRepo opens git log --raw in the provided repo and returns a parser
|
||||||
func logNameStatusRepo(ctx context.Context, repo RepositoryFacade, head, treepath string, paths ...string) *logNameStatusRepoParser {
|
func logNameStatusRepo(ctx context.Context, repo RepositoryFacade, head, treepath string, paths ...string) *logNameStatusRepoParser {
|
||||||
cmd := gitcmd.NewCommand()
|
cmd := gitcmd.NewCommand()
|
||||||
cmd.AddArguments("log", "--name-status", "-c", "--format=commit%x00%H %P%x00", "--parents", "--no-renames", "-t", "-z").AddDynamicArguments(head)
|
cmd.AddArguments("log", "--name-status", "-c", "--format=commit%x00%H %P%x00", "--parents", "--no-renames", "--no-follow", "-t", "-z").AddDynamicArguments(head)
|
||||||
|
|
||||||
var files []string
|
var files []string
|
||||||
if len(paths) < 70 {
|
if treepath != "" {
|
||||||
if treepath != "" {
|
files = append(files, treepath) // matches all entries, adding them would not narrow the log
|
||||||
files = append(files, treepath)
|
} else if len(paths) < 70 {
|
||||||
for _, pth := range paths {
|
for _, pth := range paths {
|
||||||
if pth != "" {
|
if pth != "" {
|
||||||
files = append(files, path.Join(treepath, pth))
|
files = append(files, pth)
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for _, pth := range paths {
|
|
||||||
if pth != "" {
|
|
||||||
files = append(files, pth)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if treepath != "" {
|
|
||||||
files = append(files, treepath)
|
|
||||||
}
|
}
|
||||||
// Use the :(literal) pathspec magic to handle edge cases with files named like ":file.txt" or "*.jpg"
|
// Use the :(literal) pathspec magic to handle edge cases with files named like ":file.txt" or "*.jpg"
|
||||||
for i, file := range files {
|
for i, file := range files {
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
//go:build gogit
|
|
||||||
|
|
||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
|
||||||
"github.com/go-git/go-git/v5/plumbing/hash"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ParseGogitHash(h plumbing.Hash) ObjectID {
|
|
||||||
switch hash.Size {
|
|
||||||
case 20:
|
|
||||||
return Sha1ObjectFormat.MustID(h[:])
|
|
||||||
case 32:
|
|
||||||
return Sha256ObjectFormat.MustID(h[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseGogitHashArray(objectIDs []plumbing.Hash) []ObjectID {
|
|
||||||
ret := make([]ObjectID, len(objectIDs))
|
|
||||||
for i, h := range objectIDs {
|
|
||||||
ret[i] = ParseGogitHash(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
@@ -43,8 +43,6 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
|
|||||||
return entries, nil
|
return entries, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ = catBatchParseTreeEntries // bypass "unused" lint because it is only used by "nogogit"
|
|
||||||
|
|
||||||
func catBatchParseTreeEntries(objectFormat ObjectFormat, ptree *Tree, rd BufferedReader, sz int64) ([]*TreeEntry, error) {
|
func catBatchParseTreeEntries(objectFormat ObjectFormat, ptree *Tree, rd BufferedReader, sz int64) ([]*TreeEntry, error) {
|
||||||
entries := make([]*TreeEntry, 0, 10)
|
entries := make([]*TreeEntry, 0, 10)
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func fillResultNameRev(ctx context.Context, repo git.RepositoryFacade, results []*LFSResult) error {
|
func fillResultNameRev(ctx context.Context, repo git.RepositoryFacade, results []*LFSResult) error {
|
||||||
// Should really use a go-git function here but name-rev is not completed and recapitulating it is not simple
|
|
||||||
wg := errgroup.Group{}
|
wg := errgroup.Group{}
|
||||||
cmd := gitcmd.NewCommand("name-rev", "--stdin", "--name-only", "--always").WithRepo(repo)
|
cmd := gitcmd.NewCommand("name-rev", "--stdin", "--name-only", "--always").WithRepo(repo)
|
||||||
stdin, stdinClose := cmd.MakeStdinPipe()
|
stdin, stdinClose := cmd.MakeStdinPipe()
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ func (err *ErrInvalidCloneAddr) Unwrap() error {
|
|||||||
|
|
||||||
// IsRemoteNotExistError checks the prefix of the error message to see whether a remote does not exist.
|
// IsRemoteNotExistError checks the prefix of the error message to see whether a remote does not exist.
|
||||||
func IsRemoteNotExistError(err error) bool {
|
func IsRemoteNotExistError(err error) bool {
|
||||||
return gitcmd.IsStderr(err, gitcmd.StderrNoSuchRemote1, gitcmd.StderrNoSuchRemote2)
|
return gitcmd.IsStderr(err, gitcmd.StderrNoSuchRemote)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseRemoteAddr checks if given remote address is valid,
|
// ParseRemoteAddr checks if given remote address is valid,
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsRemoteNotExistError(t *testing.T) {
|
||||||
|
_, err := GetRemoteAddress(t.Context(), mockRepository("repo1_bare"), "no-such-remote")
|
||||||
|
assert.True(t, IsRemoteNotExistError(err))
|
||||||
|
}
|
||||||
+5
-9
@@ -118,7 +118,7 @@ func IsRepoURLAccessible(ctx context.Context, url string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InitRepositoryLocal initializes a new Git repository.
|
// InitRepositoryLocal initializes a new Git repository.
|
||||||
func InitRepositoryLocal(ctx context.Context, localRepoPath string, bare bool, objectFormatName string) error {
|
func InitRepositoryLocal(ctx context.Context, localRepoPath string, bare bool, objectFormatName, initialBranch string) error {
|
||||||
err := os.MkdirAll(localRepoPath, os.ModePerm)
|
err := os.MkdirAll(localRepoPath, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -132,6 +132,9 @@ func InitRepositoryLocal(ctx context.Context, localRepoPath string, bare bool, o
|
|||||||
if DefaultFeatures().SupportHashSha256 {
|
if DefaultFeatures().SupportHashSha256 {
|
||||||
cmd.AddOptionValues("--object-format", objectFormatName)
|
cmd.AddOptionValues("--object-format", objectFormatName)
|
||||||
}
|
}
|
||||||
|
if initialBranch != "" {
|
||||||
|
cmd.AddOptionValues("--initial-branch", initialBranch)
|
||||||
|
}
|
||||||
|
|
||||||
if bare {
|
if bare {
|
||||||
cmd.AddArguments("--bare")
|
cmd.AddArguments("--bare")
|
||||||
@@ -223,7 +226,6 @@ func Clone(ctx context.Context, from, to string, opts CloneRepoOptions) error {
|
|||||||
// PushOptions options when push to remote
|
// PushOptions options when push to remote
|
||||||
type PushOptions struct {
|
type PushOptions struct {
|
||||||
Remote string
|
Remote string
|
||||||
LocalRefName string
|
|
||||||
Branch string
|
Branch string
|
||||||
Force bool
|
Force bool
|
||||||
ForceWithLease string
|
ForceWithLease string
|
||||||
@@ -245,13 +247,7 @@ func Push(ctx context.Context, localRepoPath string, opts PushOptions) error {
|
|||||||
}
|
}
|
||||||
remoteBranchArgs := []string{opts.Remote}
|
remoteBranchArgs := []string{opts.Remote}
|
||||||
if len(opts.Branch) > 0 {
|
if len(opts.Branch) > 0 {
|
||||||
var refspec string
|
remoteBranchArgs = append(remoteBranchArgs, opts.Branch)
|
||||||
if opts.LocalRefName != "" {
|
|
||||||
refspec = fmt.Sprintf("%s:%s", opts.LocalRefName, opts.Branch)
|
|
||||||
} else {
|
|
||||||
refspec = opts.Branch
|
|
||||||
}
|
|
||||||
remoteBranchArgs = append(remoteBranchArgs, refspec)
|
|
||||||
}
|
}
|
||||||
cmd.AddDashesAndList(remoteBranchArgs...)
|
cmd.AddDashesAndList(remoteBranchArgs...)
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,8 @@
|
|||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//go:build !gogit
|
|
||||||
|
|
||||||
package git
|
package git
|
||||||
|
|
||||||
const isGogit = false
|
|
||||||
|
|
||||||
type Repository struct {
|
type Repository struct {
|
||||||
RepositoryBase
|
RepositoryBase
|
||||||
}
|
}
|
||||||
Loaded 100 of 470 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user