mirror of
https://github.com/go-gitea/gitea.git
synced 2025-11-05 18:32:41 +09:00
Compare commits
152 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cccd54999a | ||
|
|
3e7cb5b639 | ||
|
|
ec146b4200 | ||
|
|
51fa86f0fa | ||
|
|
e67b004535 | ||
|
|
a4cc867401 | ||
|
|
8e5aa8fb1e | ||
|
|
046fc8684c | ||
|
|
e3e705200a | ||
|
|
a9d5ab8f88 | ||
|
|
5546b4279c | ||
|
|
4f6d09fb68 | ||
|
|
4e5aca62ee | ||
|
|
030ed9462d | ||
|
|
60f175f7ff | ||
|
|
260816d64a | ||
|
|
a01f56a61a | ||
|
|
c01459a504 | ||
|
|
6e4e3ca012 | ||
|
|
ed0e8865f3 | ||
|
|
dc5adce70d | ||
|
|
328ce0485f | ||
|
|
39b6abf955 | ||
|
|
3eda79647b | ||
|
|
97171be1b4 | ||
|
|
9ef2a338d8 | ||
|
|
66ccae8b90 | ||
|
|
0e6489317e | ||
|
|
67dc1ff926 | ||
|
|
4ee4c06b07 | ||
|
|
3063e37802 | ||
|
|
a40e15a116 | ||
|
|
8f75f61b64 | ||
|
|
25e409e025 | ||
|
|
f8f24d83cf | ||
|
|
15e93a751c | ||
|
|
5a9b3bfa50 | ||
|
|
dd901983c0 | ||
|
|
7f962a16c9 | ||
|
|
5d81f6d73f | ||
|
|
eee4a752a5 | ||
|
|
b3516767fb | ||
|
|
8d1be2a9c5 | ||
|
|
e46f9ff534 | ||
|
|
690e810bcc | ||
|
|
35983ac0a8 | ||
|
|
4b3400bd9c | ||
|
|
7758df4264 | ||
|
|
f994f3cac6 | ||
|
|
f514b2651e | ||
|
|
347101f2a8 | ||
|
|
b5f8c4a510 | ||
|
|
d6cee7c596 | ||
|
|
987219ab3c | ||
|
|
92280637a4 | ||
|
|
5fadcf997e | ||
|
|
be94f7bc07 | ||
|
|
9054a6670c | ||
|
|
fc82204fca | ||
|
|
6f8e62fa9c | ||
|
|
a2c6ecc093 | ||
|
|
523a84e5d0 | ||
|
|
869ee4fc38 | ||
|
|
16a332464d | ||
|
|
d03e7fd65e | ||
|
|
92f2d904f0 | ||
|
|
9c3511f0b1 | ||
|
|
69d35ee911 | ||
|
|
b8a7c20474 | ||
|
|
2cc76009dc | ||
|
|
730742230f | ||
|
|
8939c3845a | ||
|
|
d634e7576f | ||
|
|
7ded86f5af | ||
|
|
27a60fd91b | ||
|
|
e3021fae79 | ||
|
|
81126daf53 | ||
|
|
1c7339e385 | ||
|
|
039924aa2a | ||
|
|
b5007c6154 | ||
|
|
ae595aa913 | ||
|
|
aeeccc9642 | ||
|
|
37e99d9b34 | ||
|
|
9da6d4ea85 | ||
|
|
8844e62cb4 | ||
|
|
de7026528b | ||
|
|
ee3f5e8fac | ||
|
|
b2707bcd18 | ||
|
|
0512b02b01 | ||
|
|
99545ae2fd | ||
|
|
7697df9f93 | ||
|
|
d17f8ffcc1 | ||
|
|
5e9cc919cf | ||
|
|
cc6ec56738 | ||
|
|
76bd60fc1d | ||
|
|
744f7c8200 | ||
|
|
da33b708af | ||
|
|
8fa3925874 | ||
|
|
7794ff0874 | ||
|
|
7c17d0a73e | ||
|
|
a014d071e4 | ||
|
|
312565e3c2 | ||
|
|
58daaf66e8 | ||
|
|
f076ada601 | ||
|
|
92436b8b2a | ||
|
|
2df7d0835a | ||
|
|
200cb6140d | ||
|
|
2746c6f1aa | ||
|
|
23971a77a0 | ||
|
|
ebac324ff2 | ||
|
|
7df1204795 | ||
|
|
159544a950 | ||
|
|
9780da583d | ||
|
|
a8eaf43f97 | ||
|
|
b6fd8741ee | ||
|
|
2674d27fb8 | ||
|
|
6f3837284d | ||
|
|
c30f4f4be5 | ||
|
|
4578288ea3 | ||
|
|
826fffb59e | ||
|
|
2196ba5e42 | ||
|
|
12347f07ae | ||
|
|
a3c5358d35 | ||
|
|
987d014468 | ||
|
|
e08eed9040 | ||
|
|
4ffa49aa04 | ||
|
|
72837530bf | ||
|
|
eef635523a | ||
|
|
8f45a11919 | ||
|
|
e72d001708 | ||
|
|
8d9ea68f19 | ||
|
|
bf664c2e85 | ||
|
|
52d298890b | ||
|
|
c09e43acf5 | ||
|
|
3b4af01633 | ||
|
|
b4e2d5e8ee | ||
|
|
2984a7c121 | ||
|
|
80cc87b3d8 | ||
|
|
10b6047498 | ||
|
|
2c47b06869 | ||
|
|
31f2a325dc | ||
|
|
fcbbc24cc4 | ||
|
|
1454e1b6eb | ||
|
|
d70348836b | ||
|
|
940a930d13 | ||
|
|
45d21a0d5c | ||
|
|
15ad001aef | ||
|
|
ed1828ca92 | ||
|
|
3cfff5af0d | ||
|
|
6f6c66a07d | ||
|
|
d65af69c2b | ||
|
|
12c24c2189 |
7
.github/workflows/pull-db-tests.yml
vendored
7
.github/workflows/pull-db-tests.yml
vendored
@@ -202,12 +202,11 @@ jobs:
|
|||||||
test-mssql:
|
test-mssql:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
# specifying the version of ubuntu in use as mssql fails on newer kernels
|
# NOTE: mssql-2017 docker image will panic when run on hosts that have Ubuntu newer than 20.04
|
||||||
# pending resolution from vendor
|
runs-on: ubuntu-latest
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
services:
|
services:
|
||||||
mssql:
|
mssql:
|
||||||
image: mcr.microsoft.com/mssql/server:2017-latest
|
image: mcr.microsoft.com/mssql/server:2019-latest
|
||||||
env:
|
env:
|
||||||
ACCEPT_EULA: Y
|
ACCEPT_EULA: Y
|
||||||
MSSQL_PID: Standard
|
MSSQL_PID: Standard
|
||||||
|
|||||||
26
.github/workflows/release-nightly.yml
vendored
26
.github/workflows/release-nightly.yml
vendored
@@ -59,6 +59,8 @@ jobs:
|
|||||||
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
||||||
nightly-docker-rootful:
|
nightly-docker-rootful:
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
@@ -85,17 +87,27 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: fetch go modules
|
- name: fetch go modules
|
||||||
run: make vendor
|
run: make vendor
|
||||||
- name: build rootful docker image
|
- name: build rootful docker image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
push: true
|
push: true
|
||||||
tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}
|
tags: |-
|
||||||
|
gitea/gitea:${{ steps.clean_name.outputs.branch }}
|
||||||
|
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}
|
||||||
nightly-docker-rootless:
|
nightly-docker-rootless:
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
@@ -122,6 +134,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: fetch go modules
|
- name: fetch go modules
|
||||||
run: make vendor
|
run: make vendor
|
||||||
- name: build rootless docker image
|
- name: build rootless docker image
|
||||||
@@ -131,4 +149,6 @@ jobs:
|
|||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
file: Dockerfile.rootless
|
file: Dockerfile.rootless
|
||||||
tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
|
tags: |-
|
||||||
|
gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
|
||||||
|
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
|
||||||
|
|||||||
28
.github/workflows/release-tag-rc.yml
vendored
28
.github/workflows/release-tag-rc.yml
vendored
@@ -69,6 +69,8 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
docker-rootful:
|
docker-rootful:
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
@@ -79,7 +81,9 @@ jobs:
|
|||||||
- uses: docker/metadata-action@v5
|
- uses: docker/metadata-action@v5
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: gitea/gitea
|
images: |-
|
||||||
|
gitea/gitea
|
||||||
|
ghcr.io/go-gitea/gitea
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=false
|
latest=false
|
||||||
# 1.2.3-rc0
|
# 1.2.3-rc0
|
||||||
@@ -90,16 +94,24 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build rootful docker image
|
- name: build rootful docker image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
docker-rootless:
|
docker-rootless:
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
@@ -110,7 +122,9 @@ jobs:
|
|||||||
- uses: docker/metadata-action@v5
|
- uses: docker/metadata-action@v5
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: gitea/gitea
|
images: |-
|
||||||
|
gitea/gitea
|
||||||
|
ghcr.io/go-gitea/gitea
|
||||||
# each tag below will have the suffix of -rootless
|
# each tag below will have the suffix of -rootless
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=false
|
latest=false
|
||||||
@@ -123,11 +137,17 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build rootless docker image
|
- name: build rootless docker image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
push: true
|
push: true
|
||||||
file: Dockerfile.rootless
|
file: Dockerfile.rootless
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
|||||||
32
.github/workflows/release-tag-version.yml
vendored
32
.github/workflows/release-tag-version.yml
vendored
@@ -14,6 +14,8 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
binary:
|
binary:
|
||||||
runs-on: namespace-profile-gitea-release-binary
|
runs-on: namespace-profile-gitea-release-binary
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
@@ -71,6 +73,8 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
docker-rootful:
|
docker-rootful:
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
@@ -81,26 +85,34 @@ jobs:
|
|||||||
- uses: docker/metadata-action@v5
|
- uses: docker/metadata-action@v5
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: gitea/gitea
|
images: |-
|
||||||
|
gitea/gitea
|
||||||
|
ghcr.io/go-gitea/gitea
|
||||||
# this will generate tags in the following format:
|
# this will generate tags in the following format:
|
||||||
# latest
|
# latest
|
||||||
# 1
|
# 1
|
||||||
# 1.2
|
# 1.2
|
||||||
# 1.2.3
|
# 1.2.3
|
||||||
tags: |
|
tags: |
|
||||||
|
type=semver,pattern={{version}}
|
||||||
type=semver,pattern={{major}}
|
type=semver,pattern={{major}}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
type=semver,pattern={{version}}
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build rootful docker image
|
- name: build rootful docker image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
@@ -116,7 +128,9 @@ jobs:
|
|||||||
- uses: docker/metadata-action@v5
|
- uses: docker/metadata-action@v5
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: gitea/gitea
|
images: |-
|
||||||
|
gitea/gitea
|
||||||
|
ghcr.io/go-gitea/gitea
|
||||||
# each tag below will have the suffix of -rootless
|
# each tag below will have the suffix of -rootless
|
||||||
flavor: |
|
flavor: |
|
||||||
suffix=-rootless,onlatest=true
|
suffix=-rootless,onlatest=true
|
||||||
@@ -126,19 +140,25 @@ jobs:
|
|||||||
# 1.2
|
# 1.2
|
||||||
# 1.2.3
|
# 1.2.3
|
||||||
tags: |
|
tags: |
|
||||||
|
type=semver,pattern={{version}}
|
||||||
type=semver,pattern={{major}}
|
type=semver,pattern={{major}}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
type=semver,pattern={{version}}
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build rootless docker image
|
- name: build rootless docker image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
push: true
|
push: true
|
||||||
file: Dockerfile.rootless
|
file: Dockerfile.rootless
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
|||||||
184
CHANGELOG.md
184
CHANGELOG.md
@@ -4,6 +4,190 @@ This changelog goes through the changes that have been made in each release
|
|||||||
without substantial changes to our git log; to see the highlights of what has
|
without substantial changes to our git log; to see the highlights of what has
|
||||||
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||||
|
|
||||||
|
## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/1.23.8) - 2025-05-11
|
||||||
|
|
||||||
|
* SECURITY
|
||||||
|
* Fix a bug when uploading file via lfs ssh command (#34408) (#34411)
|
||||||
|
* Update net package (#34228) (#34232)
|
||||||
|
* BUGFIXES
|
||||||
|
* Fix releases sidebar navigation link (#34436) #34439
|
||||||
|
* Fix bug webhook milestone is not right. (#34419) #34429
|
||||||
|
* Fix two missed null value checks on the wiki page. (#34205) (#34215)
|
||||||
|
* Swift files can be passed either as file or as form value (#34068) (#34236)
|
||||||
|
* Fix bug when API get pull changed files for deleted head repository (#34333) (#34368)
|
||||||
|
* Upgrade github v61 -> v71 to fix migrating bug (#34389)
|
||||||
|
* Fix bug when visiting comparation page (#34334) (#34364)
|
||||||
|
* Fix wrong review requests when updating the pull request (#34286) (#34304)
|
||||||
|
* Fix github migration error when using multiple tokens (#34144) (#34302)
|
||||||
|
* Explicitly not update indexes when sync database schemas (#34281) (#34295)
|
||||||
|
* Fix panic when comment is nil (#34257) (#34277)
|
||||||
|
* Fix project board links to related Pull Requests (#34213) (#34222)
|
||||||
|
* Don't assume the default wiki branch is master in the wiki API (#34244) (#34245)
|
||||||
|
* DOCUMENTATION
|
||||||
|
* Update token creation API swagger documentation (#34288) (#34296)
|
||||||
|
* MISC
|
||||||
|
* Fix CI Build (#34315)
|
||||||
|
* Add riscv64 support (#34199) (#34204)
|
||||||
|
* Bump go version in go.mod (#34160)
|
||||||
|
* remove hardcoded 'code' string in clone_panel.tmpl (#34153) (#34158)
|
||||||
|
|
||||||
|
## [1.23.7](https://github.com/go-gitea/gitea/releases/tag/1.23.7) - 2025-04-07
|
||||||
|
|
||||||
|
* Enhancements
|
||||||
|
* Add a config option to block "expensive" pages for anonymous users (#34024) (#34071)
|
||||||
|
* Also check default ssh-cert location for host (#34099) (#34100) (#34116)
|
||||||
|
* BUGFIXES
|
||||||
|
* Fix discord webhook 400 status code when description limit is exceeded (#34084) (#34124)
|
||||||
|
* Get changed files based on merge base when checking `pull_request` actions trigger (#34106) (#34120)
|
||||||
|
* Fix invalid version in RPM package path (#34112) (#34115)
|
||||||
|
* Return default avatar url when user id is zero rather than updating database (#34094) (#34095)
|
||||||
|
* Add additional ReplaceAll in pathsep to cater for different pathsep (#34061) (#34070)
|
||||||
|
* Try to fix check-attr bug (#34029) (#34033)
|
||||||
|
* Git client will follow 301 but 307 (#34005) (#34010)
|
||||||
|
* Fix block expensive for 1.23 (#34127)
|
||||||
|
* Fix markdown frontmatter rendering (#34102) (#34107)
|
||||||
|
* Add new CLI flags to set name and scopes when creating a user with access token (#34080) (#34103)
|
||||||
|
* Do not show 500 error when default branch doesn't exist (#34096) (#34097)
|
||||||
|
* Hide activity contributors, recent commits and code frequrency left tabs if there is no code permission (#34053) (#34065)
|
||||||
|
* Simplify emoji rendering (#34048) (#34049)
|
||||||
|
* Adjust the layout of the toolbar on the Issues/Projects page (#33667) (#34047)
|
||||||
|
* Pull request updates will also trigger code owners review requests (#33744) (#34045)
|
||||||
|
* Fix org repo creation being limited by user limits (#34030) (#34044)
|
||||||
|
* Fix git client accessing renamed repo (#34034) (#34043)
|
||||||
|
* Fix the issue with error message logging for the `check-attr` command on Windows OS. (#34035) (#34036)
|
||||||
|
* Polyfill WeakRef (#34025) (#34028)
|
||||||
|
|
||||||
|
## [1.23.6](https://github.com/go-gitea/gitea/releases/tag/v1.23.6) - 2025-03-24
|
||||||
|
|
||||||
|
* SECURITY
|
||||||
|
* Fix LFS URL (#33840) (#33843)
|
||||||
|
* Update jwt and redis packages (#33984) (#33987)
|
||||||
|
* Update golang crypto and net (#33989)
|
||||||
|
* BUGFIXES
|
||||||
|
* Drop timeout for requests made to the internal hook api (#33947) (#33970)
|
||||||
|
* Fix maven panic when no package exists (#33888) (#33889)
|
||||||
|
* Fix markdown render (#33870) (#33875)
|
||||||
|
* Fix auto concurrency cancellation skips commit status updates (#33764) (#33849)
|
||||||
|
* Fix oauth2 auth (#33961) (#33962)
|
||||||
|
* Fix incorrect 1.23 translations (#33932)
|
||||||
|
* Try to figure out attribute checker problem (#33901) (#33902)
|
||||||
|
* Ignore trivial errors when updating push data (#33864) (#33887)
|
||||||
|
* Fix some UI problems for 1.23 (#33856)
|
||||||
|
* Removing unwanted ui container (#33833) (#33835)
|
||||||
|
* Support disable passkey auth (#33348) (#33819)
|
||||||
|
* Do not call "git diff" when listing PRs (#33817)
|
||||||
|
* Try to fix ACME (3rd) (#33807) (#33808)
|
||||||
|
* Fix incorrect code search indexer options (#33992) #33999
|
||||||
|
|
||||||
|
## [1.23.5](https://github.com/go-gitea/gitea/releases/tag/v1.23.5) - 2025-03-03
|
||||||
|
|
||||||
|
* SECURITY
|
||||||
|
* Bump x/oauth2 & x/crypto (#33704) (#33727)
|
||||||
|
* PERFORMANCE
|
||||||
|
* Optimize user dashboard loading (#33686) (#33708)
|
||||||
|
* BUGFIXES
|
||||||
|
* Fix navbar dropdown item align (#33782)
|
||||||
|
* Fix inconsistent closed issue list icon (#33722) (#33728)
|
||||||
|
* Fix for Maven Package Naming Convention Handling (#33678) (#33679)
|
||||||
|
* Improve Open-with URL encoding (#33666) (#33680)
|
||||||
|
* Deleting repository should unlink all related packages (#33653) (#33673)
|
||||||
|
* Fix omitempty bug (#33663) (#33670)
|
||||||
|
* Upgrade go-crypto from 1.1.4 to 1.1.6 (#33745) (#33754)
|
||||||
|
* Fix OCI image.version annotation for releases to use full semver (#33698) (#33701)
|
||||||
|
* Try to fix ACME path when renew (#33668) (#33693)
|
||||||
|
* Fix mCaptcha bug (#33659) (#33661)
|
||||||
|
* Git graph: don't show detached commits (#33645) (#33650)
|
||||||
|
* Use MatchPhraseQuery for bleve code search (#33628)
|
||||||
|
* Adjust appearence of commit status webhook (#33778) #33789
|
||||||
|
|
||||||
|
## [1.23.4](https://github.com/go-gitea/gitea/releases/tag/v1.23.4) - 2025-02-16
|
||||||
|
|
||||||
|
* SECURITY
|
||||||
|
* Enhance routers for the Actions variable operations (#33547) (#33553)
|
||||||
|
* Enhance routers for the Actions runner operations (#33549) (#33555)
|
||||||
|
* Fix project issues list and counting (#33594) #33619
|
||||||
|
* PERFORMANCES
|
||||||
|
* Performance optimization for pull request files loading comments attachments (#33585) (#33592)
|
||||||
|
* BUGFIXES
|
||||||
|
* Add a transaction to `pickTask` (#33543) (#33563)
|
||||||
|
* Fix mirror bug (#33597) (#33607)
|
||||||
|
* Use default Git timeout when checking repo health (#33593) (#33598)
|
||||||
|
* Fix PR's target branch dropdown (#33589) (#33591)
|
||||||
|
* Fix various problems (artifact order, api empty slice, assignee check, fuzzy prompt, mirror proxy, adopt git) (#33569) (#33577)
|
||||||
|
* Rework suggestion backend (#33538) (#33546)
|
||||||
|
* Fix context usage (#33554) (#33557)
|
||||||
|
* Only show the latest version in the Arch index (#33262) (#33580)
|
||||||
|
* Skip deletion error for action artifacts (#33476) (#33568)
|
||||||
|
* Make actions URL in commit status webhooks absolute (#33620) #33632
|
||||||
|
* Add missing locale (#33641) #33642
|
||||||
|
|
||||||
|
## [1.23.3](https://github.com/go-gitea/gitea/releases/tag/v1.23.3) - 2025-02-06
|
||||||
|
|
||||||
|
* Security
|
||||||
|
* Build Gitea with Golang v1.23.6 to fix security bugs
|
||||||
|
* BUGFIXES
|
||||||
|
* Fix a bug caused by status webhook template #33512
|
||||||
|
|
||||||
|
## [1.23.2](https://github.com/go-gitea/gitea/releases/tag/1.23.2) - 2025-02-04
|
||||||
|
|
||||||
|
* BREAKING
|
||||||
|
* Add tests for webhook and fix some webhook bugs (#33396) (#33442)
|
||||||
|
* Package webhook’s Organization was incorrectly used as the User struct. This PR fixes the issue.
|
||||||
|
* This changelog is just a hint. The change is not really breaking because most fields are the same, most users are not affected.
|
||||||
|
* ENHANCEMENTS
|
||||||
|
* Clone button enhancements (#33362) (#33404)
|
||||||
|
* Repo homepage styling tweaks (#33289) (#33381)
|
||||||
|
* Add a confirm dialog for "sync fork" (#33270) (#33273)
|
||||||
|
* Make tracked time representation display as hours (#33315) (#33334)
|
||||||
|
* Improve sync fork behavior (#33319) (#33332)
|
||||||
|
* BUGFIXES
|
||||||
|
* Fix code button alignment (#33345) (#33351)
|
||||||
|
* Correct bot label `vertical-align` (#33477) (#33480)
|
||||||
|
* Fix SSH LFS memory usage (#33455) (#33460)
|
||||||
|
* Fix issue sidebar dropdown keyboard support (#33447) (#33450)
|
||||||
|
* Fix user avatar (#33439)
|
||||||
|
* Fix `GetCommitBranchStart` bug (#33298) (#33421)
|
||||||
|
* Add pubdate for repository rss and add some tests (#33411) (#33416)
|
||||||
|
* Add missed auto merge feed message on dashboard (#33309) (#33405)
|
||||||
|
* Fix issue suggestion bug (#33389) (#33391)
|
||||||
|
* Make issue suggestion work for all editors (#33340) (#33342)
|
||||||
|
* Fix issue count (#33338) (#33341)
|
||||||
|
* Fix Account linking page (#33325) (#33327)
|
||||||
|
* Fix closed dependency title (#33285) (#33287)
|
||||||
|
* Fix sidebar milestone link (#33269) (#33272)
|
||||||
|
* Fix missing license when sync mirror (#33255) (#33258)
|
||||||
|
* Fix upload file form (#33230) (#33233)
|
||||||
|
* Fix mirror bug (#33224) (#33225)
|
||||||
|
* Fix system admin cannot fork or get private fork with API (#33401) (#33417)
|
||||||
|
* Fix push message behavior (#33215) (#33317)
|
||||||
|
* Trivial fixes (#33304) (#33312)
|
||||||
|
* Fix "stop time tracking button" on navbar (#33084) (#33300)
|
||||||
|
* Fix tag route and empty repo (#33253)
|
||||||
|
* Fix cache test triggered by non memory cache (#33220) (#33221)
|
||||||
|
* Revert empty lfs ref name (#33454) (#33457)
|
||||||
|
* Fix flex width (#33414) (#33418)
|
||||||
|
* Fix commit status events (#33320) #33493
|
||||||
|
* Fix unnecessary comment when moving issue on the same project column (#33496) #33499
|
||||||
|
* Add timetzdata build tag to binary releases (#33463) #33503
|
||||||
|
* MISC
|
||||||
|
* Use ProtonMail/go-crypto to replace keybase/go-crypto (#33402) (#33410)
|
||||||
|
* Update katex to latest version (#33361)
|
||||||
|
* Update go tool dependencies (#32916) (#33355)
|
||||||
|
|
||||||
|
## [1.23.1](https://github.com/go-gitea/gitea/releases/tag/v1.23.1) - 2025-01-09
|
||||||
|
|
||||||
|
* ENHANCEMENTS
|
||||||
|
* Move repo size to sidebar (#33155) (#33182)
|
||||||
|
* BUGFIXES
|
||||||
|
* Use updated path to s6-svscan after alpine upgrade (#33185) (#33188)
|
||||||
|
* Fix fuzz test (#33156) (#33158)
|
||||||
|
* Fix raw file API ref handling (#33172) (#33189)
|
||||||
|
* Fix ACME panic (#33178) (#33186)
|
||||||
|
* Fix branch dropdown not display ref name (#33159) (#33183)
|
||||||
|
* Fix assignee list overlapping in Issue sidebar (#33176) (#33181)
|
||||||
|
* Fix sync fork for consistency (#33147) #33192
|
||||||
|
* Fix editor markdown not incrementing in a numbered list (#33187) #33193
|
||||||
|
|
||||||
## [1.23.0](https://github.com/go-gitea/gitea/releases/tag/v1.23.0) - 2025-01-08
|
## [1.23.0](https://github.com/go-gitea/gitea/releases/tag/v1.23.0) - 2025-01-08
|
||||||
|
|
||||||
* BREAKING
|
* BREAKING
|
||||||
|
|||||||
12
Makefile
12
Makefile
@@ -26,17 +26,17 @@ COMMA := ,
|
|||||||
XGO_VERSION := go-1.23.x
|
XGO_VERSION := go-1.23.x
|
||||||
|
|
||||||
AIR_PACKAGE ?= github.com/air-verse/air@v1
|
AIR_PACKAGE ?= github.com/air-verse/air@v1
|
||||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0
|
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.0.3
|
||||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0
|
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0
|
||||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2
|
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2
|
||||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11
|
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.12
|
||||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.5.1
|
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.6.0
|
||||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0
|
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0
|
||||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
|
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
|
||||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
|
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
|
||||||
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1
|
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1
|
||||||
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.15.3
|
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.17.0
|
||||||
|
|
||||||
DOCKER_IMAGE ?= gitea/gitea
|
DOCKER_IMAGE ?= gitea/gitea
|
||||||
DOCKER_TAG ?= latest
|
DOCKER_TAG ?= latest
|
||||||
@@ -109,7 +109,7 @@ endif
|
|||||||
|
|
||||||
LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
|
LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
|
||||||
|
|
||||||
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
|
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/riscv64
|
||||||
|
|
||||||
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
||||||
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
|
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
|
||||||
@@ -508,7 +508,7 @@ unit-test-coverage:
|
|||||||
tidy:
|
tidy:
|
||||||
$(eval MIN_GO_VERSION := $(shell grep -Eo '^go\s+[0-9]+\.[0-9.]+' go.mod | cut -d' ' -f2))
|
$(eval MIN_GO_VERSION := $(shell grep -Eo '^go\s+[0-9]+\.[0-9.]+' go.mod | cut -d' ' -f2))
|
||||||
$(GO) mod tidy -compat=$(MIN_GO_VERSION)
|
$(GO) mod tidy -compat=$(MIN_GO_VERSION)
|
||||||
@$(MAKE) --no-print-directory $(GO_LICENSE_FILE)
|
$(MAKE) --no-print-directory $(GO_LICENSE_FILE)
|
||||||
|
|
||||||
vendor: go.mod go.sum
|
vendor: go.mod go.sum
|
||||||
$(GO) mod vendor
|
$(GO) mod vendor
|
||||||
|
|||||||
9
assets/go-licenses.json
generated
9
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
@@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
@@ -61,6 +62,16 @@ var microcmdUserCreate = &cli.Command{
|
|||||||
Name: "access-token",
|
Name: "access-token",
|
||||||
Usage: "Generate access token for the user",
|
Usage: "Generate access token for the user",
|
||||||
},
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "access-token-name",
|
||||||
|
Usage: `Name of the generated access token`,
|
||||||
|
Value: "gitea-admin",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "access-token-scopes",
|
||||||
|
Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`,
|
||||||
|
Value: "all",
|
||||||
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "restricted",
|
Name: "restricted",
|
||||||
Usage: "Make a restricted user account",
|
Usage: "Make a restricted user account",
|
||||||
@@ -162,23 +173,39 @@ func runCreateUser(c *cli.Context) error {
|
|||||||
IsRestricted: restricted,
|
IsRestricted: restricted,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var accessTokenName string
|
||||||
|
var accessTokenScope auth_model.AccessTokenScope
|
||||||
|
if c.IsSet("access-token") {
|
||||||
|
accessTokenName = strings.TrimSpace(c.String("access-token-name"))
|
||||||
|
if accessTokenName == "" {
|
||||||
|
return errors.New("access-token-name cannot be empty")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
accessTokenScope, err = auth_model.AccessTokenScope(c.String("access-token-scopes")).Normalize()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid access token scope provided: %w", err)
|
||||||
|
}
|
||||||
|
if !accessTokenScope.HasPermissionScope() {
|
||||||
|
return errors.New("access token does not have any permission")
|
||||||
|
}
|
||||||
|
} else if c.IsSet("access-token-name") || c.IsSet("access-token-scopes") {
|
||||||
|
return errors.New("access-token-name and access-token-scopes flags are only valid when access-token flag is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// arguments should be prepared before creating the user & access token, in case there is anything wrong
|
||||||
|
|
||||||
|
// create the user
|
||||||
if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil {
|
if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil {
|
||||||
return fmt.Errorf("CreateUser: %w", err)
|
return fmt.Errorf("CreateUser: %w", err)
|
||||||
}
|
}
|
||||||
|
// create the access token
|
||||||
if c.Bool("access-token") {
|
if accessTokenScope != "" {
|
||||||
t := &auth_model.AccessToken{
|
t := &auth_model.AccessToken{Name: accessTokenName, UID: u.ID, Scope: accessTokenScope}
|
||||||
Name: "gitea-admin",
|
|
||||||
UID: u.ID,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := auth_model.NewAccessToken(ctx, t); err != nil {
|
if err := auth_model.NewAccessToken(ctx, t); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Access token was successfully created... %s\n", t.Token)
|
fmt.Printf("Access token was successfully created... %s\n", t.Token)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("New user '%s' has been successfully created!\n", username)
|
fmt.Printf("New user '%s' has been successfully created!\n", username)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,37 +8,97 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAdminUserCreate(t *testing.T) {
|
func TestAdminUserCreate(t *testing.T) {
|
||||||
app := NewMainApp(AppVersion{})
|
app := NewMainApp(AppVersion{})
|
||||||
|
|
||||||
reset := func() {
|
reset := func() {
|
||||||
assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
|
require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
|
||||||
assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
|
require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
|
||||||
|
require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{}))
|
||||||
|
}
|
||||||
|
t.Run("MustChangePassword", func(t *testing.T) {
|
||||||
|
type check struct{ IsAdmin, MustChangePassword bool }
|
||||||
|
createCheck := func(name, args string) check {
|
||||||
|
assert.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args))))
|
||||||
|
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
|
||||||
|
return check{u.IsAdmin, u.MustChangePassword}
|
||||||
|
}
|
||||||
|
reset()
|
||||||
|
assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u", ""), "first non-admin user doesn't need to change password")
|
||||||
|
|
||||||
|
reset()
|
||||||
|
assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u", "--admin"), "first admin user doesn't need to change password")
|
||||||
|
|
||||||
|
reset()
|
||||||
|
assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u", "--admin --must-change-password"))
|
||||||
|
assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u2", "--admin"))
|
||||||
|
assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u3", "--admin --must-change-password=false"))
|
||||||
|
assert.Equal(t, check{IsAdmin: false, MustChangePassword: true}, createCheck("u4", ""))
|
||||||
|
assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u5", "--must-change-password=false"))
|
||||||
|
})
|
||||||
|
|
||||||
|
createUser := func(name, args string) error {
|
||||||
|
return app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s", name, name, args)))
|
||||||
}
|
}
|
||||||
|
|
||||||
type createCheck struct{ IsAdmin, MustChangePassword bool }
|
t.Run("AccessToken", func(t *testing.T) {
|
||||||
createUser := func(name, args string) createCheck {
|
// no generated access token
|
||||||
assert.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args))))
|
reset()
|
||||||
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
|
assert.NoError(t, createUser("u", "--random-password"))
|
||||||
return createCheck{u.IsAdmin, u.MustChangePassword}
|
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
|
||||||
}
|
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
reset()
|
|
||||||
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u", ""), "first non-admin user doesn't need to change password")
|
|
||||||
|
|
||||||
reset()
|
// using "--access-token" only means "all" access
|
||||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u", "--admin"), "first admin user doesn't need to change password")
|
reset()
|
||||||
|
assert.NoError(t, createUser("u", "--random-password --access-token"))
|
||||||
|
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
|
||||||
|
assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
|
accessToken := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"})
|
||||||
|
hasScopes, err := accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, hasScopes)
|
||||||
|
|
||||||
reset()
|
// using "--access-token" with name & scopes
|
||||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u", "--admin --must-change-password"))
|
reset()
|
||||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u2", "--admin"))
|
assert.NoError(t, createUser("u", "--random-password --access-token --access-token-name new-token-name --access-token-scopes read:issue,read:user"))
|
||||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u3", "--admin --must-change-password=false"))
|
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
|
||||||
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: true}, createUser("u4", ""))
|
assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u5", "--must-change-password=false"))
|
accessToken = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "new-token-name"})
|
||||||
|
hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadUser)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, hasScopes)
|
||||||
|
hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, hasScopes)
|
||||||
|
|
||||||
|
// using "--access-token-name" without "--access-token"
|
||||||
|
reset()
|
||||||
|
err = createUser("u", "--random-password --access-token-name new-token-name")
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
|
assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
|
||||||
|
|
||||||
|
// using "--access-token-scopes" without "--access-token"
|
||||||
|
reset()
|
||||||
|
err = createUser("u", "--random-password --access-token-scopes read:issue")
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
|
assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
|
||||||
|
|
||||||
|
// empty permission
|
||||||
|
reset()
|
||||||
|
err = createUser("u", "--random-password --access-token --access-token-scopes public-only")
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
|
assert.ErrorContains(t, err, "access token does not have any permission")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ var microcmdUserGenerateAccessToken = &cli.Command{
|
|||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "scopes",
|
Name: "scopes",
|
||||||
Value: "",
|
Value: "all",
|
||||||
Usage: "Comma separated list of scopes to apply to access token",
|
Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: runGenerateAccessToken,
|
Action: runGenerateAccessToken,
|
||||||
@@ -43,7 +43,7 @@ var microcmdUserGenerateAccessToken = &cli.Command{
|
|||||||
|
|
||||||
func runGenerateAccessToken(c *cli.Context) error {
|
func runGenerateAccessToken(c *cli.Context) error {
|
||||||
if !c.IsSet("username") {
|
if !c.IsSet("username") {
|
||||||
return errors.New("You must provide a username to generate a token for")
|
return errors.New("you must provide a username to generate a token for")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals()
|
||||||
@@ -77,6 +77,9 @@ func runGenerateAccessToken(c *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid access token scope provided: %w", err)
|
return fmt.Errorf("invalid access token scope provided: %w", err)
|
||||||
}
|
}
|
||||||
|
if !accessTokenScope.HasPermissionScope() {
|
||||||
|
return errors.New("access token does not have any permission")
|
||||||
|
}
|
||||||
t.Scope = accessTokenScope
|
t.Scope = accessTokenScope
|
||||||
|
|
||||||
// create the token
|
// create the token
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
var CmdMigrate = &cli.Command{
|
var CmdMigrate = &cli.Command{
|
||||||
Name: "migrate",
|
Name: "migrate",
|
||||||
Usage: "Migrate the database",
|
Usage: "Migrate the database",
|
||||||
Description: "This is a command for migrating the database, so that you can run gitea admin create-user before starting the server.",
|
Description: `This is a command for migrating the database, so that you can run "gitea admin create user" before starting the server.`,
|
||||||
Action: runMigrate,
|
Action: runMigrate,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/process"
|
"code.gitea.io/gitea/modules/process"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
)
|
)
|
||||||
@@ -54,8 +55,6 @@ func runACME(listenAddr string, m http.Handler) error {
|
|||||||
altTLSALPNPort = p
|
altTLSALPNPort = p
|
||||||
}
|
}
|
||||||
|
|
||||||
magic := &certmagic.Default
|
|
||||||
magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
|
|
||||||
// Try to use private CA root if provided, otherwise defaults to system's trust
|
// Try to use private CA root if provided, otherwise defaults to system's trust
|
||||||
var certPool *x509.CertPool
|
var certPool *x509.CertPool
|
||||||
if setting.AcmeCARoot != "" {
|
if setting.AcmeCARoot != "" {
|
||||||
@@ -65,8 +64,20 @@ func runACME(listenAddr string, m http.Handler) error {
|
|||||||
log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err)
|
log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
|
// FIXME: this path is not right, it uses "AppWorkPath" incorrectly, and writes the data into "AppWorkPath/https"
|
||||||
CA: setting.AcmeURL,
|
// Ideally it should migrate to AppDataPath write to "AppDataPath/https"
|
||||||
|
// And one more thing, no idea why we should set the global default variables here
|
||||||
|
// But it seems that the current ACME code needs these global variables to make renew work.
|
||||||
|
// Otherwise, "renew" will use incorrect storage path
|
||||||
|
oldDefaultACME := certmagic.DefaultACME
|
||||||
|
certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
|
||||||
|
certmagic.DefaultACME = certmagic.ACMEIssuer{
|
||||||
|
// try to use the default values provided by DefaultACME
|
||||||
|
CA: util.IfZero(setting.AcmeURL, oldDefaultACME.CA),
|
||||||
|
TestCA: oldDefaultACME.TestCA,
|
||||||
|
Logger: oldDefaultACME.Logger,
|
||||||
|
HTTPProxy: oldDefaultACME.HTTPProxy,
|
||||||
|
|
||||||
TrustedRoots: certPool,
|
TrustedRoots: certPool,
|
||||||
Email: setting.AcmeEmail,
|
Email: setting.AcmeEmail,
|
||||||
Agreed: setting.AcmeTOS,
|
Agreed: setting.AcmeTOS,
|
||||||
@@ -75,8 +86,10 @@ func runACME(listenAddr string, m http.Handler) error {
|
|||||||
ListenHost: setting.HTTPAddr,
|
ListenHost: setting.HTTPAddr,
|
||||||
AltTLSALPNPort: altTLSALPNPort,
|
AltTLSALPNPort: altTLSALPNPort,
|
||||||
AltHTTPPort: altHTTPPort,
|
AltHTTPPort: altHTTPPort,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
magic := certmagic.NewDefault()
|
||||||
|
myACME := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME)
|
||||||
magic.Issuers = []certmagic.Issuer{myACME}
|
magic.Issuers = []certmagic.Issuer{myACME}
|
||||||
|
|
||||||
// this obtains certificates or renews them if necessary
|
// this obtains certificates or renews them if necessary
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/google/go-github/v61/github"
|
"github.com/google/go-github/v71/github"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -774,6 +774,9 @@ LEVEL = Info
|
|||||||
;ALLOW_ONLY_EXTERNAL_REGISTRATION = false
|
;ALLOW_ONLY_EXTERNAL_REGISTRATION = false
|
||||||
;;
|
;;
|
||||||
;; User must sign in to view anything.
|
;; User must sign in to view anything.
|
||||||
|
;; After 1.23.7, it could be set to "expensive" to block anonymous users accessing some pages which consume a lot of resources,
|
||||||
|
;; for example: block anonymous AI crawlers from accessing repo code pages.
|
||||||
|
;; The "expensive" mode is experimental and subject to change.
|
||||||
;REQUIRE_SIGNIN_VIEW = false
|
;REQUIRE_SIGNIN_VIEW = false
|
||||||
;;
|
;;
|
||||||
;; Mail notification
|
;; Mail notification
|
||||||
@@ -784,10 +787,13 @@ LEVEL = Info
|
|||||||
;; Please note that setting this to false will not disable OAuth Basic or Basic authentication using a token
|
;; Please note that setting this to false will not disable OAuth Basic or Basic authentication using a token
|
||||||
;ENABLE_BASIC_AUTHENTICATION = true
|
;ENABLE_BASIC_AUTHENTICATION = true
|
||||||
;;
|
;;
|
||||||
;; Show the password sign-in form (for password-based login), otherwise, only show OAuth2 login methods.
|
;; Show the password sign-in form (for password-based login), otherwise, only show OAuth2 or passkey login methods if they are enabled.
|
||||||
;; If you set it to false, maybe it also needs to set ENABLE_BASIC_AUTHENTICATION to false to completely disable password-based authentication.
|
;; If you set it to false, maybe it also needs to set ENABLE_BASIC_AUTHENTICATION to false to completely disable password-based authentication.
|
||||||
;ENABLE_PASSWORD_SIGNIN_FORM = true
|
;ENABLE_PASSWORD_SIGNIN_FORM = true
|
||||||
;;
|
;;
|
||||||
|
;; Allow users to sign-in with a passkey
|
||||||
|
;ENABLE_PASSKEY_AUTHENTICATION = true
|
||||||
|
;;
|
||||||
;; More detail: https://github.com/gogits/gogs/issues/165
|
;; More detail: https://github.com/gogits/gogs/issues/165
|
||||||
;ENABLE_REVERSE_PROXY_AUTHENTICATION = false
|
;ENABLE_REVERSE_PROXY_AUTHENTICATION = false
|
||||||
; Enable this to allow reverse proxy authentication for API requests, the reverse proxy is responsible for ensuring that no CSRF is possible.
|
; Enable this to allow reverse proxy authentication for API requests, the reverse proxy is responsible for ensuring that no CSRF is possible.
|
||||||
|
|||||||
@@ -22,3 +22,8 @@ manifests:
|
|||||||
architecture: arm64
|
architecture: arm64
|
||||||
os: linux
|
os: linux
|
||||||
variant: v8
|
variant: v8
|
||||||
|
-
|
||||||
|
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}nightly{{/if}}-linux-riscv64-rootless
|
||||||
|
platform:
|
||||||
|
architecture: riscv64
|
||||||
|
os: linux
|
||||||
|
|||||||
@@ -22,3 +22,8 @@ manifests:
|
|||||||
architecture: arm64
|
architecture: arm64
|
||||||
os: linux
|
os: linux
|
||||||
variant: v8
|
variant: v8
|
||||||
|
-
|
||||||
|
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}nightly{{/if}}-linux-riscv64
|
||||||
|
platform:
|
||||||
|
architecture: riscv64
|
||||||
|
os: linux
|
||||||
|
|||||||
@@ -31,6 +31,21 @@ if [ -e /data/ssh/ssh_host_ecdsa_cert ]; then
|
|||||||
SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_cert"}
|
SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_cert"}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# In case someone wants to sign the `{keyname}.pub` key by `ssh-keygen -s ca -I identity ...` to
|
||||||
|
# make use of the ssh-key certificate authority feature (see ssh-keygen CERTIFICATES section),
|
||||||
|
# the generated key file name is `{keyname}-cert.pub`
|
||||||
|
if [ -e /data/ssh/ssh_host_ed25519_key-cert.pub ]; then
|
||||||
|
SSH_ED25519_CERT=${SSH_ED25519_CERT:-"/data/ssh/ssh_host_ed25519_key-cert.pub"}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e /data/ssh/ssh_host_rsa_key-cert.pub ]; then
|
||||||
|
SSH_RSA_CERT=${SSH_RSA_CERT:-"/data/ssh/ssh_host_rsa_key-cert.pub"}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e /data/ssh/ssh_host_ecdsa_key-cert.pub ]; then
|
||||||
|
SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_key-cert.pub"}
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -d /etc/ssh ]; then
|
if [ -d /etc/ssh ]; then
|
||||||
SSH_PORT=${SSH_PORT:-"22"} \
|
SSH_PORT=${SSH_PORT:-"22"} \
|
||||||
SSH_LISTEN_PORT=${SSH_LISTEN_PORT:-"${SSH_PORT}"} \
|
SSH_LISTEN_PORT=${SSH_LISTEN_PORT:-"${SSH_PORT}"} \
|
||||||
|
|||||||
@@ -37,5 +37,5 @@ done
|
|||||||
if [ $# -gt 0 ]; then
|
if [ $# -gt 0 ]; then
|
||||||
exec "$@"
|
exec "$@"
|
||||||
else
|
else
|
||||||
exec /bin/s6-svscan /etc/s6
|
exec /usr/bin/s6-svscan /etc/s6
|
||||||
fi
|
fi
|
||||||
|
|||||||
27
go.mod
27
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module code.gitea.io/gitea
|
module code.gitea.io/gitea
|
||||||
|
|
||||||
go 1.23
|
go 1.23.8
|
||||||
|
|
||||||
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
|
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
|
||||||
// But some CAs use negative serial number, just relax the check. related:
|
// But some CAs use negative serial number, just relax the check. related:
|
||||||
@@ -24,7 +24,7 @@ require (
|
|||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
|
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
||||||
github.com/ProtonMail/go-crypto v1.1.4
|
github.com/ProtonMail/go-crypto v1.1.6
|
||||||
github.com/PuerkitoBio/goquery v1.10.0
|
github.com/PuerkitoBio/goquery v1.10.0
|
||||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3
|
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3
|
||||||
github.com/alecthomas/chroma/v2 v2.15.0
|
github.com/alecthomas/chroma/v2 v2.15.0
|
||||||
@@ -64,8 +64,8 @@ require (
|
|||||||
github.com/gobwas/glob v0.2.3
|
github.com/gobwas/glob v0.2.3
|
||||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
||||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||||
github.com/google/go-github/v61 v61.0.0
|
github.com/google/go-github/v71 v71.0.0
|
||||||
github.com/google/licenseclassifier/v2 v2.0.0
|
github.com/google/licenseclassifier/v2 v2.0.0
|
||||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db
|
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
@@ -79,7 +79,6 @@ require (
|
|||||||
github.com/jhillyerd/enmime v1.3.0
|
github.com/jhillyerd/enmime v1.3.0
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||||
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4
|
|
||||||
github.com/klauspost/compress v1.17.11
|
github.com/klauspost/compress v1.17.11
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8
|
github.com/klauspost/cpuid/v2 v2.2.8
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
@@ -101,7 +100,7 @@ require (
|
|||||||
github.com/pquerna/otp v1.4.0
|
github.com/pquerna/otp v1.4.0
|
||||||
github.com/prometheus/client_golang v1.20.5
|
github.com/prometheus/client_golang v1.20.5
|
||||||
github.com/quasoft/websspi v1.1.2
|
github.com/quasoft/websspi v1.1.2
|
||||||
github.com/redis/go-redis/v9 v9.7.0
|
github.com/redis/go-redis/v9 v9.7.3
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
|
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
|
||||||
github.com/sassoftware/go-rpmutils v0.4.0
|
github.com/sassoftware/go-rpmutils v0.4.0
|
||||||
@@ -119,13 +118,13 @@ require (
|
|||||||
github.com/yuin/goldmark v1.7.8
|
github.com/yuin/goldmark v1.7.8
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||||
github.com/yuin/goldmark-meta v1.1.0
|
github.com/yuin/goldmark-meta v1.1.0
|
||||||
golang.org/x/crypto v0.32.0
|
golang.org/x/crypto v0.36.0
|
||||||
golang.org/x/image v0.21.0
|
golang.org/x/image v0.21.0
|
||||||
golang.org/x/net v0.34.0
|
golang.org/x/net v0.38.0
|
||||||
golang.org/x/oauth2 v0.23.0
|
golang.org/x/oauth2 v0.27.0
|
||||||
golang.org/x/sync v0.10.0
|
golang.org/x/sync v0.12.0
|
||||||
golang.org/x/sys v0.29.0
|
golang.org/x/sys v0.31.0
|
||||||
golang.org/x/text v0.21.0
|
golang.org/x/text v0.23.0
|
||||||
golang.org/x/tools v0.29.0
|
golang.org/x/tools v0.29.0
|
||||||
google.golang.org/grpc v1.67.1
|
google.golang.org/grpc v1.67.1
|
||||||
google.golang.org/protobuf v1.35.1
|
google.golang.org/protobuf v1.35.1
|
||||||
@@ -216,7 +215,7 @@ require (
|
|||||||
github.com/go-openapi/validate v0.24.0 // indirect
|
github.com/go-openapi/validate v0.24.0 // indirect
|
||||||
github.com/go-webauthn/x v0.1.15 // indirect
|
github.com/go-webauthn/x v0.1.15 // indirect
|
||||||
github.com/goccy/go-json v0.10.3 // indirect
|
github.com/goccy/go-json v0.10.3 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.2 // 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/geo v0.0.0-20230421003525-6adc56603217 // indirect
|
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
|
||||||
@@ -326,6 +325,8 @@ replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-tra
|
|||||||
// TODO: This could be removed after https://github.com/mholt/archiver/pull/396 merged
|
// TODO: This could be removed after https://github.com/mholt/archiver/pull/396 merged
|
||||||
replace github.com/mholt/archiver/v3 => github.com/anchore/archiver/v3 v3.5.2
|
replace github.com/mholt/archiver/v3 => github.com/anchore/archiver/v3 v3.5.2
|
||||||
|
|
||||||
|
replace git.sr.ht/~mariusor/go-xsd-duration => gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078
|
||||||
|
|
||||||
exclude github.com/gofrs/uuid v3.2.0+incompatible
|
exclude github.com/gofrs/uuid v3.2.0+incompatible
|
||||||
|
|
||||||
exclude github.com/gofrs/uuid v4.0.0+incompatible
|
exclude github.com/gofrs/uuid v4.0.0+incompatible
|
||||||
|
|||||||
56
go.sum
56
go.sum
@@ -14,12 +14,12 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
|||||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg=
|
|
||||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
|
|
||||||
gitea.com/gitea/act v0.261.3 h1:BhiYpGJQKGq0XMYYICCYAN4KnsEWHyLbA6dxhZwFcV4=
|
gitea.com/gitea/act v0.261.3 h1:BhiYpGJQKGq0XMYYICCYAN4KnsEWHyLbA6dxhZwFcV4=
|
||||||
gitea.com/gitea/act v0.261.3/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
|
gitea.com/gitea/act v0.261.3/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
|
||||||
gitea.com/gitea/git-lfs-transfer v0.2.0 h1:baHaNoBSRaeq/xKayEXwiDQtlIjps4Ac/Ll4KqLMB40=
|
gitea.com/gitea/git-lfs-transfer v0.2.0 h1:baHaNoBSRaeq/xKayEXwiDQtlIjps4Ac/Ll4KqLMB40=
|
||||||
gitea.com/gitea/git-lfs-transfer v0.2.0/go.mod h1:UrXUCm3xLQkq15fu7qlXHUMlrhdlXHoi13KH2Dfiits=
|
gitea.com/gitea/git-lfs-transfer v0.2.0/go.mod h1:UrXUCm3xLQkq15fu7qlXHUMlrhdlXHoi13KH2Dfiits=
|
||||||
|
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:BAFmdZpRW7zMQZQDClaCWobRj9uL1MR3MzpCVJvc5s4=
|
||||||
|
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
|
||||||
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso=
|
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso=
|
||||||
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw=
|
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw=
|
||||||
gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g=
|
gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g=
|
||||||
@@ -71,8 +71,8 @@ github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSC
|
|||||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
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 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/ProtonMail/go-crypto v1.1.4 h1:G5U5asvD5N/6/36oIw3k2bOfBn5XVcZrb7PBjzzKKoE=
|
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
|
||||||
github.com/ProtonMail/go-crypto v1.1.4/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||||
github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
|
github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
|
||||||
github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
|
github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
|
||||||
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
|
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
|
||||||
@@ -373,10 +373,11 @@ github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7w
|
|||||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0=
|
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0=
|
||||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
|
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||||
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=
|
||||||
@@ -409,10 +410,11 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
|||||||
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=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-github/v61 v61.0.0 h1:VwQCBwhyE9JclCI+22/7mLB1PuU9eowCXKY5pNlu1go=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-github/v61 v61.0.0/go.mod h1:0WR+KmsWX75G2EbpyGsGmradjo3IiciuI4BmdVCobQY=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/go-github/v71 v71.0.0 h1:Zi16OymGKZZMm8ZliffVVJ/Q9YZreDKONCr+WUd0Z30=
|
||||||
|
github.com/google/go-github/v71 v71.0.0/go.mod h1:URZXObp2BLlMjwu0O8g4y6VBneUj2bCHgnI8FfgZ51M=
|
||||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
github.com/google/go-tpm v0.9.1 h1:0pGc4X//bAlmZzMKf8iz6IsDo1nYTbYJ6FZN/rg4zdM=
|
github.com/google/go-tpm v0.9.1 h1:0pGc4X//bAlmZzMKf8iz6IsDo1nYTbYJ6FZN/rg4zdM=
|
||||||
@@ -510,8 +512,6 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU
|
|||||||
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.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 h1:cTxwSmnaqLoo+4tLukHoB9iqHOu3LmLhRmgUxZo6Vp4=
|
|
||||||
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M=
|
|
||||||
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.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
@@ -660,8 +660,8 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG
|
|||||||
github.com/quasoft/websspi v1.1.2 h1:/mA4w0LxWlE3novvsoEL6BBA1WnjJATbjkh1kFrTidw=
|
github.com/quasoft/websspi v1.1.2 h1:/mA4w0LxWlE3novvsoEL6BBA1WnjJATbjkh1kFrTidw=
|
||||||
github.com/quasoft/websspi v1.1.2/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk=
|
github.com/quasoft/websspi v1.1.2/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk=
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
|
||||||
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
|
||||||
github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo=
|
github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo=
|
||||||
github.com/redis/rueidis v1.0.19/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo=
|
github.com/redis/rueidis v1.0.19/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||||
@@ -835,8 +835,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
|
|||||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
||||||
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
|
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
|
||||||
@@ -871,10 +871,10 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
|||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||||
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=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -886,8 +886,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
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=
|
||||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -921,8 +921,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|||||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.18.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.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
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=
|
||||||
@@ -934,8 +934,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
|||||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
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=
|
||||||
@@ -946,8 +946,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|||||||
16
main_timezones.go
Normal file
16
main_timezones.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// Golang has the ability to load OS's timezone data from most UNIX systems (https://github.com/golang/go/blob/master/src/time/zoneinfo_unix.go)
|
||||||
|
// Even if the timezone data is missing, users could install the related packages to get it.
|
||||||
|
// But on Windows, although `zoneinfo_windows.go` tries to load the timezone data from Windows registry,
|
||||||
|
// some users still suffer from the issue that the timezone data is missing: https://github.com/go-gitea/gitea/issues/33235
|
||||||
|
// So we import the tzdata package to make sure the timezone data is included in the binary.
|
||||||
|
//
|
||||||
|
// For non-Windows package builders, they could still use the "TAGS=timetzdata" to include the tzdata package in the binary.
|
||||||
|
// If we decided to add the tzdata for other platforms, modify the "go:build" directive above.
|
||||||
|
import _ "time/tzdata"
|
||||||
@@ -114,6 +114,12 @@ type FindArtifactsOptions struct {
|
|||||||
Status int
|
Status int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opts FindArtifactsOptions) ToOrders() string {
|
||||||
|
return "id"
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ db.FindOptionsOrder = (*FindArtifactsOptions)(nil)
|
||||||
|
|
||||||
func (opts FindArtifactsOptions) ToConds() builder.Cond {
|
func (opts FindArtifactsOptions) ToConds() builder.Cond {
|
||||||
cond := builder.NewCond()
|
cond := builder.NewCond()
|
||||||
if opts.RepoID > 0 {
|
if opts.RepoID > 0 {
|
||||||
@@ -132,7 +138,7 @@ func (opts FindArtifactsOptions) ToConds() builder.Cond {
|
|||||||
return cond
|
return cond
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActionArtifactMeta is the meta data of an artifact
|
// ActionArtifactMeta is the meta-data of an artifact
|
||||||
type ActionArtifactMeta struct {
|
type ActionArtifactMeta struct {
|
||||||
ArtifactName string
|
ArtifactName string
|
||||||
FileSize int64
|
FileSize int64
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) err
|
|||||||
|
|
||||||
// CancelPreviousJobs cancels all previous jobs of the same repository, reference, workflow, and event.
|
// CancelPreviousJobs cancels all previous jobs of the same repository, reference, workflow, and event.
|
||||||
// It's useful when a new run is triggered, and all previous runs needn't be continued anymore.
|
// It's useful when a new run is triggered, and all previous runs needn't be continued anymore.
|
||||||
func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) error {
|
func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) ([]*ActionRunJob, error) {
|
||||||
// Find all runs in the specified repository, reference, and workflow with non-final status
|
// Find all runs in the specified repository, reference, and workflow with non-final status
|
||||||
runs, total, err := db.FindAndCount[ActionRun](ctx, FindRunOptions{
|
runs, total, err := db.FindAndCount[ActionRun](ctx, FindRunOptions{
|
||||||
RepoID: repoID,
|
RepoID: repoID,
|
||||||
@@ -204,14 +204,16 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
|
|||||||
Status: []Status{StatusRunning, StatusWaiting, StatusBlocked},
|
Status: []Status{StatusRunning, StatusWaiting, StatusBlocked},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are no runs found, there's no need to proceed with cancellation, so return nil.
|
// If there are no runs found, there's no need to proceed with cancellation, so return nil.
|
||||||
if total == 0 {
|
if total == 0 {
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancelledJobs := make([]*ActionRunJob, 0, total)
|
||||||
|
|
||||||
// Iterate over each found run and cancel its associated jobs.
|
// Iterate over each found run and cancel its associated jobs.
|
||||||
for _, run := range runs {
|
for _, run := range runs {
|
||||||
// Find all jobs associated with the current run.
|
// Find all jobs associated with the current run.
|
||||||
@@ -219,7 +221,7 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
|
|||||||
RunID: run.ID,
|
RunID: run.ID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return cancelledJobs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate over each job and attempt to cancel it.
|
// Iterate over each job and attempt to cancel it.
|
||||||
@@ -238,27 +240,29 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
|
|||||||
// Update the job's status and stopped time in the database.
|
// Update the job's status and stopped time in the database.
|
||||||
n, err := UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}, "status", "stopped")
|
n, err := UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}, "status", "stopped")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return cancelledJobs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the update affected 0 rows, it means the job has changed in the meantime, so we need to try again.
|
// If the update affected 0 rows, it means the job has changed in the meantime, so we need to try again.
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return fmt.Errorf("job has changed, try again")
|
return cancelledJobs, fmt.Errorf("job has changed, try again")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancelledJobs = append(cancelledJobs, job)
|
||||||
// Continue with the next job.
|
// Continue with the next job.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the job has an associated task, try to stop the task, effectively cancelling the job.
|
// If the job has an associated task, try to stop the task, effectively cancelling the job.
|
||||||
if err := StopTask(ctx, job.TaskID, StatusCancelled); err != nil {
|
if err := StopTask(ctx, job.TaskID, StatusCancelled); err != nil {
|
||||||
return err
|
return cancelledJobs, err
|
||||||
}
|
}
|
||||||
|
cancelledJobs = append(cancelledJobs, job)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return nil to indicate successful cancellation of all running and waiting jobs.
|
// Return nil to indicate successful cancellation of all running and waiting jobs.
|
||||||
return nil
|
return cancelledJobs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InsertRun inserts a run
|
// InsertRun inserts a run
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ func init() {
|
|||||||
|
|
||||||
type FindRunnerOptions struct {
|
type FindRunnerOptions struct {
|
||||||
db.ListOptions
|
db.ListOptions
|
||||||
|
IDs []int64
|
||||||
RepoID int64
|
RepoID int64
|
||||||
OwnerID int64 // it will be ignored if RepoID is set
|
OwnerID int64 // it will be ignored if RepoID is set
|
||||||
Sort string
|
Sort string
|
||||||
@@ -178,6 +179,14 @@ type FindRunnerOptions struct {
|
|||||||
func (opts FindRunnerOptions) ToConds() builder.Cond {
|
func (opts FindRunnerOptions) ToConds() builder.Cond {
|
||||||
cond := builder.NewCond()
|
cond := builder.NewCond()
|
||||||
|
|
||||||
|
if len(opts.IDs) > 0 {
|
||||||
|
if len(opts.IDs) == 1 {
|
||||||
|
cond = cond.And(builder.Eq{"id": opts.IDs[0]})
|
||||||
|
} else {
|
||||||
|
cond = cond.And(builder.In("id", opts.IDs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if opts.RepoID > 0 {
|
if opts.RepoID > 0 {
|
||||||
c := builder.NewCond().And(builder.Eq{"repo_id": opts.RepoID})
|
c := builder.NewCond().And(builder.Eq{"repo_id": opts.RepoID})
|
||||||
if opts.WithAvailable {
|
if opts.WithAvailable {
|
||||||
|
|||||||
@@ -120,21 +120,22 @@ func DeleteScheduleTaskByRepo(ctx context.Context, id int64) error {
|
|||||||
return committer.Commit()
|
return committer.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) error {
|
func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) ([]*ActionRunJob, error) {
|
||||||
// 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 fmt.Errorf("DeleteCronTaskByRepo: %v", err)
|
return nil, fmt.Errorf("DeleteCronTaskByRepo: %v", err)
|
||||||
}
|
}
|
||||||
// cancel running cron jobs of this repository and delete old schedules
|
// cancel running cron jobs of this repository and delete old schedules
|
||||||
if err := CancelPreviousJobs(
|
jobs, err := CancelPreviousJobs(
|
||||||
ctx,
|
ctx,
|
||||||
repo.ID,
|
repo.ID,
|
||||||
repo.DefaultBranch,
|
repo.DefaultBranch,
|
||||||
"",
|
"",
|
||||||
webhook_module.HookEventSchedule,
|
webhook_module.HookEventSchedule,
|
||||||
); err != nil {
|
)
|
||||||
return fmt.Errorf("CancelPreviousJobs: %v", err)
|
if err != nil {
|
||||||
|
return jobs, fmt.Errorf("CancelPreviousJobs: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return jobs, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ func InsertVariable(ctx context.Context, ownerID, repoID int64, name, data strin
|
|||||||
|
|
||||||
type FindVariablesOpts struct {
|
type FindVariablesOpts struct {
|
||||||
db.ListOptions
|
db.ListOptions
|
||||||
|
IDs []int64
|
||||||
RepoID int64
|
RepoID int64
|
||||||
OwnerID int64 // it will be ignored if RepoID is set
|
OwnerID int64 // it will be ignored if RepoID is set
|
||||||
Name string
|
Name string
|
||||||
@@ -65,6 +66,15 @@ type FindVariablesOpts struct {
|
|||||||
|
|
||||||
func (opts FindVariablesOpts) ToConds() builder.Cond {
|
func (opts FindVariablesOpts) ToConds() builder.Cond {
|
||||||
cond := builder.NewCond()
|
cond := builder.NewCond()
|
||||||
|
|
||||||
|
if len(opts.IDs) > 0 {
|
||||||
|
if len(opts.IDs) == 1 {
|
||||||
|
cond = cond.And(builder.Eq{"id": opts.IDs[0]})
|
||||||
|
} else {
|
||||||
|
cond = cond.And(builder.In("id", opts.IDs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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})
|
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||||
@@ -85,12 +95,12 @@ func FindVariables(ctx context.Context, opts FindVariablesOpts) ([]*ActionVariab
|
|||||||
return db.Find[ActionVariable](ctx, opts)
|
return db.Find[ActionVariable](ctx, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) {
|
func UpdateVariableCols(ctx context.Context, variable *ActionVariable, cols ...string) (bool, error) {
|
||||||
count, err := db.GetEngine(ctx).ID(variable.ID).Cols("name", "data").
|
variable.Name = strings.ToUpper(variable.Name)
|
||||||
Update(&ActionVariable{
|
count, err := db.GetEngine(ctx).
|
||||||
Name: variable.Name,
|
ID(variable.ID).
|
||||||
Data: variable.Data,
|
Cols(cols...).
|
||||||
})
|
Update(variable)
|
||||||
return count != 0, err
|
return count != 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,9 +72,9 @@ func (at ActionType) String() string {
|
|||||||
case ActionRenameRepo:
|
case ActionRenameRepo:
|
||||||
return "rename_repo"
|
return "rename_repo"
|
||||||
case ActionStarRepo:
|
case ActionStarRepo:
|
||||||
return "star_repo"
|
return "star_repo" // will not displayed in feeds.tmpl
|
||||||
case ActionWatchRepo:
|
case ActionWatchRepo:
|
||||||
return "watch_repo"
|
return "watch_repo" // will not displayed in feeds.tmpl
|
||||||
case ActionCommitRepo:
|
case ActionCommitRepo:
|
||||||
return "commit_repo"
|
return "commit_repo"
|
||||||
case ActionCreateIssue:
|
case ActionCreateIssue:
|
||||||
@@ -454,6 +454,24 @@ func ActivityReadable(user, doer *user_model.User) bool {
|
|||||||
doer != nil && (doer.IsAdmin || user.ID == doer.ID)
|
doer != nil && (doer.IsAdmin || user.ID == doer.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FeedDateCond(opts GetFeedsOptions) builder.Cond {
|
||||||
|
cond := builder.NewCond()
|
||||||
|
if opts.Date == "" {
|
||||||
|
return cond
|
||||||
|
}
|
||||||
|
|
||||||
|
dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err)
|
||||||
|
} else {
|
||||||
|
dateHigh := dateLow.Add(86399000000000) // 23h59m59s
|
||||||
|
|
||||||
|
cond = cond.And(builder.Gte{"`action`.created_unix": dateLow.Unix()})
|
||||||
|
cond = cond.And(builder.Lte{"`action`.created_unix": dateHigh.Unix()})
|
||||||
|
}
|
||||||
|
return cond
|
||||||
|
}
|
||||||
|
|
||||||
func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.Cond, error) {
|
func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.Cond, error) {
|
||||||
cond := builder.NewCond()
|
cond := builder.NewCond()
|
||||||
|
|
||||||
@@ -534,17 +552,7 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.
|
|||||||
cond = cond.And(builder.Eq{"is_deleted": false})
|
cond = cond.And(builder.Eq{"is_deleted": false})
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Date != "" {
|
cond = cond.And(FeedDateCond(opts))
|
||||||
dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err)
|
|
||||||
} else {
|
|
||||||
dateHigh := dateLow.Add(86399000000000) // 23h59m59s
|
|
||||||
|
|
||||||
cond = cond.And(builder.Gte{"`action`.created_unix": dateLow.Unix()})
|
|
||||||
cond = cond.And(builder.Lte{"`action`.created_unix": dateHigh.Unix()})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cond, nil
|
return cond, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,9 +208,31 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
|
|||||||
return nil, 0, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo")
|
return nil, 0, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo")
|
||||||
}
|
}
|
||||||
|
|
||||||
cond, err := ActivityQueryCondition(ctx, opts)
|
var err error
|
||||||
if err != nil {
|
var cond builder.Cond
|
||||||
return nil, 0, err
|
// if the actor is the requested user or is an administrator, we can skip the ActivityQueryCondition
|
||||||
|
if opts.Actor != nil && opts.RequestedUser != nil && (opts.Actor.IsAdmin || opts.Actor.ID == opts.RequestedUser.ID) {
|
||||||
|
cond = builder.Eq{
|
||||||
|
"user_id": opts.RequestedUser.ID,
|
||||||
|
}.And(
|
||||||
|
FeedDateCond(opts),
|
||||||
|
)
|
||||||
|
|
||||||
|
if !opts.IncludeDeleted {
|
||||||
|
cond = cond.And(builder.Eq{"is_deleted": false})
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.IncludePrivate {
|
||||||
|
cond = cond.And(builder.Eq{"is_private": false})
|
||||||
|
}
|
||||||
|
if opts.OnlyPerformedBy {
|
||||||
|
cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cond, err = ActivityQueryCondition(ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actions := make([]*Action, 0, opts.PageSize)
|
actions := make([]*Action, 0, opts.PageSize)
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func init() {
|
|||||||
// TranslatableMessage represents JSON struct that can be translated with a Locale
|
// TranslatableMessage represents JSON struct that can be translated with a Locale
|
||||||
type TranslatableMessage struct {
|
type TranslatableMessage struct {
|
||||||
Format string
|
Format string
|
||||||
Args []any `json:"omitempty"`
|
Args []any `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadRepo loads repository of the task
|
// LoadRepo loads repository of the task
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import (
|
|||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
"github.com/keybase/go-crypto/openpgp"
|
"github.com/ProtonMail/go-crypto/openpgp"
|
||||||
"github.com/keybase/go-crypto/openpgp/packet"
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -141,7 +141,11 @@ func parseGPGKey(ctx context.Context, ownerID int64, e *openpgp.Entity, verified
|
|||||||
// Parse Subkeys
|
// Parse Subkeys
|
||||||
subkeys := make([]*GPGKey, len(e.Subkeys))
|
subkeys := make([]*GPGKey, len(e.Subkeys))
|
||||||
for i, k := range e.Subkeys {
|
for i, k := range e.Subkeys {
|
||||||
subs, err := parseSubGPGKey(ownerID, pubkey.KeyIdString(), k.PublicKey, expiry)
|
subkeyExpiry := expiry
|
||||||
|
if k.Sig.KeyLifetimeSecs != nil {
|
||||||
|
subkeyExpiry = k.PublicKey.CreationTime.Add(time.Duration(*k.Sig.KeyLifetimeSecs) * time.Second)
|
||||||
|
}
|
||||||
|
subs, err := parseSubGPGKey(ownerID, pubkey.KeyIdString(), k.PublicKey, subkeyExpiry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrGPGKeyParsing{ParseError: err}
|
return nil, ErrGPGKeyParsing{ParseError: err}
|
||||||
}
|
}
|
||||||
@@ -156,7 +160,7 @@ func parseGPGKey(ctx context.Context, ownerID int64, e *openpgp.Entity, verified
|
|||||||
|
|
||||||
emails := make([]*user_model.EmailAddress, 0, len(e.Identities))
|
emails := make([]*user_model.EmailAddress, 0, len(e.Identities))
|
||||||
for _, ident := range e.Identities {
|
for _, ident := range e.Identities {
|
||||||
if ident.Revocation != nil {
|
if ident.Revoked(time.Now()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
email := strings.ToLower(strings.TrimSpace(ident.UserId.Email))
|
email := strings.ToLower(strings.TrimSpace(ident.UserId.Email))
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
|
||||||
"github.com/keybase/go-crypto/openpgp"
|
"github.com/ProtonMail/go-crypto/openpgp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// __________________ ________ ____ __.
|
// __________________ ________ ____ __.
|
||||||
@@ -83,12 +83,12 @@ func AddGPGKey(ctx context.Context, ownerID int64, content, token, signature str
|
|||||||
verified := false
|
verified := false
|
||||||
// Handle provided signature
|
// Handle provided signature
|
||||||
if signature != "" {
|
if signature != "" {
|
||||||
signer, err := openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token), strings.NewReader(signature))
|
signer, err := openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token), strings.NewReader(signature), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\n"), strings.NewReader(signature))
|
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\n"), strings.NewReader(signature), nil)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\r\n"), strings.NewReader(signature))
|
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\r\n"), strings.NewReader(signature), nil)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to validate token signature. Error: %v", err)
|
log.Error("Unable to validate token signature. Error: %v", err)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/keybase/go-crypto/openpgp/packet"
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
// __________________ ________ ____ __.
|
// __________________ ________ ____ __.
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/keybase/go-crypto/openpgp"
|
"github.com/ProtonMail/go-crypto/openpgp"
|
||||||
"github.com/keybase/go-crypto/openpgp/armor"
|
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||||
"github.com/keybase/go-crypto/openpgp/packet"
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
// __________________ ________ ____ __.
|
// __________________ ________ ____ __.
|
||||||
@@ -80,7 +80,7 @@ func base64DecPubKey(content string) (*packet.PublicKey, error) {
|
|||||||
return pkey, nil
|
return pkey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getExpiryTime extract the expire time of primary key based on sig
|
// getExpiryTime extract the expiry time of primary key based on sig
|
||||||
func getExpiryTime(e *openpgp.Entity) time.Time {
|
func getExpiryTime(e *openpgp.Entity) time.Time {
|
||||||
expiry := time.Time{}
|
expiry := time.Time{}
|
||||||
// Extract self-sign for expire date based on : https://github.com/golang/crypto/blob/master/openpgp/keys.go#L165
|
// Extract self-sign for expire date based on : https://github.com/golang/crypto/blob/master/openpgp/keys.go#L165
|
||||||
@@ -88,12 +88,12 @@ func getExpiryTime(e *openpgp.Entity) time.Time {
|
|||||||
for _, ident := range e.Identities {
|
for _, ident := range e.Identities {
|
||||||
if selfSig == nil {
|
if selfSig == nil {
|
||||||
selfSig = ident.SelfSignature
|
selfSig = ident.SelfSignature
|
||||||
} else if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
|
} else if ident.SelfSignature != nil && ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
|
||||||
selfSig = ident.SelfSignature
|
selfSig = ident.SelfSignature
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if selfSig.KeyLifetimeSecs != nil {
|
if selfSig != nil && selfSig.KeyLifetimeSecs != nil {
|
||||||
expiry = e.PrimaryKey.CreationTime.Add(time.Duration(*selfSig.KeyLifetimeSecs) * time.Second)
|
expiry = e.PrimaryKey.CreationTime.Add(time.Duration(*selfSig.KeyLifetimeSecs) * time.Second)
|
||||||
}
|
}
|
||||||
return expiry
|
return expiry
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/keybase/go-crypto/openpgp/packet"
|
"github.com/ProtonMail/go-crypto/openpgp"
|
||||||
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -403,3 +404,25 @@ func TestTryGetKeyIDFromSignature(t *testing.T) {
|
|||||||
IssuerFingerprint: []uint8{0xb, 0x23, 0x24, 0xc7, 0xe6, 0xfe, 0x4f, 0x3a, 0x6, 0x26, 0xc1, 0x21, 0x3, 0x8d, 0x1a, 0x3e, 0xad, 0xdb, 0xea, 0x9c},
|
IssuerFingerprint: []uint8{0xb, 0x23, 0x24, 0xc7, 0xe6, 0xfe, 0x4f, 0x3a, 0x6, 0x26, 0xc1, 0x21, 0x3, 0x8d, 0x1a, 0x3e, 0xad, 0xdb, 0xea, 0x9c},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseGPGKey(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
assert.NoError(t, db.Insert(db.DefaultContext, &user_model.EmailAddress{UID: 1, Email: "email1@example.com", IsActivated: true}))
|
||||||
|
|
||||||
|
// create a key for test email
|
||||||
|
e, err := openpgp.NewEntity("name", "comment", "email1@example.com", nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
k, err := parseGPGKey(db.DefaultContext, 1, e, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, k.KeyID)
|
||||||
|
assert.NotEmpty(t, k.Emails) // the key is valid, matches the email
|
||||||
|
|
||||||
|
// then revoke the key
|
||||||
|
for _, id := range e.Identities {
|
||||||
|
id.Revocations = append(id.Revocations, &packet.Signature{RevocationReason: util.ToPointer(packet.KeyCompromised)})
|
||||||
|
}
|
||||||
|
k, err = parseGPGKey(db.DefaultContext, 1, e, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, k.KeyID)
|
||||||
|
assert.Empty(t, k.Emails) // the key is revoked, matches no email
|
||||||
|
}
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ func (s AccessTokenScope) Normalize() (AccessTokenScope, error) {
|
|||||||
return bitmap.toScope(), nil
|
return bitmap.toScope(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s AccessTokenScope) HasPermissionScope() bool {
|
||||||
|
return s != "" && s != AccessTokenScopePublicOnly
|
||||||
|
}
|
||||||
|
|
||||||
// PublicOnly checks if this token scope is limited to public resources
|
// PublicOnly checks if this token scope is limited to public resources
|
||||||
func (s AccessTokenScope) PublicOnly() (bool, error) {
|
func (s AccessTokenScope) PublicOnly() (bool, error) {
|
||||||
bitmap, err := s.parse()
|
bitmap, err := s.parse()
|
||||||
|
|||||||
@@ -167,9 +167,24 @@ func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, e
|
|||||||
BranchName: branchName,
|
BranchName: branchName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// FIXME: this design is not right: it doesn't check `branch.IsDeleted`, it doesn't make sense to make callers to check IsDeleted again and again.
|
||||||
|
// It causes inconsistency with `GetBranches` and `git.GetBranch`, and will lead to strange bugs
|
||||||
|
// In the future, there should be 2 functions: `GetBranchExisting` and `GetBranchWithDeleted`
|
||||||
return &branch, nil
|
return &branch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsBranchExist returns true if the branch exists in the repository.
|
||||||
|
func IsBranchExist(ctx context.Context, repoID int64, branchName string) (bool, error) {
|
||||||
|
var branch Branch
|
||||||
|
has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).And("name=?", branchName).Get(&branch)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
} else if !has {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return !branch.IsDeleted, nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetBranches(ctx context.Context, repoID int64, branchNames []string, includeDeleted bool) ([]*Branch, error) {
|
func GetBranches(ctx context.Context, repoID int64, branchNames []string, includeDeleted bool) ([]*Branch, error) {
|
||||||
branches := make([]*Branch, 0, len(branchNames))
|
branches := make([]*Branch, 0, len(branchNames))
|
||||||
|
|
||||||
@@ -440,6 +455,8 @@ type FindRecentlyPushedNewBranchesOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RecentlyPushedNewBranch struct {
|
type RecentlyPushedNewBranch struct {
|
||||||
|
BranchRepo *repo_model.Repository
|
||||||
|
BranchName string
|
||||||
BranchDisplayName string
|
BranchDisplayName string
|
||||||
BranchLink string
|
BranchLink string
|
||||||
BranchCompareURL string
|
BranchCompareURL string
|
||||||
@@ -540,7 +557,9 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
|
|||||||
branchDisplayName = fmt.Sprintf("%s:%s", branch.Repo.FullName(), branchDisplayName)
|
branchDisplayName = fmt.Sprintf("%s:%s", branch.Repo.FullName(), branchDisplayName)
|
||||||
}
|
}
|
||||||
newBranches = append(newBranches, &RecentlyPushedNewBranch{
|
newBranches = append(newBranches, &RecentlyPushedNewBranch{
|
||||||
|
BranchRepo: branch.Repo,
|
||||||
BranchDisplayName: branchDisplayName,
|
BranchDisplayName: branchDisplayName,
|
||||||
|
BranchName: branch.Name,
|
||||||
BranchLink: fmt.Sprintf("%s/src/branch/%s", branch.Repo.Link(), util.PathEscapeSegments(branch.Name)),
|
BranchLink: fmt.Sprintf("%s/src/branch/%s", branch.Repo.Link(), util.PathEscapeSegments(branch.Name)),
|
||||||
BranchCompareURL: branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, branch.Name),
|
BranchCompareURL: branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, branch.Name),
|
||||||
CommitTime: branch.CommitTime,
|
CommitTime: branch.CommitTime,
|
||||||
|
|||||||
@@ -86,8 +86,10 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
|
|||||||
ids = append(ids, comment.ReviewID)
|
ids = append(ids, comment.ReviewID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := e.In("id", ids).Find(&reviews); err != nil {
|
if len(ids) > 0 {
|
||||||
return nil, err
|
if err := e.In("id", ids).Find(&reviews); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
n := 0
|
n := 0
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
@@ -531,6 +532,45 @@ func GetIssueByIndex(ctx context.Context, repoID, index int64) (*Issue, error) {
|
|||||||
return issue, nil
|
return issue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isPullToCond(isPull optional.Option[bool]) builder.Cond {
|
||||||
|
if isPull.Has() {
|
||||||
|
return builder.Eq{"is_pull": isPull.Value()}
|
||||||
|
}
|
||||||
|
return builder.NewCond()
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindLatestUpdatedIssues(ctx context.Context, repoID int64, isPull optional.Option[bool], pageSize int) (IssueList, error) {
|
||||||
|
issues := make([]*Issue, 0, pageSize)
|
||||||
|
err := db.GetEngine(ctx).Where("repo_id = ?", repoID).
|
||||||
|
And(isPullToCond(isPull)).
|
||||||
|
OrderBy("updated_unix DESC").
|
||||||
|
Limit(pageSize).
|
||||||
|
Find(&issues)
|
||||||
|
return issues, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindIssuesSuggestionByKeyword(ctx context.Context, repoID int64, keyword string, isPull optional.Option[bool], excludedID int64, pageSize int) (IssueList, error) {
|
||||||
|
cond := builder.NewCond()
|
||||||
|
if excludedID > 0 {
|
||||||
|
cond = cond.And(builder.Neq{"`id`": excludedID})
|
||||||
|
}
|
||||||
|
|
||||||
|
// It seems that GitHub searches both title and content (maybe sorting by the search engine's ranking system?)
|
||||||
|
// The first PR (https://github.com/go-gitea/gitea/pull/32327) uses "search indexer" to search "name(title) + content"
|
||||||
|
// But it seems that searching "content" (especially LIKE by DB engine) generates worse (unusable) results.
|
||||||
|
// So now (https://github.com/go-gitea/gitea/pull/33538) it only searches "name(title)", leave the improvements to the future.
|
||||||
|
cond = cond.And(db.BuildCaseInsensitiveLike("`name`", keyword))
|
||||||
|
|
||||||
|
issues := make([]*Issue, 0, pageSize)
|
||||||
|
err := db.GetEngine(ctx).Where("repo_id = ?", repoID).
|
||||||
|
And(isPullToCond(isPull)).
|
||||||
|
And(cond).
|
||||||
|
OrderBy("updated_unix DESC, `index` DESC").
|
||||||
|
Limit(pageSize).
|
||||||
|
Find(&issues)
|
||||||
|
return issues, err
|
||||||
|
}
|
||||||
|
|
||||||
// GetIssueWithAttrsByIndex returns issue by index in a repository.
|
// GetIssueWithAttrsByIndex returns issue by index in a repository.
|
||||||
func GetIssueWithAttrsByIndex(ctx context.Context, repoID, index int64) (*Issue, error) {
|
func GetIssueWithAttrsByIndex(ctx context.Context, repoID, index int64) (*Issue, error) {
|
||||||
issue, err := GetIssueByIndex(ctx, repoID, index)
|
issue, err := GetIssueByIndex(ctx, repoID, index)
|
||||||
|
|||||||
@@ -38,13 +38,30 @@ func (issue *Issue) projectID(ctx context.Context) int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ProjectColumnID return project column id if issue was assigned to one
|
// ProjectColumnID return project column id if issue was assigned to one
|
||||||
func (issue *Issue) ProjectColumnID(ctx context.Context) int64 {
|
func (issue *Issue) ProjectColumnID(ctx context.Context) (int64, error) {
|
||||||
var ip project_model.ProjectIssue
|
var ip project_model.ProjectIssue
|
||||||
has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip)
|
has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip)
|
||||||
if err != nil || !has {
|
if err != nil {
|
||||||
return 0
|
return 0, err
|
||||||
|
} else if !has {
|
||||||
|
return 0, nil
|
||||||
}
|
}
|
||||||
return ip.ProjectColumnID
|
return ip.ProjectColumnID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadProjectIssueColumnMap(ctx context.Context, projectID, defaultColumnID int64) (map[int64]int64, error) {
|
||||||
|
issues := make([]project_model.ProjectIssue, 0)
|
||||||
|
if err := db.GetEngine(ctx).Where("project_id=?", projectID).Find(&issues); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make(map[int64]int64, len(issues))
|
||||||
|
for _, issue := range issues {
|
||||||
|
if issue.ProjectColumnID == 0 {
|
||||||
|
issue.ProjectColumnID = defaultColumnID
|
||||||
|
}
|
||||||
|
result[issue.IssueID] = issue.ProjectColumnID
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadIssuesFromColumn load issues assigned to this column
|
// LoadIssuesFromColumn load issues assigned to this column
|
||||||
@@ -59,11 +76,11 @@ func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column, opts *Is
|
|||||||
}
|
}
|
||||||
|
|
||||||
if b.Default {
|
if b.Default {
|
||||||
issues, err := Issues(ctx, &IssuesOptions{
|
issues, err := Issues(ctx, opts.Copy(func(o *IssuesOptions) {
|
||||||
ProjectColumnID: db.NoConditionID,
|
o.ProjectColumnID = db.NoConditionID
|
||||||
ProjectID: b.ProjectID,
|
o.ProjectID = b.ProjectID
|
||||||
SortType: "project-column-sorting",
|
o.SortType = "project-column-sorting"
|
||||||
})
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -77,19 +94,6 @@ func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column, opts *Is
|
|||||||
return issueList, nil
|
return issueList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadIssuesFromColumnList load issues assigned to the columns
|
|
||||||
func LoadIssuesFromColumnList(ctx context.Context, bs project_model.ColumnList, opts *IssuesOptions) (map[int64]IssueList, error) {
|
|
||||||
issuesMap := make(map[int64]IssueList, len(bs))
|
|
||||||
for i := range bs {
|
|
||||||
il, err := LoadIssuesFromColumn(ctx, bs[i], opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
issuesMap[bs[i].ID] = il
|
|
||||||
}
|
|
||||||
return issuesMap, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IssueAssignOrRemoveProject changes the project associated with an issue
|
// IssueAssignOrRemoveProject changes the project associated with an issue
|
||||||
// If newProjectID is 0, the issue is removed from the project
|
// If newProjectID is 0, the issue is removed from the project
|
||||||
func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID, newColumnID int64) error {
|
func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID, newColumnID int64) error {
|
||||||
@@ -110,7 +114,7 @@ func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_mo
|
|||||||
return util.NewPermissionDeniedErrorf("issue %d can't be accessed by project %d", issue.ID, newProject.ID)
|
return util.NewPermissionDeniedErrorf("issue %d can't be accessed by project %d", issue.ID, newProject.ID)
|
||||||
}
|
}
|
||||||
if newColumnID == 0 {
|
if newColumnID == 0 {
|
||||||
newDefaultColumn, err := newProject.GetDefaultColumn(ctx)
|
newDefaultColumn, err := newProject.MustDefaultColumn(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ type IssuesOptions struct { //nolint
|
|||||||
// prioritize issues from this repo
|
// prioritize issues from this repo
|
||||||
PriorityRepoID int64
|
PriorityRepoID int64
|
||||||
IsArchived optional.Option[bool]
|
IsArchived optional.Option[bool]
|
||||||
Org *organization.Organization // issues permission scope
|
Owner *user_model.User // issues permission scope, it could be an organization or a user
|
||||||
Team *organization.Team // issues permission scope
|
Team *organization.Team // issues permission scope
|
||||||
User *user_model.User // issues permission scope
|
Doer *user_model.User // issues permission scope
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy returns a copy of the options.
|
// Copy returns a copy of the options.
|
||||||
@@ -273,8 +273,12 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) {
|
|||||||
|
|
||||||
applyLabelsCondition(sess, opts)
|
applyLabelsCondition(sess, opts)
|
||||||
|
|
||||||
if opts.User != nil {
|
if opts.Owner != nil {
|
||||||
sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.Value()))
|
sess.And(repo_model.UserOwnedRepoCond(opts.Owner.ID))
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Doer != nil && !opts.Doer.IsAdmin {
|
||||||
|
sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.Doer.ID, opts.Owner, opts.Team, opts.IsPull.Value()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,20 +325,20 @@ func teamUnitsRepoCond(id string, userID, orgID, teamID int64, units ...unit.Typ
|
|||||||
}
|
}
|
||||||
|
|
||||||
// issuePullAccessibleRepoCond userID must not be zero, this condition require join repository table
|
// issuePullAccessibleRepoCond userID must not be zero, this condition require join repository table
|
||||||
func issuePullAccessibleRepoCond(repoIDstr string, userID int64, org *organization.Organization, team *organization.Team, isPull bool) builder.Cond {
|
func issuePullAccessibleRepoCond(repoIDstr string, userID int64, owner *user_model.User, team *organization.Team, isPull bool) builder.Cond {
|
||||||
cond := builder.NewCond()
|
cond := builder.NewCond()
|
||||||
unitType := unit.TypeIssues
|
unitType := unit.TypeIssues
|
||||||
if isPull {
|
if isPull {
|
||||||
unitType = unit.TypePullRequests
|
unitType = unit.TypePullRequests
|
||||||
}
|
}
|
||||||
if org != nil {
|
if owner != nil && owner.IsOrganization() {
|
||||||
if team != nil {
|
if team != nil {
|
||||||
cond = cond.And(teamUnitsRepoCond(repoIDstr, userID, org.ID, team.ID, unitType)) // special team member repos
|
cond = cond.And(teamUnitsRepoCond(repoIDstr, userID, owner.ID, team.ID, unitType)) // special team member repos
|
||||||
} else {
|
} else {
|
||||||
cond = cond.And(
|
cond = cond.And(
|
||||||
builder.Or(
|
builder.Or(
|
||||||
repo_model.UserOrgUnitRepoCond(repoIDstr, userID, org.ID, unitType), // team member repos
|
repo_model.UserOrgUnitRepoCond(repoIDstr, userID, owner.ID, unitType), // team member repos
|
||||||
repo_model.UserOrgPublicUnitRepoCond(userID, org.ID), // user org public non-member repos, TODO: check repo has issues
|
repo_model.UserOrgPublicUnitRepoCond(userID, owner.ID), // user org public non-member repos, TODO: check repo has issues
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -663,7 +663,7 @@ func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_mo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if review != nil {
|
if review != nil {
|
||||||
// skip it when reviewer hase been request to review
|
// skip it when reviewer has been request to review
|
||||||
if review.Type == ReviewTypeRequest {
|
if review.Type == ReviewTypeRequest {
|
||||||
return nil, committer.Commit() // still commit the transaction, or committer.Close() will rollback it, even if it's a reused transaction.
|
return nil, committer.Commit() // still commit the transaction, or committer.Close() will rollback it, even if it's a reused transaction.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func (s Stopwatch) Seconds() int64 {
|
|||||||
|
|
||||||
// Duration returns a human-readable duration string based on local server time
|
// Duration returns a human-readable duration string based on local server time
|
||||||
func (s Stopwatch) Duration() string {
|
func (s Stopwatch) Duration() string {
|
||||||
return util.SecToTime(s.Seconds())
|
return util.SecToHours(s.Seconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, exists bool, err error) {
|
func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, exists bool, err error) {
|
||||||
@@ -201,7 +201,7 @@ func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Iss
|
|||||||
Doer: user,
|
Doer: user,
|
||||||
Issue: issue,
|
Issue: issue,
|
||||||
Repo: issue.Repo,
|
Repo: issue.Repo,
|
||||||
Content: util.SecToTime(timediff),
|
Content: util.SecToHours(timediff),
|
||||||
Type: CommentTypeStopTracking,
|
Type: CommentTypeStopTracking,
|
||||||
TimeID: tt.ID,
|
TimeID: tt.ID,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
|||||||
@@ -14,5 +14,9 @@ func AddContentVersionToIssueAndComment(x *xorm.Engine) error {
|
|||||||
ContentVersion int `xorm:"NOT NULL DEFAULT 0"`
|
ContentVersion int `xorm:"NOT NULL DEFAULT 0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
return x.Sync(new(Comment), new(Issue))
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
|
IgnoreConstrains: true,
|
||||||
|
IgnoreIndices: true,
|
||||||
|
}, new(Comment), new(Issue))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,9 @@ func AddForcePushBranchProtection(x *xorm.Engine) error {
|
|||||||
ForcePushAllowlistTeamIDs []int64 `xorm:"JSON TEXT"`
|
ForcePushAllowlistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||||
ForcePushAllowlistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
|
ForcePushAllowlistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
}
|
}
|
||||||
return x.Sync(new(ProtectedBranch))
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
|
IgnoreConstrains: true,
|
||||||
|
IgnoreIndices: true,
|
||||||
|
}, new(ProtectedBranch))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,5 +10,9 @@ func AddSkipSecondaryAuthColumnToOAuth2ApplicationTable(x *xorm.Engine) error {
|
|||||||
type oauth2Application struct {
|
type oauth2Application struct {
|
||||||
SkipSecondaryAuthorization bool `xorm:"NOT NULL DEFAULT FALSE"`
|
SkipSecondaryAuthorization bool `xorm:"NOT NULL DEFAULT FALSE"`
|
||||||
}
|
}
|
||||||
return x.Sync(new(oauth2Application))
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
|
IgnoreConstrains: true,
|
||||||
|
IgnoreIndices: true,
|
||||||
|
}, new(oauth2Application))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,5 +19,9 @@ func AddCommentMetaDataColumn(x *xorm.Engine) error {
|
|||||||
CommentMetaData *CommentMetaData `xorm:"JSON TEXT"` // put all non-index metadata in a single field
|
CommentMetaData *CommentMetaData `xorm:"JSON TEXT"` // put all non-index metadata in a single field
|
||||||
}
|
}
|
||||||
|
|
||||||
return x.Sync(new(Comment))
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
|
IgnoreConstrains: true,
|
||||||
|
IgnoreIndices: true,
|
||||||
|
}, new(Comment))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,5 +9,9 @@ func AddBlockAdminMergeOverrideBranchProtection(x *xorm.Engine) error {
|
|||||||
type ProtectedBranch struct {
|
type ProtectedBranch struct {
|
||||||
BlockAdminMergeOverride bool `xorm:"NOT NULL DEFAULT false"`
|
BlockAdminMergeOverride bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
}
|
}
|
||||||
return x.Sync(new(ProtectedBranch))
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
|
IgnoreConstrains: true,
|
||||||
|
IgnoreIndices: true,
|
||||||
|
}, new(ProtectedBranch))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,5 +12,9 @@ func AddPriorityToProtectedBranch(x *xorm.Engine) error {
|
|||||||
Priority int64 `xorm:"NOT NULL DEFAULT 0"`
|
Priority int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
return x.Sync(new(ProtectedBranch))
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
|
IgnoreConstrains: true,
|
||||||
|
IgnoreIndices: true,
|
||||||
|
}, new(ProtectedBranch))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ func AddTimeEstimateColumnToIssueTable(x *xorm.Engine) error {
|
|||||||
type Issue struct {
|
type Issue struct {
|
||||||
TimeEstimate int64 `xorm:"NOT NULL DEFAULT 0"`
|
TimeEstimate int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||||
}
|
}
|
||||||
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
return x.Sync(new(Issue))
|
IgnoreConstrains: true,
|
||||||
|
IgnoreIndices: true,
|
||||||
|
}, new(Issue))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -279,9 +279,7 @@ func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
|
|||||||
default:
|
default:
|
||||||
e.Desc("package_version.created_unix")
|
e.Desc("package_version.created_unix")
|
||||||
}
|
}
|
||||||
|
e.Desc("package_version.id") // Sort by id for stable order with duplicates in the other field
|
||||||
// Sort by id for stable order with duplicates in the other field
|
|
||||||
e.Asc("package_version.id")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchVersions gets all versions of packages matching the search options
|
// SearchVersions gets all versions of packages matching the search options
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ type Column struct {
|
|||||||
ProjectID int64 `xorm:"INDEX NOT NULL"`
|
ProjectID int64 `xorm:"INDEX NOT NULL"`
|
||||||
CreatorID int64 `xorm:"NOT NULL"`
|
CreatorID int64 `xorm:"NOT NULL"`
|
||||||
|
|
||||||
|
NumIssues int64 `xorm:"-"`
|
||||||
|
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||||
}
|
}
|
||||||
@@ -57,20 +59,6 @@ func (Column) TableName() string {
|
|||||||
return "project_board" // TODO: the legacy table name should be project_column
|
return "project_board" // TODO: the legacy table name should be project_column
|
||||||
}
|
}
|
||||||
|
|
||||||
// NumIssues return counter of all issues assigned to the column
|
|
||||||
func (c *Column) NumIssues(ctx context.Context) int {
|
|
||||||
total, err := db.GetEngine(ctx).Table("project_issue").
|
|
||||||
Where("project_id=?", c.ProjectID).
|
|
||||||
And("project_board_id=?", c.ID).
|
|
||||||
GroupBy("issue_id").
|
|
||||||
Cols("issue_id").
|
|
||||||
Count()
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int(total)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Column) GetIssues(ctx context.Context) ([]*ProjectIssue, error) {
|
func (c *Column) GetIssues(ctx context.Context) ([]*ProjectIssue, error) {
|
||||||
issues := make([]*ProjectIssue, 0, 5)
|
issues := make([]*ProjectIssue, 0, 5)
|
||||||
if err := db.GetEngine(ctx).Where("project_id=?", c.ProjectID).
|
if err := db.GetEngine(ctx).Where("project_id=?", c.ProjectID).
|
||||||
@@ -192,7 +180,7 @@ func deleteColumnByID(ctx context.Context, columnID int64) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defaultColumn, err := project.GetDefaultColumn(ctx)
|
defaultColumn, err := project.MustDefaultColumn(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -257,8 +245,8 @@ func (p *Project) GetColumns(ctx context.Context) (ColumnList, error) {
|
|||||||
return columns, nil
|
return columns, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultColumn return default column and ensure only one exists
|
// getDefaultColumn return default column and ensure only one exists
|
||||||
func (p *Project) GetDefaultColumn(ctx context.Context) (*Column, error) {
|
func (p *Project) getDefaultColumn(ctx context.Context) (*Column, error) {
|
||||||
var column Column
|
var column Column
|
||||||
has, err := db.GetEngine(ctx).
|
has, err := db.GetEngine(ctx).
|
||||||
Where("project_id=? AND `default` = ?", p.ID, true).
|
Where("project_id=? AND `default` = ?", p.ID, true).
|
||||||
@@ -270,6 +258,33 @@ func (p *Project) GetDefaultColumn(ctx context.Context) (*Column, error) {
|
|||||||
if has {
|
if has {
|
||||||
return &column, nil
|
return &column, nil
|
||||||
}
|
}
|
||||||
|
return nil, ErrProjectColumnNotExist{ColumnID: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustDefaultColumn returns the default column for a project.
|
||||||
|
// If one exists, it is returned
|
||||||
|
// If none exists, the first column will be elevated to the default column of this project
|
||||||
|
func (p *Project) MustDefaultColumn(ctx context.Context) (*Column, error) {
|
||||||
|
c, err := p.getDefaultColumn(ctx)
|
||||||
|
if err != nil && !IsErrProjectColumnNotExist(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var column Column
|
||||||
|
has, err := db.GetEngine(ctx).Where("project_id=?", p.ID).OrderBy("sorting, id").Get(&column)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if has {
|
||||||
|
column.Default = true
|
||||||
|
if _, err := db.GetEngine(ctx).ID(column.ID).Cols("`default`").Update(&column); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &column, nil
|
||||||
|
}
|
||||||
|
|
||||||
// create a default column if none is found
|
// create a default column if none is found
|
||||||
column = Column{
|
column = Column{
|
||||||
|
|||||||
@@ -20,19 +20,19 @@ func TestGetDefaultColumn(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// check if default column was added
|
// check if default column was added
|
||||||
column, err := projectWithoutDefault.GetDefaultColumn(db.DefaultContext)
|
column, err := projectWithoutDefault.MustDefaultColumn(db.DefaultContext)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(5), column.ProjectID)
|
assert.Equal(t, int64(5), column.ProjectID)
|
||||||
assert.Equal(t, "Uncategorized", column.Title)
|
assert.Equal(t, "Done", column.Title)
|
||||||
|
|
||||||
projectWithMultipleDefaults, err := GetProjectByID(db.DefaultContext, 6)
|
projectWithMultipleDefaults, err := GetProjectByID(db.DefaultContext, 6)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// check if multiple defaults were removed
|
// check if multiple defaults were removed
|
||||||
column, err = projectWithMultipleDefaults.GetDefaultColumn(db.DefaultContext)
|
column, err = projectWithMultipleDefaults.MustDefaultColumn(db.DefaultContext)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(6), column.ProjectID)
|
assert.Equal(t, int64(6), column.ProjectID)
|
||||||
assert.Equal(t, int64(9), column.ID)
|
assert.Equal(t, int64(9), column.ID) // there are 2 default columns in the test data, use the latest one
|
||||||
|
|
||||||
// set 8 as default column
|
// set 8 as default column
|
||||||
assert.NoError(t, SetDefaultColumn(db.DefaultContext, column.ProjectID, 8))
|
assert.NoError(t, SetDefaultColumn(db.DefaultContext, column.ProjectID, 8))
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,48 +33,6 @@ func deleteProjectIssuesByProjectID(ctx context.Context, projectID int64) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NumIssues return counter of all issues assigned to a project
|
|
||||||
func (p *Project) NumIssues(ctx context.Context) int {
|
|
||||||
c, err := db.GetEngine(ctx).Table("project_issue").
|
|
||||||
Where("project_id=?", p.ID).
|
|
||||||
GroupBy("issue_id").
|
|
||||||
Cols("issue_id").
|
|
||||||
Count()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("NumIssues: %v", err)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NumClosedIssues return counter of closed issues assigned to a project
|
|
||||||
func (p *Project) NumClosedIssues(ctx context.Context) int {
|
|
||||||
c, err := db.GetEngine(ctx).Table("project_issue").
|
|
||||||
Join("INNER", "issue", "project_issue.issue_id=issue.id").
|
|
||||||
Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, true).
|
|
||||||
Cols("issue_id").
|
|
||||||
Count()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("NumClosedIssues: %v", err)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NumOpenIssues return counter of open issues assigned to a project
|
|
||||||
func (p *Project) NumOpenIssues(ctx context.Context) int {
|
|
||||||
c, err := db.GetEngine(ctx).Table("project_issue").
|
|
||||||
Join("INNER", "issue", "project_issue.issue_id=issue.id").
|
|
||||||
Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false).
|
|
||||||
Cols("issue_id").
|
|
||||||
Count()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("NumOpenIssues: %v", err)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Column) moveIssuesToAnotherColumn(ctx context.Context, newColumn *Column) error {
|
func (c *Column) moveIssuesToAnotherColumn(ctx context.Context, newColumn *Column) error {
|
||||||
if c.ProjectID != newColumn.ProjectID {
|
if c.ProjectID != newColumn.ProjectID {
|
||||||
return fmt.Errorf("columns have to be in the same project")
|
return fmt.Errorf("columns have to be in the same project")
|
||||||
|
|||||||
@@ -97,6 +97,9 @@ type Project struct {
|
|||||||
Type Type
|
Type Type
|
||||||
|
|
||||||
RenderedContent template.HTML `xorm:"-"`
|
RenderedContent template.HTML `xorm:"-"`
|
||||||
|
NumOpenIssues int64 `xorm:"-"`
|
||||||
|
NumClosedIssues int64 `xorm:"-"`
|
||||||
|
NumIssues int64 `xorm:"-"`
|
||||||
|
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ func UpdateRepoLicenses(ctx context.Context, repo *Repository, commitID string,
|
|||||||
for _, o := range oldLicenses {
|
for _, o := range oldLicenses {
|
||||||
// Update already existing license
|
// Update already existing license
|
||||||
if o.License == license {
|
if o.License == license {
|
||||||
|
o.CommitID = commitID
|
||||||
if _, err := db.GetEngine(ctx).ID(o.ID).Cols("`commit_id`").Update(o); err != nil {
|
if _, err := db.GetEngine(ctx).ID(o.ID).Cols("`commit_id`").Update(o); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,27 +38,32 @@ func GenerateRandomAvatar(ctx context.Context, u *User) error {
|
|||||||
|
|
||||||
u.Avatar = avatars.HashEmail(seed)
|
u.Avatar = avatars.HashEmail(seed)
|
||||||
|
|
||||||
// Don't share the images so that we can delete them easily
|
_, err = storage.Avatars.Stat(u.CustomAvatarRelativePath())
|
||||||
if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
|
if err != nil {
|
||||||
if err := png.Encode(w, img); err != nil {
|
// If unable to Stat the avatar file (usually it means non-existing), then try to save a new one
|
||||||
log.Error("Encode: %v", err)
|
// Don't share the images so that we can delete them easily
|
||||||
|
if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
|
||||||
|
if err := png.Encode(w, img); err != nil {
|
||||||
|
log.Error("Encode: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return fmt.Errorf("failed to save avatar %s: %w", u.CustomAvatarRelativePath(), err)
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
}); err != nil {
|
|
||||||
return fmt.Errorf("Failed to create dir %s: %w", u.CustomAvatarRelativePath(), err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := db.GetEngine(ctx).ID(u.ID).Cols("avatar").Update(u); err != nil {
|
if _, err := db.GetEngine(ctx).ID(u.ID).Cols("avatar").Update(u); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("New random avatar created: %d", u.ID)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size
|
// AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size
|
||||||
func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
|
func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
|
||||||
if u.IsGhost() {
|
// ghost user was deleted, Gitea actions is a bot user, 0 means the user should be a virtual user
|
||||||
|
// which comes from git configure information
|
||||||
|
if u.IsGhost() || u.IsGiteaActions() || u.ID <= 0 {
|
||||||
return avatars.DefaultAvatarLink()
|
return avatars.DefaultAvatarLink()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,19 @@
|
|||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/storage"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUserAvatarLink(t *testing.T) {
|
func TestUserAvatarLink(t *testing.T) {
|
||||||
@@ -26,3 +32,37 @@ func TestUserAvatarLink(t *testing.T) {
|
|||||||
link = u.AvatarLink(db.DefaultContext)
|
link = u.AvatarLink(db.DefaultContext)
|
||||||
assert.Equal(t, "https://localhost/sub-path/avatars/avatar.png", link)
|
assert.Equal(t, "https://localhost/sub-path/avatars/avatar.png", link)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUserAvatarGenerate(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
var err error
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
storage.Avatars, err = storage.NewLocalStorage(context.Background(), &setting.Storage{Path: tmpDir})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
u := unittest.AssertExistsAndLoadBean(t, &User{ID: 2})
|
||||||
|
|
||||||
|
// there was no avatar, generate a new one
|
||||||
|
assert.Empty(t, u.Avatar)
|
||||||
|
err = GenerateRandomAvatar(db.DefaultContext, u)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, u.Avatar)
|
||||||
|
|
||||||
|
// make sure the generated one exists
|
||||||
|
oldAvatarPath := u.CustomAvatarRelativePath()
|
||||||
|
_, err = storage.Avatars.Stat(u.CustomAvatarRelativePath())
|
||||||
|
require.NoError(t, err)
|
||||||
|
// and try to change its content
|
||||||
|
_, err = storage.Avatars.Save(u.CustomAvatarRelativePath(), strings.NewReader("abcd"), 4)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// try to generate again
|
||||||
|
err = GenerateRandomAvatar(db.DefaultContext, u)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, oldAvatarPath, u.CustomAvatarRelativePath())
|
||||||
|
f, err := storage.Avatars.Open(u.CustomAvatarRelativePath())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer f.Close()
|
||||||
|
content, _ := io.ReadAll(f)
|
||||||
|
assert.Equal(t, "abcd", string(content))
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ func NewGhostUser() *User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsGhostUserName(name string) bool {
|
||||||
|
return strings.EqualFold(name, GhostUserName)
|
||||||
|
}
|
||||||
|
|
||||||
// IsGhost check if user is fake user for a deleted account
|
// IsGhost check if user is fake user for a deleted account
|
||||||
func (u *User) IsGhost() bool {
|
func (u *User) IsGhost() bool {
|
||||||
if u == nil {
|
if u == nil {
|
||||||
@@ -48,6 +52,10 @@ const (
|
|||||||
ActionsEmail = "teabot@gitea.io"
|
ActionsEmail = "teabot@gitea.io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func IsGiteaActionsUserName(name string) bool {
|
||||||
|
return strings.EqualFold(name, ActionsUserName)
|
||||||
|
}
|
||||||
|
|
||||||
// NewActionsUser creates and returns a fake user for running the actions.
|
// NewActionsUser creates and returns a fake user for running the actions.
|
||||||
func NewActionsUser() *User {
|
func NewActionsUser() *User {
|
||||||
return &User{
|
return &User{
|
||||||
@@ -65,6 +73,16 @@ func NewActionsUser() *User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) IsActions() bool {
|
func (u *User) IsGiteaActions() bool {
|
||||||
return u != nil && u.ID == ActionsUserID
|
return u != nil && u.ID == ActionsUserID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetSystemUserByName(name string) *User {
|
||||||
|
if IsGhostUserName(name) {
|
||||||
|
return NewGhostUser()
|
||||||
|
}
|
||||||
|
if IsGiteaActionsUserName(name) {
|
||||||
|
return NewActionsUser()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
32
models/user/user_system_test.go
Normal file
32
models/user/user_system_test.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSystemUser(t *testing.T) {
|
||||||
|
u, err := GetPossibleUserByID(db.DefaultContext, -1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "Ghost", u.Name)
|
||||||
|
assert.Equal(t, "ghost", u.LowerName)
|
||||||
|
assert.True(t, u.IsGhost())
|
||||||
|
assert.True(t, IsGhostUserName("gHost"))
|
||||||
|
|
||||||
|
u, err = GetPossibleUserByID(db.DefaultContext, -2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "gitea-actions", u.Name)
|
||||||
|
assert.Equal(t, "gitea-actions", u.LowerName)
|
||||||
|
assert.True(t, u.IsGiteaActions())
|
||||||
|
assert.True(t, IsGiteaActionsUserName("Gitea-actionS"))
|
||||||
|
|
||||||
|
_, err = GetPossibleUserByID(db.DefaultContext, -3)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
@@ -299,6 +299,11 @@ func (w *Webhook) HasPackageEvent() bool {
|
|||||||
(w.ChooseEvents && w.HookEvents.Package)
|
(w.ChooseEvents && w.HookEvents.Package)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *Webhook) HasStatusEvent() bool {
|
||||||
|
return w.SendEverything ||
|
||||||
|
(w.ChooseEvents && w.HookEvents.Status)
|
||||||
|
}
|
||||||
|
|
||||||
// HasPullRequestReviewRequestEvent returns true if hook enabled pull request review request event.
|
// HasPullRequestReviewRequestEvent returns true if hook enabled pull request review request event.
|
||||||
func (w *Webhook) HasPullRequestReviewRequestEvent() bool {
|
func (w *Webhook) HasPullRequestReviewRequestEvent() bool {
|
||||||
return w.SendEverything ||
|
return w.SendEverything ||
|
||||||
@@ -337,6 +342,7 @@ func (w *Webhook) EventCheckers() []struct {
|
|||||||
{w.HasReleaseEvent, webhook_module.HookEventRelease},
|
{w.HasReleaseEvent, webhook_module.HookEventRelease},
|
||||||
{w.HasPackageEvent, webhook_module.HookEventPackage},
|
{w.HasPackageEvent, webhook_module.HookEventPackage},
|
||||||
{w.HasPullRequestReviewRequestEvent, webhook_module.HookEventPullRequestReviewRequest},
|
{w.HasPullRequestReviewRequestEvent, webhook_module.HookEventPullRequestReviewRequest},
|
||||||
|
{w.HasStatusEvent, webhook_module.HookEventStatus},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func TestWebhook_EventsArray(t *testing.T) {
|
|||||||
"pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone",
|
"pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone",
|
||||||
"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
|
"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
|
||||||
"pull_request_review_comment", "pull_request_sync", "wiki", "repository", "release",
|
"pull_request_review_comment", "pull_request_sync", "wiki", "repository", "release",
|
||||||
"package", "pull_request_review_request",
|
"package", "pull_request_review_request", "status",
|
||||||
},
|
},
|
||||||
(&Webhook{
|
(&Webhook{
|
||||||
HookEvent: &webhook_module.HookEvent{SendEverything: true},
|
HookEvent: &webhook_module.HookEvent{SendEverything: true},
|
||||||
|
|||||||
@@ -463,7 +463,7 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
|
|||||||
matchTimes++
|
matchTimes++
|
||||||
}
|
}
|
||||||
case "paths":
|
case "paths":
|
||||||
filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.Base.Ref)
|
filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.MergeBase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err)
|
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err)
|
||||||
} else {
|
} else {
|
||||||
@@ -476,7 +476,7 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "paths-ignore":
|
case "paths-ignore":
|
||||||
filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.Base.Ref)
|
filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.MergeBase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err)
|
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -15,5 +15,5 @@ func TestPamAuth(t *testing.T) {
|
|||||||
result, err := Auth("gitea", "user1", "false-pwd")
|
result, err := Auth("gitea", "user1", "false-pwd")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.EqualError(t, err, "Authentication failure")
|
assert.EqualError(t, err, "Authentication failure")
|
||||||
assert.Len(t, result)
|
assert.Empty(t, result)
|
||||||
}
|
}
|
||||||
|
|||||||
9
modules/cache/cache.go
vendored
9
modules/cache/cache.go
vendored
@@ -37,10 +37,15 @@ func Init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
testCacheKey = "DefaultCache.TestKey"
|
testCacheKey = "DefaultCache.TestKey"
|
||||||
SlowCacheThreshold = 100 * time.Microsecond
|
// SlowCacheThreshold marks cache tests as slow
|
||||||
|
// set to 30ms per discussion: https://github.com/go-gitea/gitea/issues/33190
|
||||||
|
// TODO: Replace with metrics histogram
|
||||||
|
SlowCacheThreshold = 30 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Test performs delete, put and get operations on a predefined key
|
||||||
|
// returns
|
||||||
func Test() (time.Duration, error) {
|
func Test() (time.Duration, error) {
|
||||||
if defaultCache == nil {
|
if defaultCache == nil {
|
||||||
return 0, fmt.Errorf("default cache not initialized")
|
return 0, fmt.Errorf("default cache not initialized")
|
||||||
|
|||||||
3
modules/cache/cache_test.go
vendored
3
modules/cache/cache_test.go
vendored
@@ -43,7 +43,8 @@ func TestTest(t *testing.T) {
|
|||||||
elapsed, err := Test()
|
elapsed, err := Test()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
// mem cache should take from 300ns up to 1ms on modern hardware ...
|
// mem cache should take from 300ns up to 1ms on modern hardware ...
|
||||||
assert.Less(t, elapsed, time.Millisecond)
|
assert.Positive(t, elapsed)
|
||||||
|
assert.Less(t, elapsed, SlowCacheThreshold)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetCache(t *testing.T) {
|
func TestGetCache(t *testing.T) {
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ type Command struct {
|
|||||||
desc string
|
desc string
|
||||||
globalArgsLength int
|
globalArgsLength int
|
||||||
brokenArgs []string
|
brokenArgs []string
|
||||||
|
cmd *exec.Cmd // for debug purpose only
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Command) String() string {
|
func (c *Command) String() string {
|
||||||
@@ -314,6 +315,7 @@ func (c *Command) Run(opts *RunOpts) error {
|
|||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, c.prog, c.args...)
|
cmd := exec.CommandContext(ctx, c.prog, c.args...)
|
||||||
|
c.cmd = cmd // for debug purpose only
|
||||||
if opts.Env == nil {
|
if opts.Env == nil {
|
||||||
cmd.Env = os.Environ()
|
cmd.Env = os.Environ()
|
||||||
} else {
|
} else {
|
||||||
@@ -348,9 +350,10 @@ func (c *Command) Run(opts *RunOpts) error {
|
|||||||
// We need to check if the context is canceled by the program on Windows.
|
// We need to check if the context is canceled by the program on Windows.
|
||||||
// This is because Windows does not have signal checking when terminating the process.
|
// This is because Windows does not have signal checking when terminating the process.
|
||||||
// It always returns exit code 1, unlike Linux, which has many exit codes for signals.
|
// It always returns exit code 1, unlike Linux, which has many exit codes for signals.
|
||||||
|
// `err.Error()` returns "exit status 1" when using the `git check-attr` command after the context is canceled.
|
||||||
if runtime.GOOS == "windows" &&
|
if runtime.GOOS == "windows" &&
|
||||||
err != nil &&
|
err != nil &&
|
||||||
err.Error() == "" &&
|
(err.Error() == "" || err.Error() == "exit status 1") &&
|
||||||
cmd.ProcessState.ExitCode() == 1 &&
|
cmd.ProcessState.ExitCode() == 1 &&
|
||||||
ctx.Err() == context.Canceled {
|
ctx.Err() == context.Canceled {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
|
|||||||
@@ -360,5 +360,5 @@ func Test_GetCommitBranchStart(t *testing.T) {
|
|||||||
startCommitID, err := repo.GetCommitBranchStart(os.Environ(), "branch1", commit.ID.String())
|
startCommitID, err := repo.GetCommitBranchStart(os.Environ(), "branch1", commit.ID.String())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotEmpty(t, startCommitID)
|
assert.NotEmpty(t, startCommitID)
|
||||||
assert.EqualValues(t, "9c9aef8dd84e02bc7ec12641deb4c930a7c30185", startCommitID)
|
assert.EqualValues(t, "95bb4d39648ee7e325106df01a621c530863a653", startCommitID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ func TestRefName(t *testing.T) {
|
|||||||
|
|
||||||
// Test pull names
|
// Test pull names
|
||||||
assert.Equal(t, "1", RefName("refs/pull/1/head").PullName())
|
assert.Equal(t, "1", RefName("refs/pull/1/head").PullName())
|
||||||
|
assert.True(t, RefName("refs/pull/1/head").IsPull())
|
||||||
|
assert.True(t, RefName("refs/pull/1/merge").IsPull())
|
||||||
assert.Equal(t, "my/pull", RefName("refs/pull/my/pull/head").PullName())
|
assert.Equal(t, "my/pull", RefName("refs/pull/my/pull/head").PullName())
|
||||||
|
|
||||||
// Test for branch names
|
// Test for branch names
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
)
|
)
|
||||||
@@ -102,7 +104,7 @@ type CheckAttributeReader struct {
|
|||||||
|
|
||||||
stdinReader io.ReadCloser
|
stdinReader io.ReadCloser
|
||||||
stdinWriter *os.File
|
stdinWriter *os.File
|
||||||
stdOut attributeWriter
|
stdOut *nulSeparatedAttributeWriter
|
||||||
cmd *Command
|
cmd *Command
|
||||||
env []string
|
env []string
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -152,7 +154,6 @@ func (c *CheckAttributeReader) Init(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run run cmd
|
|
||||||
func (c *CheckAttributeReader) Run() error {
|
func (c *CheckAttributeReader) Run() error {
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = c.stdinReader.Close()
|
_ = c.stdinReader.Close()
|
||||||
@@ -176,7 +177,7 @@ func (c *CheckAttributeReader) Run() error {
|
|||||||
func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err error) {
|
func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil && err != c.ctx.Err() {
|
if err != nil && err != c.ctx.Err() {
|
||||||
log.Error("Unexpected error when checking path %s in %s. Error: %v", path, c.Repo.Path, err)
|
log.Error("Unexpected error when checking path %s in %s, error: %v", path, filepath.Base(c.Repo.Path), err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -191,9 +192,31 @@ func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reportTimeout := func() error {
|
||||||
|
stdOutClosed := false
|
||||||
|
select {
|
||||||
|
case <-c.stdOut.closed:
|
||||||
|
stdOutClosed = true
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
debugMsg := fmt.Sprintf("check path %q in repo %q", path, filepath.Base(c.Repo.Path))
|
||||||
|
debugMsg += fmt.Sprintf(", stdOut: tmp=%q, pos=%d, closed=%v", string(c.stdOut.tmp), c.stdOut.pos, stdOutClosed)
|
||||||
|
if c.cmd.cmd != nil {
|
||||||
|
debugMsg += fmt.Sprintf(", process state: %q", c.cmd.cmd.ProcessState.String())
|
||||||
|
}
|
||||||
|
_ = c.Close()
|
||||||
|
return fmt.Errorf("CheckPath timeout: %s", debugMsg)
|
||||||
|
}
|
||||||
|
|
||||||
rs = make(map[string]string)
|
rs = make(map[string]string)
|
||||||
for range c.Attributes {
|
for range c.Attributes {
|
||||||
select {
|
select {
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
// There is a strange "hang" problem in gitdiff.GetDiff -> CheckPath
|
||||||
|
// So add a timeout here to mitigate the problem, and output more logs for debug purpose
|
||||||
|
// In real world, if CheckPath runs long than seconds, it blocks the end user's operation,
|
||||||
|
// and at the moment the CheckPath result is not so important, so we can just ignore it.
|
||||||
|
return nil, reportTimeout()
|
||||||
case attr, ok := <-c.stdOut.ReadAttribute():
|
case attr, ok := <-c.stdOut.ReadAttribute():
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, c.ctx.Err()
|
return nil, c.ctx.Err()
|
||||||
@@ -206,18 +229,12 @@ func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err
|
|||||||
return rs, nil
|
return rs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close close pip after use
|
|
||||||
func (c *CheckAttributeReader) Close() error {
|
func (c *CheckAttributeReader) Close() error {
|
||||||
c.cancel()
|
c.cancel()
|
||||||
err := c.stdinWriter.Close()
|
err := c.stdinWriter.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
type attributeWriter interface {
|
|
||||||
io.WriteCloser
|
|
||||||
ReadAttribute() <-chan attributeTriple
|
|
||||||
}
|
|
||||||
|
|
||||||
type attributeTriple struct {
|
type attributeTriple struct {
|
||||||
Filename string
|
Filename string
|
||||||
Attribute string
|
Attribute string
|
||||||
@@ -263,7 +280,7 @@ func (wr *nulSeparatedAttributeWriter) Write(p []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
wr.tmp = append(wr.tmp, p...)
|
wr.tmp = append(wr.tmp, p...)
|
||||||
return len(p), nil
|
return l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wr *nulSeparatedAttributeWriter) ReadAttribute() <-chan attributeTriple {
|
func (wr *nulSeparatedAttributeWriter) ReadAttribute() <-chan attributeTriple {
|
||||||
@@ -281,7 +298,7 @@ func (wr *nulSeparatedAttributeWriter) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a check attribute reader for the current repository and provided commit ID
|
// CheckAttributeReader creates a check attribute reader for the current repository and provided commit ID
|
||||||
func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeReader, context.CancelFunc) {
|
func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeReader, context.CancelFunc) {
|
||||||
indexFilename, worktree, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID)
|
indexFilename, worktree, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -303,21 +320,21 @@ func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeRe
|
|||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(repo.Ctx)
|
ctx, cancel := context.WithCancel(repo.Ctx)
|
||||||
if err := checker.Init(ctx); err != nil {
|
if err := checker.Init(ctx); err != nil {
|
||||||
log.Error("Unable to open checker for %s. Error: %v", commitID, err)
|
log.Error("Unable to open attribute checker for commit %s, error: %v", commitID, err)
|
||||||
} else {
|
} else {
|
||||||
go func() {
|
go func() {
|
||||||
err := checker.Run()
|
err := checker.Run()
|
||||||
if err != nil && err != ctx.Err() {
|
if err != nil && !IsErrCanceledOrKilled(err) {
|
||||||
log.Error("Unable to open checker for %s. Error: %v", commitID, err)
|
log.Error("Attribute checker for commit %s exits with error: %v", commitID, err)
|
||||||
}
|
}
|
||||||
cancel()
|
cancel()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
deferable := func() {
|
deferrable := func() {
|
||||||
_ = checker.Close()
|
_ = checker.Close()
|
||||||
cancel()
|
cancel()
|
||||||
deleteTemporaryFile()
|
deleteTemporaryFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
return checker, deferable
|
return checker, deferrable
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,16 @@
|
|||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
mathRand "math/rand/v2"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
|
func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
|
||||||
@@ -95,3 +101,57 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
|
|||||||
Value: "unspecified",
|
Value: "unspecified",
|
||||||
}, attr)
|
}, attr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAttributeReader(t *testing.T) {
|
||||||
|
t.Skip() // for debug purpose only, do not run in CI
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
timeout := 1 * time.Second
|
||||||
|
repoPath := filepath.Join(testReposDir, "language_stats_repo")
|
||||||
|
commitRef := "HEAD"
|
||||||
|
|
||||||
|
oneRound := func(t *testing.T, roundIdx int) {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
|
_ = cancel
|
||||||
|
gitRepo, err := OpenRepository(ctx, repoPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
commit, err := gitRepo.GetCommit(commitRef)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
files, err := gitRepo.LsFiles()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
randomFiles := slices.Clone(files)
|
||||||
|
randomFiles = append(randomFiles, "any-file-1", "any-file-2")
|
||||||
|
|
||||||
|
t.Logf("Round %v with %d files", roundIdx, len(randomFiles))
|
||||||
|
|
||||||
|
attrReader, deferrable := gitRepo.CheckAttributeReader(commit.ID.String())
|
||||||
|
defer deferrable()
|
||||||
|
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
file := randomFiles[mathRand.IntN(len(randomFiles))]
|
||||||
|
_, err := attrReader.CheckPath(file)
|
||||||
|
if err != nil {
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
_, _ = attrReader.CheckPath(file)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
oneRound(t, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -519,6 +519,7 @@ func (repo *Repository) AddLastCommitCache(cacheKey, fullName, sha string) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCommitBranchStart returns the commit where the branch diverged
|
||||||
func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID string) (string, error) {
|
func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID string) (string, error) {
|
||||||
cmd := NewCommand(repo.Ctx, "log", prettyLogFormat)
|
cmd := NewCommand(repo.Ctx, "log", prettyLogFormat)
|
||||||
cmd.AddDynamicArguments(endCommitID)
|
cmd.AddDynamicArguments(endCommitID)
|
||||||
@@ -533,7 +534,8 @@ func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID s
|
|||||||
|
|
||||||
parts := bytes.Split(bytes.TrimSpace(stdout), []byte{'\n'})
|
parts := bytes.Split(bytes.TrimSpace(stdout), []byte{'\n'})
|
||||||
|
|
||||||
var startCommitID string
|
// check the commits one by one until we find a commit contained by another branch
|
||||||
|
// and we think this commit is the divergence point
|
||||||
for _, commitID := range parts {
|
for _, commitID := range parts {
|
||||||
branches, err := repo.getBranches(env, string(commitID), 2)
|
branches, err := repo.getBranches(env, string(commitID), 2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -541,11 +543,9 @@ func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID s
|
|||||||
}
|
}
|
||||||
for _, b := range branches {
|
for _, b := range branches {
|
||||||
if b != branch {
|
if b != branch {
|
||||||
return startCommitID, nil
|
return string(commitID), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startCommitID = string(commitID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", nil
|
return "", nil
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(branches) == 0 {
|
if len(branches) == 0 {
|
||||||
graphCmd.AddArguments("--all")
|
graphCmd.AddArguments("--tags", "--branches")
|
||||||
}
|
}
|
||||||
|
|
||||||
graphCmd.AddArguments("-C", "-M", "--date=iso-strict").
|
graphCmd.AddArguments("-C", "-M", "--date=iso-strict").
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@@ -99,10 +100,13 @@ func (r *Request) Param(key, value string) *Request {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// Body adds request raw body.
|
// Body adds request raw body. It supports string, []byte and io.Reader as body.
|
||||||
// it supports string and []byte.
|
|
||||||
func (r *Request) Body(data any) *Request {
|
func (r *Request) Body(data any) *Request {
|
||||||
|
if r == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
switch t := data.(type) {
|
switch t := data.(type) {
|
||||||
|
case nil: // do nothing
|
||||||
case string:
|
case string:
|
||||||
bf := bytes.NewBufferString(t)
|
bf := bytes.NewBufferString(t)
|
||||||
r.req.Body = io.NopCloser(bf)
|
r.req.Body = io.NopCloser(bf)
|
||||||
@@ -111,6 +115,12 @@ func (r *Request) Body(data any) *Request {
|
|||||||
bf := bytes.NewBuffer(t)
|
bf := bytes.NewBuffer(t)
|
||||||
r.req.Body = io.NopCloser(bf)
|
r.req.Body = io.NopCloser(bf)
|
||||||
r.req.ContentLength = int64(len(t))
|
r.req.ContentLength = int64(len(t))
|
||||||
|
case io.ReadCloser:
|
||||||
|
r.req.Body = t
|
||||||
|
case io.Reader:
|
||||||
|
r.req.Body = io.NopCloser(t)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unsupported request body type %T", t))
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@@ -141,7 +151,7 @@ func (r *Request) getResponse() (*http.Response, error) {
|
|||||||
}
|
}
|
||||||
} else if r.req.Method == "POST" && r.req.Body == nil && len(paramBody) > 0 {
|
} else if r.req.Method == "POST" && r.req.Body == nil && len(paramBody) > 0 {
|
||||||
r.Header("Content-Type", "application/x-www-form-urlencoded")
|
r.Header("Content-Type", "application/x-www-form-urlencoded")
|
||||||
r.Body(paramBody)
|
r.Body(paramBody) // string
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@@ -185,7 +195,11 @@ func (r *Request) getResponse() (*http.Response, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Response executes request client gets response manually.
|
// Response executes request client gets response manually.
|
||||||
|
// Caller MUST close the response body if no error occurs
|
||||||
func (r *Request) Response() (*http.Response, error) {
|
func (r *Request) Response() (*http.Response, error) {
|
||||||
|
if r == nil {
|
||||||
|
return nil, errors.New("invalid request")
|
||||||
|
}
|
||||||
return r.getResponse()
|
return r.getResponse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import (
|
|||||||
"github.com/blevesearch/bleve/v2"
|
"github.com/blevesearch/bleve/v2"
|
||||||
analyzer_custom "github.com/blevesearch/bleve/v2/analysis/analyzer/custom"
|
analyzer_custom "github.com/blevesearch/bleve/v2/analysis/analyzer/custom"
|
||||||
analyzer_keyword "github.com/blevesearch/bleve/v2/analysis/analyzer/keyword"
|
analyzer_keyword "github.com/blevesearch/bleve/v2/analysis/analyzer/keyword"
|
||||||
"github.com/blevesearch/bleve/v2/analysis/token/camelcase"
|
|
||||||
"github.com/blevesearch/bleve/v2/analysis/token/lowercase"
|
"github.com/blevesearch/bleve/v2/analysis/token/lowercase"
|
||||||
"github.com/blevesearch/bleve/v2/analysis/token/unicodenorm"
|
"github.com/blevesearch/bleve/v2/analysis/token/unicodenorm"
|
||||||
"github.com/blevesearch/bleve/v2/analysis/tokenizer/letter"
|
"github.com/blevesearch/bleve/v2/analysis/tokenizer/letter"
|
||||||
@@ -70,7 +69,7 @@ const (
|
|||||||
filenameIndexerAnalyzer = "filenameIndexerAnalyzer"
|
filenameIndexerAnalyzer = "filenameIndexerAnalyzer"
|
||||||
filenameIndexerTokenizer = "filenameIndexerTokenizer"
|
filenameIndexerTokenizer = "filenameIndexerTokenizer"
|
||||||
repoIndexerDocType = "repoIndexerDocType"
|
repoIndexerDocType = "repoIndexerDocType"
|
||||||
repoIndexerLatestVersion = 8
|
repoIndexerLatestVersion = 9
|
||||||
)
|
)
|
||||||
|
|
||||||
// generateBleveIndexMapping generates a bleve index mapping for the repo indexer
|
// generateBleveIndexMapping generates a bleve index mapping for the repo indexer
|
||||||
@@ -107,7 +106,7 @@ func generateBleveIndexMapping() (mapping.IndexMapping, error) {
|
|||||||
"type": analyzer_custom.Name,
|
"type": analyzer_custom.Name,
|
||||||
"char_filters": []string{},
|
"char_filters": []string{},
|
||||||
"tokenizer": letter.Name,
|
"tokenizer": letter.Name,
|
||||||
"token_filters": []string{unicodeNormalizeName, camelcase.Name, lowercase.Name},
|
"token_filters": []string{unicodeNormalizeName, lowercase.Name},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -266,7 +265,7 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
|
|||||||
pathQuery.FieldVal = "Filename"
|
pathQuery.FieldVal = "Filename"
|
||||||
pathQuery.SetBoost(10)
|
pathQuery.SetBoost(10)
|
||||||
|
|
||||||
contentQuery := bleve.NewMatchQuery(opts.Keyword)
|
contentQuery := bleve.NewMatchPhraseQuery(opts.Keyword)
|
||||||
contentQuery.FieldVal = "Content"
|
contentQuery.FieldVal = "Content"
|
||||||
|
|
||||||
if opts.IsKeywordFuzzy {
|
if opts.IsKeywordFuzzy {
|
||||||
|
|||||||
@@ -165,35 +165,6 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Search for matches on the contents of files within the repo '62'.
|
|
||||||
// This scenario yields two results (both are based on contents, the first one is an exact match where as the second is a 'fuzzy' one)
|
|
||||||
{
|
|
||||||
RepoIDs: []int64{62},
|
|
||||||
Keyword: "This is not cheese",
|
|
||||||
Langs: 1,
|
|
||||||
Results: []codeSearchResult{
|
|
||||||
{
|
|
||||||
Filename: "potato/ham.md",
|
|
||||||
Content: "This is not cheese",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Filename: "ham.md",
|
|
||||||
Content: "This is also not cheese",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Search for matches on the contents of files regardless of case.
|
|
||||||
{
|
|
||||||
RepoIDs: nil,
|
|
||||||
Keyword: "dESCRIPTION",
|
|
||||||
Langs: 1,
|
|
||||||
Results: []codeSearchResult{
|
|
||||||
{
|
|
||||||
Filename: "README.md",
|
|
||||||
Content: "# repo1\n\nDescription for repo1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Search for an exact match on the filename within the repo '62' (case insenstive).
|
// Search for an exact match on the filename within the repo '62' (case insenstive).
|
||||||
// This scenario yields a single result (the file avocado.md on the repo '62')
|
// This scenario yields a single result (the file avocado.md on the repo '62')
|
||||||
{
|
{
|
||||||
@@ -233,6 +204,47 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if name == "elastic_search" {
|
||||||
|
// Additional scenarios for elastic_search only
|
||||||
|
additional := []struct {
|
||||||
|
RepoIDs []int64
|
||||||
|
Keyword string
|
||||||
|
Langs int
|
||||||
|
Results []codeSearchResult
|
||||||
|
}{
|
||||||
|
// Search for matches on the contents of files within the repo '62'.
|
||||||
|
// This scenario yields two results (both are based on contents, the first one is an exact match where as the second is a 'fuzzy' one)
|
||||||
|
{
|
||||||
|
RepoIDs: []int64{62},
|
||||||
|
Keyword: "This is not cheese",
|
||||||
|
Langs: 1,
|
||||||
|
Results: []codeSearchResult{
|
||||||
|
{
|
||||||
|
Filename: "potato/ham.md",
|
||||||
|
Content: "This is not cheese",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Filename: "ham.md",
|
||||||
|
Content: "This is also not cheese",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Search for matches on the contents of files regardless of case.
|
||||||
|
{
|
||||||
|
RepoIDs: nil,
|
||||||
|
Keyword: "dESCRIPTION",
|
||||||
|
Langs: 1,
|
||||||
|
Results: []codeSearchResult{
|
||||||
|
{
|
||||||
|
Filename: "README.md",
|
||||||
|
Content: "# repo1\n\nDescription for repo1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
keywords = append(keywords, additional...)
|
||||||
|
}
|
||||||
|
|
||||||
for _, kw := range keywords {
|
for _, kw := range keywords {
|
||||||
t.Run(kw.Keyword, func(t *testing.T) {
|
t.Run(kw.Keyword, func(t *testing.T) {
|
||||||
total, res, langs, err := indexer.Search(context.TODO(), &internal.SearchOptions{
|
total, res, langs, err := indexer.Search(context.TODO(), &internal.SearchOptions{
|
||||||
|
|||||||
@@ -73,9 +73,9 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m
|
|||||||
UpdatedBeforeUnix: options.UpdatedBeforeUnix.Value(),
|
UpdatedBeforeUnix: options.UpdatedBeforeUnix.Value(),
|
||||||
PriorityRepoID: 0,
|
PriorityRepoID: 0,
|
||||||
IsArchived: options.IsArchived,
|
IsArchived: options.IsArchived,
|
||||||
Org: nil,
|
Owner: nil,
|
||||||
Team: nil,
|
Team: nil,
|
||||||
User: nil,
|
Doer: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(options.MilestoneIDs) == 1 && options.MilestoneIDs[0] == 0 {
|
if len(options.MilestoneIDs) == 1 && options.MilestoneIDs[0] == 0 {
|
||||||
|
|||||||
@@ -92,6 +92,11 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD
|
|||||||
projectID = issue.Project.ID
|
projectID = issue.Project.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
projectColumnID, err := issue.ProjectColumnID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
return &internal.IndexerData{
|
return &internal.IndexerData{
|
||||||
ID: issue.ID,
|
ID: issue.ID,
|
||||||
RepoID: issue.RepoID,
|
RepoID: issue.RepoID,
|
||||||
@@ -106,7 +111,7 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD
|
|||||||
NoLabel: len(labels) == 0,
|
NoLabel: len(labels) == 0,
|
||||||
MilestoneID: issue.MilestoneID,
|
MilestoneID: issue.MilestoneID,
|
||||||
ProjectID: projectID,
|
ProjectID: projectID,
|
||||||
ProjectColumnID: issue.ProjectColumnID(ctx),
|
ProjectColumnID: projectColumnID,
|
||||||
PosterID: issue.PosterID,
|
PosterID: issue.PosterID,
|
||||||
AssigneeID: issue.AssigneeID,
|
AssigneeID: issue.AssigneeID,
|
||||||
MentionIDs: mentionIDs,
|
MentionIDs: mentionIDs,
|
||||||
|
|||||||
@@ -72,10 +72,14 @@ func (c *HTTPClient) batch(ctx context.Context, operation string, objects []Poin
|
|||||||
|
|
||||||
url := fmt.Sprintf("%s/objects/batch", c.endpoint)
|
url := fmt.Sprintf("%s/objects/batch", c.endpoint)
|
||||||
|
|
||||||
|
// Original: In some lfs server implementations, they require the ref attribute. #32838
|
||||||
// `ref` is an "optional object describing the server ref that the objects belong to"
|
// `ref` is an "optional object describing the server ref that the objects belong to"
|
||||||
// but some (incorrect) lfs servers require it, so maybe adding an empty ref here doesn't break the correct ones.
|
// but some (incorrect) lfs servers like aliyun require it, so maybe adding an empty ref here doesn't break the correct ones.
|
||||||
// https://github.com/git-lfs/git-lfs/blob/a32a02b44bf8a511aa14f047627c49e1a7fd5021/docs/api/batch.md?plain=1#L37
|
// https://github.com/git-lfs/git-lfs/blob/a32a02b44bf8a511aa14f047627c49e1a7fd5021/docs/api/batch.md?plain=1#L37
|
||||||
request := &BatchRequest{operation, c.transferNames(), &Reference{}, objects}
|
//
|
||||||
|
// UPDATE: it can't use "empty ref" here because it breaks others like https://github.com/go-gitea/gitea/issues/33453
|
||||||
|
request := &BatchRequest{operation, c.transferNames(), nil, objects}
|
||||||
|
|
||||||
payload := new(bytes.Buffer)
|
payload := new(bytes.Buffer)
|
||||||
err := json.NewEncoder(payload).Encode(request)
|
err := json.NewEncoder(payload).Encode(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
package backend
|
package backend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -29,7 +28,7 @@ var Capabilities = []string{
|
|||||||
"locking",
|
"locking",
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ transfer.Backend = &GiteaBackend{}
|
var _ transfer.Backend = (*GiteaBackend)(nil)
|
||||||
|
|
||||||
// GiteaBackend is an adapter between git-lfs-transfer library and Gitea's internal LFS API
|
// GiteaBackend is an adapter between git-lfs-transfer library and Gitea's internal LFS API
|
||||||
type GiteaBackend struct {
|
type GiteaBackend struct {
|
||||||
@@ -71,24 +70,23 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans
|
|||||||
g.logger.Log("json marshal error", err)
|
g.logger.Log("json marshal error", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
url := g.server.JoinPath("objects/batch").String()
|
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
headerAuthorization: g.authToken,
|
headerAuthorization: g.authToken,
|
||||||
headerGiteaInternalAuth: g.internalAuth,
|
headerGiteaInternalAuth: g.internalAuth,
|
||||||
headerAccept: mimeGitLFS,
|
headerAccept: mimeGitLFS,
|
||||||
headerContentType: mimeGitLFS,
|
headerContentType: mimeGitLFS,
|
||||||
}
|
}
|
||||||
req := newInternalRequest(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
req := newInternalRequestLFS(g.ctx, g.server.JoinPath("objects/batch").String(), http.MethodPost, headers, bodyBytes)
|
||||||
resp, err := req.Response()
|
resp, err := req.Response()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.logger.Log("http request error", err)
|
g.logger.Log("http request error", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
g.logger.Log("http statuscode error", resp.StatusCode, statusCodeToErr(resp.StatusCode))
|
g.logger.Log("http statuscode error", resp.StatusCode, statusCodeToErr(resp.StatusCode))
|
||||||
return nil, statusCodeToErr(resp.StatusCode)
|
return nil, statusCodeToErr(resp.StatusCode)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
respBytes, err := io.ReadAll(resp.Body)
|
respBytes, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.logger.Log("http read error", err)
|
g.logger.Log("http read error", err)
|
||||||
@@ -158,8 +156,7 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans
|
|||||||
return pointers, nil
|
return pointers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download implements transfer.Backend. The returned reader must be closed by the
|
// Download implements transfer.Backend. The returned reader must be closed by the caller.
|
||||||
// caller.
|
|
||||||
func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser, int64, error) {
|
func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser, int64, error) {
|
||||||
idMapStr, exists := args[argID]
|
idMapStr, exists := args[argID]
|
||||||
if !exists {
|
if !exists {
|
||||||
@@ -181,31 +178,30 @@ func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser,
|
|||||||
g.logger.Log("argument id incorrect")
|
g.logger.Log("argument id incorrect")
|
||||||
return nil, 0, transfer.ErrCorruptData
|
return nil, 0, transfer.ErrCorruptData
|
||||||
}
|
}
|
||||||
url := action.Href
|
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
headerAuthorization: g.authToken,
|
headerAuthorization: g.authToken,
|
||||||
headerGiteaInternalAuth: g.internalAuth,
|
headerGiteaInternalAuth: g.internalAuth,
|
||||||
headerAccept: mimeOctetStream,
|
headerAccept: mimeOctetStream,
|
||||||
}
|
}
|
||||||
req := newInternalRequest(g.ctx, url, http.MethodGet, headers, nil)
|
req := newInternalRequestLFS(g.ctx, toInternalLFSURL(action.Href), http.MethodGet, headers, nil)
|
||||||
resp, err := req.Response()
|
resp, err := req.Response()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, fmt.Errorf("failed to get response: %w", err)
|
||||||
}
|
}
|
||||||
|
// no need to close the body here by "defer resp.Body.Close()", see below
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return nil, 0, statusCodeToErr(resp.StatusCode)
|
return nil, 0, statusCodeToErr(resp.StatusCode)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
respBytes, err := io.ReadAll(resp.Body)
|
respSize, err := strconv.ParseInt(resp.Header.Get("X-Gitea-LFS-Content-Length"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, fmt.Errorf("failed to parse content length: %w", err)
|
||||||
}
|
}
|
||||||
respSize := int64(len(respBytes))
|
// transfer.Backend will check io.Closer interface and close this Body reader
|
||||||
respBuf := io.NopCloser(bytes.NewBuffer(respBytes))
|
return resp.Body, respSize, nil
|
||||||
return respBuf, respSize, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartUpload implements transfer.Backend.
|
// Upload implements transfer.Backend.
|
||||||
func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer.Args) error {
|
func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer.Args) error {
|
||||||
idMapStr, exists := args[argID]
|
idMapStr, exists := args[argID]
|
||||||
if !exists {
|
if !exists {
|
||||||
@@ -227,22 +223,20 @@ func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer
|
|||||||
g.logger.Log("argument id incorrect")
|
g.logger.Log("argument id incorrect")
|
||||||
return transfer.ErrCorruptData
|
return transfer.ErrCorruptData
|
||||||
}
|
}
|
||||||
url := action.Href
|
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
headerAuthorization: g.authToken,
|
headerAuthorization: g.authToken,
|
||||||
headerGiteaInternalAuth: g.internalAuth,
|
headerGiteaInternalAuth: g.internalAuth,
|
||||||
headerContentType: mimeOctetStream,
|
headerContentType: mimeOctetStream,
|
||||||
headerContentLength: strconv.FormatInt(size, 10),
|
headerContentLength: strconv.FormatInt(size, 10),
|
||||||
}
|
}
|
||||||
reqBytes, err := io.ReadAll(r)
|
|
||||||
if err != nil {
|
req := newInternalRequestLFS(g.ctx, toInternalLFSURL(action.Href), http.MethodPut, headers, nil)
|
||||||
return err
|
req.Body(r)
|
||||||
}
|
|
||||||
req := newInternalRequest(g.ctx, url, http.MethodPut, headers, reqBytes)
|
|
||||||
resp, err := req.Response()
|
resp, err := req.Response()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return statusCodeToErr(resp.StatusCode)
|
return statusCodeToErr(resp.StatusCode)
|
||||||
}
|
}
|
||||||
@@ -277,18 +271,18 @@ func (g *GiteaBackend) Verify(oid string, size int64, args transfer.Args) (trans
|
|||||||
// the server sent no verify action
|
// the server sent no verify action
|
||||||
return transfer.SuccessStatus(), nil
|
return transfer.SuccessStatus(), nil
|
||||||
}
|
}
|
||||||
url := action.Href
|
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
headerAuthorization: g.authToken,
|
headerAuthorization: g.authToken,
|
||||||
headerGiteaInternalAuth: g.internalAuth,
|
headerGiteaInternalAuth: g.internalAuth,
|
||||||
headerAccept: mimeGitLFS,
|
headerAccept: mimeGitLFS,
|
||||||
headerContentType: mimeGitLFS,
|
headerContentType: mimeGitLFS,
|
||||||
}
|
}
|
||||||
req := newInternalRequest(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
req := newInternalRequestLFS(g.ctx, toInternalLFSURL(action.Href), http.MethodPost, headers, bodyBytes)
|
||||||
resp, err := req.Response()
|
resp, err := req.Response()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return transfer.NewStatus(transfer.StatusInternalServerError), err
|
return transfer.NewStatus(transfer.StatusInternalServerError), err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return transfer.NewStatus(uint32(resp.StatusCode), http.StatusText(resp.StatusCode)), statusCodeToErr(resp.StatusCode)
|
return transfer.NewStatus(uint32(resp.StatusCode), http.StatusText(resp.StatusCode)), statusCodeToErr(resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,14 +43,13 @@ func (g *giteaLockBackend) Create(path, refname string) (transfer.Lock, error) {
|
|||||||
g.logger.Log("json marshal error", err)
|
g.logger.Log("json marshal error", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
url := g.server.String()
|
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
headerAuthorization: g.authToken,
|
headerAuthorization: g.authToken,
|
||||||
headerGiteaInternalAuth: g.internalAuth,
|
headerGiteaInternalAuth: g.internalAuth,
|
||||||
headerAccept: mimeGitLFS,
|
headerAccept: mimeGitLFS,
|
||||||
headerContentType: mimeGitLFS,
|
headerContentType: mimeGitLFS,
|
||||||
}
|
}
|
||||||
req := newInternalRequest(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
req := newInternalRequestLFS(g.ctx, g.server.String(), http.MethodPost, headers, bodyBytes)
|
||||||
resp, err := req.Response()
|
resp, err := req.Response()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.logger.Log("http request error", err)
|
g.logger.Log("http request error", err)
|
||||||
@@ -95,14 +94,13 @@ func (g *giteaLockBackend) Unlock(lock transfer.Lock) error {
|
|||||||
g.logger.Log("json marshal error", err)
|
g.logger.Log("json marshal error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
url := g.server.JoinPath(lock.ID(), "unlock").String()
|
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
headerAuthorization: g.authToken,
|
headerAuthorization: g.authToken,
|
||||||
headerGiteaInternalAuth: g.internalAuth,
|
headerGiteaInternalAuth: g.internalAuth,
|
||||||
headerAccept: mimeGitLFS,
|
headerAccept: mimeGitLFS,
|
||||||
headerContentType: mimeGitLFS,
|
headerContentType: mimeGitLFS,
|
||||||
}
|
}
|
||||||
req := newInternalRequest(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
req := newInternalRequestLFS(g.ctx, g.server.JoinPath(lock.ID(), "unlock").String(), http.MethodPost, headers, bodyBytes)
|
||||||
resp, err := req.Response()
|
resp, err := req.Response()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.logger.Log("http request error", err)
|
g.logger.Log("http request error", err)
|
||||||
@@ -176,16 +174,15 @@ func (g *giteaLockBackend) Range(cursor string, limit int, iter func(transfer.Lo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *giteaLockBackend) queryLocks(v url.Values) ([]transfer.Lock, string, error) {
|
func (g *giteaLockBackend) queryLocks(v url.Values) ([]transfer.Lock, string, error) {
|
||||||
urlq := g.server.JoinPath() // get a copy
|
serverURLWithQuery := g.server.JoinPath() // get a copy
|
||||||
urlq.RawQuery = v.Encode()
|
serverURLWithQuery.RawQuery = v.Encode()
|
||||||
url := urlq.String()
|
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
headerAuthorization: g.authToken,
|
headerAuthorization: g.authToken,
|
||||||
headerGiteaInternalAuth: g.internalAuth,
|
headerGiteaInternalAuth: g.internalAuth,
|
||||||
headerAccept: mimeGitLFS,
|
headerAccept: mimeGitLFS,
|
||||||
headerContentType: mimeGitLFS,
|
headerContentType: mimeGitLFS,
|
||||||
}
|
}
|
||||||
req := newInternalRequest(g.ctx, url, http.MethodGet, headers, nil)
|
req := newInternalRequestLFS(g.ctx, serverURLWithQuery.String(), http.MethodGet, headers, nil)
|
||||||
resp, err := req.Response()
|
resp, err := req.Response()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.logger.Log("http request error", err)
|
g.logger.Log("http request error", err)
|
||||||
|
|||||||
@@ -5,15 +5,16 @@ package backend
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/httplib"
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/proxyprotocol"
|
"code.gitea.io/gitea/modules/private"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/charmbracelet/git-lfs-transfer/transfer"
|
"github.com/charmbracelet/git-lfs-transfer/transfer"
|
||||||
)
|
)
|
||||||
@@ -60,8 +61,7 @@ const (
|
|||||||
|
|
||||||
// Operations enum
|
// Operations enum
|
||||||
const (
|
const (
|
||||||
opNone = iota
|
opDownload = iota + 1
|
||||||
opDownload
|
|
||||||
opUpload
|
opUpload
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -89,53 +89,60 @@ func statusCodeToErr(code int) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newInternalRequest(ctx context.Context, url, method string, headers map[string]string, body []byte) *httplib.Request {
|
func toInternalLFSURL(s string) string {
|
||||||
req := httplib.NewRequest(url, method).
|
pos1 := strings.Index(s, "://")
|
||||||
SetContext(ctx).
|
if pos1 == -1 {
|
||||||
SetTimeout(10*time.Second, 60*time.Second).
|
return ""
|
||||||
SetTLSClientConfig(&tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
if setting.Protocol == setting.HTTPUnix {
|
|
||||||
req.SetTransport(&http.Transport{
|
|
||||||
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
|
||||||
var d net.Dialer
|
|
||||||
conn, err := d.DialContext(ctx, "unix", setting.HTTPAddr)
|
|
||||||
if err != nil {
|
|
||||||
return conn, err
|
|
||||||
}
|
|
||||||
if setting.LocalUseProxyProtocol {
|
|
||||||
if err = proxyprotocol.WriteLocalHeader(conn); err != nil {
|
|
||||||
_ = conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return conn, err
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else if setting.LocalUseProxyProtocol {
|
|
||||||
req.SetTransport(&http.Transport{
|
|
||||||
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
|
||||||
var d net.Dialer
|
|
||||||
conn, err := d.DialContext(ctx, network, address)
|
|
||||||
if err != nil {
|
|
||||||
return conn, err
|
|
||||||
}
|
|
||||||
if err = proxyprotocol.WriteLocalHeader(conn); err != nil {
|
|
||||||
_ = conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return conn, err
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
appSubURLWithSlash := setting.AppSubURL + "/"
|
||||||
|
pos2 := strings.Index(s[pos1+3:], appSubURLWithSlash)
|
||||||
|
if pos2 == -1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
routePath := s[pos1+3+pos2+len(appSubURLWithSlash):]
|
||||||
|
fields := strings.SplitN(routePath, "/", 3)
|
||||||
|
if len(fields) < 3 || !strings.HasPrefix(fields[2], "info/lfs") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return setting.LocalURL + "api/internal/repo/" + routePath
|
||||||
|
}
|
||||||
|
|
||||||
|
func isInternalLFSURL(s string) bool {
|
||||||
|
if !strings.HasPrefix(s, setting.LocalURL) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
u, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
routePath := util.PathJoinRelX(u.Path)
|
||||||
|
subRoutePath, cut := strings.CutPrefix(routePath, "api/internal/repo/")
|
||||||
|
if !cut {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
fields := strings.SplitN(subRoutePath, "/", 3)
|
||||||
|
if len(fields) < 3 || !strings.HasPrefix(fields[2], "info/lfs") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func newInternalRequestLFS(ctx context.Context, internalURL, method string, headers map[string]string, body any) *httplib.Request {
|
||||||
|
if !isInternalLFSURL(internalURL) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
req := private.NewInternalRequest(ctx, internalURL, method)
|
||||||
for k, v := range headers {
|
for k, v := range headers {
|
||||||
req.Header(k, v)
|
req.Header(k, v)
|
||||||
}
|
}
|
||||||
|
switch body := body.(type) {
|
||||||
req.Body(body)
|
case nil: // do nothing
|
||||||
|
case []byte:
|
||||||
|
req.Body(body) // []byte
|
||||||
|
case io.Reader:
|
||||||
|
req.Body(body) // io.Reader or io.ReadCloser
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unsupported request body type %T", body))
|
||||||
|
}
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|||||||
54
modules/lfstransfer/backend/util_test.go
Normal file
54
modules/lfstransfer/backend/util_test.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package backend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestToInternalLFSURL(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.LocalURL, "http://localurl/")()
|
||||||
|
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
|
||||||
|
cases := []struct {
|
||||||
|
url string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"http://appurl/any", ""},
|
||||||
|
{"http://appurl/sub/any", ""},
|
||||||
|
{"http://appurl/sub/owner/repo/any", ""},
|
||||||
|
{"http://appurl/sub/owner/repo/info/any", ""},
|
||||||
|
{"http://appurl/sub/owner/repo/info/lfs/any", "http://localurl/api/internal/repo/owner/repo/info/lfs/any"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
assert.Equal(t, c.expected, toInternalLFSURL(c.url), c.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsInternalLFSURL(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.LocalURL, "http://localurl/")()
|
||||||
|
defer test.MockVariableValue(&setting.InternalToken, "mock-token")()
|
||||||
|
cases := []struct {
|
||||||
|
url string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"", false},
|
||||||
|
{"http://otherurl/api/internal/repo/owner/repo/info/lfs/any", false},
|
||||||
|
{"http://localurl/api/internal/repo/owner/repo/info/lfs/any", true},
|
||||||
|
{"http://localurl/api/internal/repo/owner/repo/info", false},
|
||||||
|
{"http://localurl/api/internal/misc/owner/repo/info/lfs/any", false},
|
||||||
|
{"http://localurl/api/internal/owner/repo/info/lfs/any", false},
|
||||||
|
{"http://localurl/api/internal/foo/bar", false},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
req := newInternalRequestLFS(context.Background(), c.url, "GET", nil, nil)
|
||||||
|
assert.Equal(t, c.expected, req != nil, c.url)
|
||||||
|
assert.Equal(t, c.expected, isInternalLFSURL(c.url), c.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ type GlobalVarsType struct {
|
|||||||
LinkRegex *regexp.Regexp // fast matching a URL link, no any extra validation.
|
LinkRegex *regexp.Regexp // fast matching a URL link, no any extra validation.
|
||||||
}
|
}
|
||||||
|
|
||||||
var GlobalVars = sync.OnceValue[*GlobalVarsType](func() *GlobalVarsType {
|
var GlobalVars = sync.OnceValue(func() *GlobalVarsType {
|
||||||
v := &GlobalVarsType{}
|
v := &GlobalVarsType{}
|
||||||
v.wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
|
v.wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
|
||||||
v.LinkRegex, _ = xurls.StrictMatchingScheme("https?://")
|
v.LinkRegex, _ = xurls.StrictMatchingScheme("https?://")
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ type globalVarsType struct {
|
|||||||
nulCleaner *strings.Replacer
|
nulCleaner *strings.Replacer
|
||||||
}
|
}
|
||||||
|
|
||||||
var globalVars = sync.OnceValue[*globalVarsType](func() *globalVarsType {
|
var globalVars = sync.OnceValue(func() *globalVarsType {
|
||||||
v := &globalVarsType{}
|
v := &globalVarsType{}
|
||||||
// NOTE: All below regex matching do not perform any extra validation.
|
// NOTE: All below regex matching do not perform any extra validation.
|
||||||
// Thus a link is produced even if the linked entity does not exist.
|
// Thus a link is produced even if the linked entity does not exist.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
)
|
)
|
||||||
|
|
||||||
var reAttrClass = sync.OnceValue[*regexp.Regexp](func() *regexp.Regexp {
|
var reAttrClass = sync.OnceValue(func() *regexp.Regexp {
|
||||||
// TODO: it isn't a problem at the moment because our HTML contents are always well constructed
|
// TODO: it isn't a problem at the moment because our HTML contents are always well constructed
|
||||||
return regexp.MustCompile(`(<[^>]+)\s+class="([^"]+)"([^>]*>)`)
|
return regexp.MustCompile(`(<[^>]+)\s+class="([^"]+)"([^>]*>)`)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package markdown
|
package markdown
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"html/template"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/yuin/goldmark/ast"
|
"github.com/yuin/goldmark/ast"
|
||||||
@@ -29,9 +30,7 @@ func (n *Details) Kind() ast.NodeKind {
|
|||||||
|
|
||||||
// NewDetails returns a new Paragraph node.
|
// NewDetails returns a new Paragraph node.
|
||||||
func NewDetails() *Details {
|
func NewDetails() *Details {
|
||||||
return &Details{
|
return &Details{}
|
||||||
BaseBlock: ast.BaseBlock{},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Summary is a block that contains the summary of details block
|
// Summary is a block that contains the summary of details block
|
||||||
@@ -54,9 +53,7 @@ func (n *Summary) Kind() ast.NodeKind {
|
|||||||
|
|
||||||
// NewSummary returns a new Summary node.
|
// NewSummary returns a new Summary node.
|
||||||
func NewSummary() *Summary {
|
func NewSummary() *Summary {
|
||||||
return &Summary{
|
return &Summary{}
|
||||||
BaseBlock: ast.BaseBlock{},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TaskCheckBoxListItem is a block that represents a list item of a markdown block with a checkbox
|
// TaskCheckBoxListItem is a block that represents a list item of a markdown block with a checkbox
|
||||||
@@ -95,29 +92,6 @@ type Icon struct {
|
|||||||
Name []byte
|
Name []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dump implements Node.Dump .
|
|
||||||
func (n *Icon) Dump(source []byte, level int) {
|
|
||||||
m := map[string]string{}
|
|
||||||
m["Name"] = string(n.Name)
|
|
||||||
ast.DumpHelper(n, source, level, m, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// KindIcon is the NodeKind for Icon
|
|
||||||
var KindIcon = ast.NewNodeKind("Icon")
|
|
||||||
|
|
||||||
// Kind implements Node.Kind.
|
|
||||||
func (n *Icon) Kind() ast.NodeKind {
|
|
||||||
return KindIcon
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIcon returns a new Paragraph node.
|
|
||||||
func NewIcon(name string) *Icon {
|
|
||||||
return &Icon{
|
|
||||||
BaseInline: ast.BaseInline{},
|
|
||||||
Name: []byte(name),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ColorPreview is an inline for a color preview
|
// ColorPreview is an inline for a color preview
|
||||||
type ColorPreview struct {
|
type ColorPreview struct {
|
||||||
ast.BaseInline
|
ast.BaseInline
|
||||||
@@ -175,3 +149,24 @@ func NewAttention(attentionType string) *Attention {
|
|||||||
AttentionType: attentionType,
|
AttentionType: attentionType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var KindRawHTML = ast.NewNodeKind("RawHTML")
|
||||||
|
|
||||||
|
type RawHTML struct {
|
||||||
|
ast.BaseBlock
|
||||||
|
rawHTML template.HTML
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *RawHTML) Dump(source []byte, level int) {
|
||||||
|
m := map[string]string{}
|
||||||
|
m["RawHTML"] = string(n.rawHTML)
|
||||||
|
ast.DumpHelper(n, source, level, m, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *RawHTML) Kind() ast.NodeKind {
|
||||||
|
return KindRawHTML
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRawHTML(rawHTML template.HTML) *RawHTML {
|
||||||
|
return &RawHTML{rawHTML: rawHTML}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,23 +4,22 @@
|
|||||||
package markdown
|
package markdown
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/htmlutil"
|
||||||
|
"code.gitea.io/gitea/modules/svg"
|
||||||
|
|
||||||
"github.com/yuin/goldmark/ast"
|
"github.com/yuin/goldmark/ast"
|
||||||
east "github.com/yuin/goldmark/extension/ast"
|
east "github.com/yuin/goldmark/extension/ast"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func nodeToTable(meta *yaml.Node) ast.Node {
|
func nodeToTable(meta *yaml.Node) ast.Node {
|
||||||
for {
|
for meta != nil && meta.Kind == yaml.DocumentNode {
|
||||||
if meta == nil {
|
meta = meta.Content[0]
|
||||||
return nil
|
}
|
||||||
}
|
if meta == nil {
|
||||||
switch meta.Kind {
|
return nil
|
||||||
case yaml.DocumentNode:
|
|
||||||
meta = meta.Content[0]
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
switch meta.Kind {
|
switch meta.Kind {
|
||||||
case yaml.MappingNode:
|
case yaml.MappingNode:
|
||||||
@@ -72,12 +71,28 @@ func sequenceNodeToTable(meta *yaml.Node) ast.Node {
|
|||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeToDetails(meta *yaml.Node, icon string) ast.Node {
|
func nodeToDetails(g *ASTTransformer, meta *yaml.Node) ast.Node {
|
||||||
|
for meta != nil && meta.Kind == yaml.DocumentNode {
|
||||||
|
meta = meta.Content[0]
|
||||||
|
}
|
||||||
|
if meta == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if meta.Kind != yaml.MappingNode {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var keys []string
|
||||||
|
for i := 0; i < len(meta.Content); i += 2 {
|
||||||
|
if meta.Content[i].Kind == yaml.ScalarNode {
|
||||||
|
keys = append(keys, meta.Content[i].Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
details := NewDetails()
|
details := NewDetails()
|
||||||
|
details.SetAttributeString(g.renderInternal.SafeAttr("class"), g.renderInternal.SafeValue("frontmatter-content"))
|
||||||
summary := NewSummary()
|
summary := NewSummary()
|
||||||
summary.AppendChild(summary, NewIcon(icon))
|
summaryInnerHTML := htmlutil.HTMLFormat("%s %s", svg.RenderHTML("octicon-table", 12), strings.Join(keys, ", "))
|
||||||
|
summary.AppendChild(summary, NewRawHTML(summaryInnerHTML))
|
||||||
details.AppendChild(details, summary)
|
details.AppendChild(details, summary)
|
||||||
details.AppendChild(details, nodeToTable(meta))
|
details.AppendChild(details, nodeToTable(meta))
|
||||||
|
|
||||||
return details
|
return details
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,6 @@ package markdown
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
@@ -51,7 +48,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
|
|||||||
|
|
||||||
tocList := make([]Header, 0, 20)
|
tocList := make([]Header, 0, 20)
|
||||||
if rc.yamlNode != nil {
|
if rc.yamlNode != nil {
|
||||||
metaNode := rc.toMetaNode()
|
metaNode := rc.toMetaNode(g)
|
||||||
if metaNode != nil {
|
if metaNode != nil {
|
||||||
node.InsertBefore(node, firstChild, metaNode)
|
node.InsertBefore(node, firstChild, metaNode)
|
||||||
}
|
}
|
||||||
@@ -111,11 +108,6 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// it is copied from old code, which is quite doubtful whether it is correct
|
|
||||||
var reValidIconName = sync.OnceValue[*regexp.Regexp](func() *regexp.Regexp {
|
|
||||||
return regexp.MustCompile(`^[-\w]+$`) // old: regexp.MustCompile("^[a-z ]+$")
|
|
||||||
})
|
|
||||||
|
|
||||||
// NewHTMLRenderer creates a HTMLRenderer to render in the gitea form.
|
// NewHTMLRenderer creates a HTMLRenderer to render in the gitea form.
|
||||||
func NewHTMLRenderer(renderInternal *internal.RenderInternal, opts ...html.Option) renderer.NodeRenderer {
|
func NewHTMLRenderer(renderInternal *internal.RenderInternal, opts ...html.Option) renderer.NodeRenderer {
|
||||||
r := &HTMLRenderer{
|
r := &HTMLRenderer{
|
||||||
@@ -140,11 +132,11 @@ func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
|||||||
reg.Register(ast.KindDocument, r.renderDocument)
|
reg.Register(ast.KindDocument, r.renderDocument)
|
||||||
reg.Register(KindDetails, r.renderDetails)
|
reg.Register(KindDetails, r.renderDetails)
|
||||||
reg.Register(KindSummary, r.renderSummary)
|
reg.Register(KindSummary, r.renderSummary)
|
||||||
reg.Register(KindIcon, r.renderIcon)
|
|
||||||
reg.Register(ast.KindCodeSpan, r.renderCodeSpan)
|
reg.Register(ast.KindCodeSpan, r.renderCodeSpan)
|
||||||
reg.Register(KindAttention, r.renderAttention)
|
reg.Register(KindAttention, r.renderAttention)
|
||||||
reg.Register(KindTaskCheckBoxListItem, r.renderTaskCheckBoxListItem)
|
reg.Register(KindTaskCheckBoxListItem, r.renderTaskCheckBoxListItem)
|
||||||
reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox)
|
reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox)
|
||||||
|
reg.Register(KindRawHTML, r.renderRawHTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
@@ -206,30 +198,14 @@ func (r *HTMLRenderer) renderSummary(w util.BufWriter, source []byte, node ast.N
|
|||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *HTMLRenderer) renderIcon(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *HTMLRenderer) renderRawHTML(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
if !entering {
|
if !entering {
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
n := node.(*RawHTML)
|
||||||
n := node.(*Icon)
|
_, err := w.WriteString(string(r.renderInternal.ProtectSafeAttrs(n.rawHTML)))
|
||||||
|
|
||||||
name := strings.TrimSpace(strings.ToLower(string(n.Name)))
|
|
||||||
|
|
||||||
if len(name) == 0 {
|
|
||||||
// skip this
|
|
||||||
return ast.WalkContinue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reValidIconName().MatchString(name) {
|
|
||||||
// skip this
|
|
||||||
return ast.WalkContinue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: the "icon xxx" is from Fomantic UI, it's really questionable whether it still works correctly
|
|
||||||
err := r.renderInternal.FormatWithSafeAttrs(w, `<i class="icon %s"></i>`, name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ast.WalkStop, err
|
return ast.WalkStop, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,21 +159,7 @@ func render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
|
|||||||
limit: setting.UI.MaxDisplayFileSize * 3,
|
limit: setting.UI.MaxDisplayFileSize * 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: should we include a timeout to abort the renderer if it takes too long?
|
|
||||||
defer func() {
|
|
||||||
err := recover()
|
|
||||||
if err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Warn("Unable to render markdown due to panic in goldmark: %v", err)
|
|
||||||
if (!setting.IsProd && !setting.IsInTesting) || log.IsDebug() {
|
|
||||||
log.Error("Panic in markdown: %v\n%s", err, log.Stack(2))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// FIXME: Don't read all to memory, but goldmark doesn't support
|
// FIXME: Don't read all to memory, but goldmark doesn't support
|
||||||
pc := newParserContext(ctx)
|
|
||||||
buf, err := io.ReadAll(input)
|
buf, err := io.ReadAll(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to ReadAll: %v", err)
|
log.Error("Unable to ReadAll: %v", err)
|
||||||
@@ -181,14 +167,24 @@ func render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
|
|||||||
}
|
}
|
||||||
buf = giteautil.NormalizeEOL(buf)
|
buf = giteautil.NormalizeEOL(buf)
|
||||||
|
|
||||||
|
// FIXME: should we include a timeout to abort the renderer if it takes too long?
|
||||||
|
defer func() {
|
||||||
|
err := recover()
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Error("Panic in markdown: %v\n%s", err, log.Stack(2))
|
||||||
|
escapedHTML := template.HTMLEscapeString(giteautil.UnsafeBytesToString(buf))
|
||||||
|
_, _ = output.Write(giteautil.UnsafeStringToBytes(escapedHTML))
|
||||||
|
}()
|
||||||
|
|
||||||
|
pc := newParserContext(ctx)
|
||||||
|
|
||||||
// Preserve original length.
|
// Preserve original length.
|
||||||
bufWithMetadataLength := len(buf)
|
bufWithMetadataLength := len(buf)
|
||||||
|
|
||||||
rc := &RenderConfig{
|
rc := &RenderConfig{Meta: markup.RenderMetaAsDetails}
|
||||||
Meta: markup.RenderMetaAsDetails,
|
|
||||||
Icon: "table",
|
|
||||||
Lang: "",
|
|
||||||
}
|
|
||||||
buf, _ = ExtractMetadataBytes(buf, rc)
|
buf, _ = ExtractMetadataBytes(buf, rc)
|
||||||
|
|
||||||
metaLength := bufWithMetadataLength - len(buf)
|
metaLength := bufWithMetadataLength - len(buf)
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ func TestAttention(t *testing.T) {
|
|||||||
defer svg.MockIcon("octicon-alert")()
|
defer svg.MockIcon("octicon-alert")()
|
||||||
defer svg.MockIcon("octicon-stop")()
|
defer svg.MockIcon("octicon-stop")()
|
||||||
|
|
||||||
|
test := func(input, expected string) {
|
||||||
|
result, err := markdown.RenderString(markup.NewTestRenderContext(), input)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(result)))
|
||||||
|
}
|
||||||
renderAttention := func(attention, icon string) string {
|
renderAttention := func(attention, icon string) string {
|
||||||
tmpl := `<blockquote class="attention-header attention-{attention}"><p><svg class="attention-icon attention-{attention} svg {icon}" width="16" height="16"></svg><strong class="attention-{attention}">{Attention}</strong></p>`
|
tmpl := `<blockquote class="attention-header attention-{attention}"><p><svg class="attention-icon attention-{attention} svg {icon}" width="16" height="16"></svg><strong class="attention-{attention}">{Attention}</strong></p>`
|
||||||
tmpl = strings.ReplaceAll(tmpl, "{attention}", attention)
|
tmpl = strings.ReplaceAll(tmpl, "{attention}", attention)
|
||||||
@@ -31,12 +36,6 @@ func TestAttention(t *testing.T) {
|
|||||||
return tmpl
|
return tmpl
|
||||||
}
|
}
|
||||||
|
|
||||||
test := func(input, expected string) {
|
|
||||||
result, err := markdown.RenderString(markup.NewTestRenderContext(), input)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(result)))
|
|
||||||
}
|
|
||||||
|
|
||||||
test(`
|
test(`
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> text
|
> text
|
||||||
@@ -53,4 +52,7 @@ func TestAttention(t *testing.T) {
|
|||||||
|
|
||||||
// legacy GitHub style
|
// legacy GitHub style
|
||||||
test(`> **warning**`, renderAttention("warning", "octicon-alert")+"\n</blockquote>")
|
test(`> **warning**`, renderAttention("warning", "octicon-alert")+"\n</blockquote>")
|
||||||
|
|
||||||
|
// edge case (it used to cause panic)
|
||||||
|
test(">\ntext", "<blockquote>\n</blockquote>\n<p>text</p>")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -383,18 +383,74 @@ func TestColorPreview(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTaskList(t *testing.T) {
|
func TestMarkdownFrontmatter(t *testing.T) {
|
||||||
testcases := []struct {
|
testcases := []struct {
|
||||||
testcase string
|
name string
|
||||||
|
input string
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
"MapInFrontmatter",
|
||||||
|
`---
|
||||||
|
key1: val1
|
||||||
|
key2: val2
|
||||||
|
---
|
||||||
|
test
|
||||||
|
`,
|
||||||
|
`<details class="frontmatter-content"><summary><span>octicon-table(12/)</span> key1, key2</summary><table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>key1</th>
|
||||||
|
<th>key2</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>val1</td>
|
||||||
|
<td>val2</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</details><p>test</p>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"ListInFrontmatter",
|
||||||
|
`---
|
||||||
|
- item1
|
||||||
|
- item2
|
||||||
|
---
|
||||||
|
test
|
||||||
|
`,
|
||||||
|
`- item1
|
||||||
|
- item2
|
||||||
|
|
||||||
|
<p>test</p>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"StringInFrontmatter",
|
||||||
|
`---
|
||||||
|
anything
|
||||||
|
---
|
||||||
|
test
|
||||||
|
`,
|
||||||
|
`anything
|
||||||
|
|
||||||
|
<p>test</p>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
// data-source-position should take into account YAML frontmatter.
|
// data-source-position should take into account YAML frontmatter.
|
||||||
|
"ListAfterFrontmatter",
|
||||||
`---
|
`---
|
||||||
foo: bar
|
foo: bar
|
||||||
---
|
---
|
||||||
- [ ] task 1`,
|
- [ ] task 1`,
|
||||||
`<details><summary><i class="icon table"></i></summary><table>
|
`<details class="frontmatter-content"><summary><span>octicon-table(12/)</span> foo</summary><table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>foo</th>
|
<th>foo</th>
|
||||||
@@ -414,9 +470,9 @@ foo: bar
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testcases {
|
for _, test := range testcases {
|
||||||
res, err := markdown.RenderString(markup.NewTestRenderContext(), test.testcase)
|
res, err := markdown.RenderString(markup.NewTestRenderContext(), test.input)
|
||||||
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
|
assert.NoError(t, err, "Unexpected error in testcase: %q", test.name)
|
||||||
assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
|
assert.Equal(t, test.expected, string(res), "Unexpected result in testcase %q", test.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
// RenderConfig represents rendering configuration for this file
|
// RenderConfig represents rendering configuration for this file
|
||||||
type RenderConfig struct {
|
type RenderConfig struct {
|
||||||
Meta markup.RenderMetaMode
|
Meta markup.RenderMetaMode
|
||||||
Icon string
|
|
||||||
TOC string // "false": hide, "side"/empty: in sidebar, "main"/"true": in main view
|
TOC string // "false": hide, "side"/empty: in sidebar, "main"/"true": in main view
|
||||||
Lang string
|
Lang string
|
||||||
yamlNode *yaml.Node
|
yamlNode *yaml.Node
|
||||||
@@ -74,7 +73,7 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
|
|
||||||
type yamlRenderConfig struct {
|
type yamlRenderConfig struct {
|
||||||
Meta *string `yaml:"meta"`
|
Meta *string `yaml:"meta"`
|
||||||
Icon *string `yaml:"details_icon"`
|
Icon *string `yaml:"details_icon"` // deprecated, because there is no font icon, so no custom icon
|
||||||
TOC *string `yaml:"include_toc"`
|
TOC *string `yaml:"include_toc"`
|
||||||
Lang *string `yaml:"lang"`
|
Lang *string `yaml:"lang"`
|
||||||
}
|
}
|
||||||
@@ -96,10 +95,6 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
rc.Meta = renderMetaModeFromString(*cfg.Gitea.Meta)
|
rc.Meta = renderMetaModeFromString(*cfg.Gitea.Meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Gitea.Icon != nil {
|
|
||||||
rc.Icon = strings.TrimSpace(strings.ToLower(*cfg.Gitea.Icon))
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Gitea.Lang != nil && *cfg.Gitea.Lang != "" {
|
if cfg.Gitea.Lang != nil && *cfg.Gitea.Lang != "" {
|
||||||
rc.Lang = *cfg.Gitea.Lang
|
rc.Lang = *cfg.Gitea.Lang
|
||||||
}
|
}
|
||||||
@@ -111,7 +106,7 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RenderConfig) toMetaNode() ast.Node {
|
func (rc *RenderConfig) toMetaNode(g *ASTTransformer) ast.Node {
|
||||||
if rc.yamlNode == nil {
|
if rc.yamlNode == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -119,7 +114,7 @@ func (rc *RenderConfig) toMetaNode() ast.Node {
|
|||||||
case markup.RenderMetaAsTable:
|
case markup.RenderMetaAsTable:
|
||||||
return nodeToTable(rc.yamlNode)
|
return nodeToTable(rc.yamlNode)
|
||||||
case markup.RenderMetaAsDetails:
|
case markup.RenderMetaAsDetails:
|
||||||
return nodeToDetails(rc.yamlNode, rc.Icon)
|
return nodeToDetails(g, rc.yamlNode)
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,42 +20,36 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"empty", &RenderConfig{
|
"empty", &RenderConfig{
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
Lang: "",
|
Lang: "",
|
||||||
}, "",
|
}, "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"lang", &RenderConfig{
|
"lang", &RenderConfig{
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
Lang: "test",
|
Lang: "test",
|
||||||
}, "lang: test",
|
}, "lang: test",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"metatable", &RenderConfig{
|
"metatable", &RenderConfig{
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
Lang: "",
|
Lang: "",
|
||||||
}, "gitea: table",
|
}, "gitea: table",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"metanone", &RenderConfig{
|
"metanone", &RenderConfig{
|
||||||
Meta: "none",
|
Meta: "none",
|
||||||
Icon: "table",
|
|
||||||
Lang: "",
|
Lang: "",
|
||||||
}, "gitea: none",
|
}, "gitea: none",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"metadetails", &RenderConfig{
|
"metadetails", &RenderConfig{
|
||||||
Meta: "details",
|
Meta: "details",
|
||||||
Icon: "table",
|
|
||||||
Lang: "",
|
Lang: "",
|
||||||
}, "gitea: details",
|
}, "gitea: details",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"metawrong", &RenderConfig{
|
"metawrong", &RenderConfig{
|
||||||
Meta: "details",
|
Meta: "details",
|
||||||
Icon: "table",
|
|
||||||
Lang: "",
|
Lang: "",
|
||||||
}, "gitea: wrong",
|
}, "gitea: wrong",
|
||||||
},
|
},
|
||||||
@@ -62,7 +57,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||||||
"toc", &RenderConfig{
|
"toc", &RenderConfig{
|
||||||
TOC: "true",
|
TOC: "true",
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
Lang: "",
|
Lang: "",
|
||||||
}, "include_toc: true",
|
}, "include_toc: true",
|
||||||
},
|
},
|
||||||
@@ -70,14 +64,12 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||||||
"tocfalse", &RenderConfig{
|
"tocfalse", &RenderConfig{
|
||||||
TOC: "false",
|
TOC: "false",
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
Lang: "",
|
Lang: "",
|
||||||
}, "include_toc: false",
|
}, "include_toc: false",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"toclang", &RenderConfig{
|
"toclang", &RenderConfig{
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
TOC: "true",
|
TOC: "true",
|
||||||
Lang: "testlang",
|
Lang: "testlang",
|
||||||
}, `
|
}, `
|
||||||
@@ -88,7 +80,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"complexlang", &RenderConfig{
|
"complexlang", &RenderConfig{
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
Lang: "testlang",
|
Lang: "testlang",
|
||||||
}, `
|
}, `
|
||||||
gitea:
|
gitea:
|
||||||
@@ -98,7 +89,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"complexlang2", &RenderConfig{
|
"complexlang2", &RenderConfig{
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
Lang: "testlang",
|
Lang: "testlang",
|
||||||
}, `
|
}, `
|
||||||
lang: notright
|
lang: notright
|
||||||
@@ -109,7 +99,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"complexlang", &RenderConfig{
|
"complexlang", &RenderConfig{
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
Lang: "testlang",
|
Lang: "testlang",
|
||||||
}, `
|
}, `
|
||||||
gitea:
|
gitea:
|
||||||
@@ -121,7 +110,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||||||
Lang: "two",
|
Lang: "two",
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
TOC: "true",
|
TOC: "true",
|
||||||
Icon: "smiley",
|
|
||||||
}, `
|
}, `
|
||||||
lang: one
|
lang: one
|
||||||
include_toc: true
|
include_toc: true
|
||||||
@@ -137,7 +125,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got := &RenderConfig{
|
got := &RenderConfig{
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
Lang: "",
|
Lang: "",
|
||||||
}
|
}
|
||||||
if err := yaml.Unmarshal([]byte(strings.ReplaceAll(tt.args, "\t", " ")), got); err != nil {
|
if err := yaml.Unmarshal([]byte(strings.ReplaceAll(tt.args, "\t", " ")), got); err != nil {
|
||||||
@@ -145,18 +132,9 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if got.Meta != tt.expected.Meta {
|
assert.Equal(t, tt.expected.Meta, got.Meta)
|
||||||
t.Errorf("Meta Expected %s Got %s", tt.expected.Meta, got.Meta)
|
assert.Equal(t, tt.expected.Lang, got.Lang)
|
||||||
}
|
assert.Equal(t, tt.expected.TOC, got.TOC)
|
||||||
if got.Icon != tt.expected.Icon {
|
|
||||||
t.Errorf("Icon Expected %s Got %s", tt.expected.Icon, got.Icon)
|
|
||||||
}
|
|
||||||
if got.Lang != tt.expected.Lang {
|
|
||||||
t.Errorf("Lang Expected %s Got %s", tt.expected.Lang, got.Lang)
|
|
||||||
}
|
|
||||||
if got.TOC != tt.expected.TOC {
|
|
||||||
t.Errorf("TOC Expected %q Got %q", tt.expected.TOC, got.TOC)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,6 +115,9 @@ func (g *ASTTransformer) transformBlockquote(v *ast.Blockquote, reader text.Read
|
|||||||
|
|
||||||
// grab these nodes and make sure we adhere to the attention blockquote structure
|
// grab these nodes and make sure we adhere to the attention blockquote structure
|
||||||
firstParagraph := v.FirstChild()
|
firstParagraph := v.FirstChild()
|
||||||
|
if firstParagraph == nil {
|
||||||
|
return ast.WalkContinue, nil
|
||||||
|
}
|
||||||
g.applyElementDir(firstParagraph)
|
g.applyElementDir(firstParagraph)
|
||||||
|
|
||||||
attentionType, processedNodes := g.extractBlockquoteAttentionEmphasis(firstParagraph, reader)
|
attentionType, processedNodes := g.extractBlockquoteAttentionEmphasis(firstParagraph, reader)
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package markup_test
|
|
||||||
@@ -48,7 +48,7 @@ func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy {
|
|||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(unchecked|checked|indeterminate)$`)).OnElements("li")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(unchecked|checked|indeterminate)$`)).OnElements("li")
|
||||||
|
|
||||||
// Allow 'color' and 'background-color' properties for the style attribute on text elements.
|
// Allow 'color' and 'background-color' properties for the style attribute on text elements.
|
||||||
policy.AllowStyles("color", "background-color").OnElements("span", "p")
|
policy.AllowStyles("color", "background-color").OnElements("div", "span", "p", "tr", "th", "td")
|
||||||
|
|
||||||
policy.AllowAttrs("src", "autoplay", "controls").OnElements("video")
|
policy.AllowAttrs("src", "autoplay", "controls").OnElements("video")
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type GenerateTokenRequest struct {
|
|||||||
func GenerateActionsRunnerToken(ctx context.Context, scope string) (*ResponseText, ResponseExtra) {
|
func GenerateActionsRunnerToken(ctx context.Context, scope string) (*ResponseText, ResponseExtra) {
|
||||||
reqURL := setting.LocalURL + "api/internal/actions/generate_actions_runner_token"
|
reqURL := setting.LocalURL + "api/internal/actions/generate_actions_runner_token"
|
||||||
|
|
||||||
req := newInternalRequest(ctx, reqURL, "POST", GenerateTokenRequest{
|
req := newInternalRequestAPI(ctx, reqURL, "POST", GenerateTokenRequest{
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/repository"
|
"code.gitea.io/gitea/modules/repository"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
)
|
)
|
||||||
@@ -82,29 +82,32 @@ type HookProcReceiveRefResult struct {
|
|||||||
HeadBranch string
|
HeadBranch string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newInternalRequestAPIForHooks(ctx context.Context, hookName, ownerName, repoName string, opts HookOptions) *httplib.Request {
|
||||||
|
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/%s/%s/%s", hookName, url.PathEscape(ownerName), url.PathEscape(repoName))
|
||||||
|
req := newInternalRequestAPI(ctx, reqURL, "POST", opts)
|
||||||
|
// This "timeout" applies to http.Client's timeout: A Timeout of zero means no timeout.
|
||||||
|
// This "timeout" was previously set to `time.Duration(60+len(opts.OldCommitIDs))` seconds, but it caused unnecessary timeout failures.
|
||||||
|
// It should be good enough to remove the client side timeout, only respect the "ctx" and server side timeout.
|
||||||
|
req.SetReadWriteTimeout(0)
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
// HookPreReceive check whether the provided commits are allowed
|
// HookPreReceive check whether the provided commits are allowed
|
||||||
func HookPreReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) ResponseExtra {
|
func HookPreReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) ResponseExtra {
|
||||||
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
|
req := newInternalRequestAPIForHooks(ctx, "pre-receive", ownerName, repoName, opts)
|
||||||
req := newInternalRequest(ctx, reqURL, "POST", opts)
|
|
||||||
req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
|
|
||||||
_, extra := requestJSONResp(req, &ResponseText{})
|
_, extra := requestJSONResp(req, &ResponseText{})
|
||||||
return extra
|
return extra
|
||||||
}
|
}
|
||||||
|
|
||||||
// HookPostReceive updates services and users
|
// HookPostReceive updates services and users
|
||||||
func HookPostReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookPostReceiveResult, ResponseExtra) {
|
func HookPostReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookPostReceiveResult, ResponseExtra) {
|
||||||
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/post-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
|
req := newInternalRequestAPIForHooks(ctx, "post-receive", ownerName, repoName, opts)
|
||||||
req := newInternalRequest(ctx, reqURL, "POST", opts)
|
|
||||||
req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
|
|
||||||
return requestJSONResp(req, &HookPostReceiveResult{})
|
return requestJSONResp(req, &HookPostReceiveResult{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// HookProcReceive proc-receive hook
|
// HookProcReceive proc-receive hook
|
||||||
func HookProcReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookProcReceiveResult, ResponseExtra) {
|
func HookProcReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookProcReceiveResult, ResponseExtra) {
|
||||||
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/proc-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
|
req := newInternalRequestAPIForHooks(ctx, "proc-receive", ownerName, repoName, opts)
|
||||||
|
|
||||||
req := newInternalRequest(ctx, reqURL, "POST", opts)
|
|
||||||
req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
|
|
||||||
return requestJSONResp(req, &HookProcReceiveResult{})
|
return requestJSONResp(req, &HookProcReceiveResult{})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +118,7 @@ func SetDefaultBranch(ctx context.Context, ownerName, repoName, branch string) R
|
|||||||
url.PathEscape(repoName),
|
url.PathEscape(repoName),
|
||||||
url.PathEscape(branch),
|
url.PathEscape(branch),
|
||||||
)
|
)
|
||||||
req := newInternalRequest(ctx, reqURL, "POST")
|
req := newInternalRequestAPI(ctx, reqURL, "POST")
|
||||||
_, extra := requestJSONResp(req, &ResponseText{})
|
_, extra := requestJSONResp(req, &ResponseText{})
|
||||||
return extra
|
return extra
|
||||||
}
|
}
|
||||||
@@ -123,7 +126,7 @@ func SetDefaultBranch(ctx context.Context, ownerName, repoName, branch string) R
|
|||||||
// SSHLog sends ssh error log response
|
// SSHLog sends ssh error log response
|
||||||
func SSHLog(ctx context.Context, isErr bool, msg string) error {
|
func SSHLog(ctx context.Context, isErr bool, msg string) error {
|
||||||
reqURL := setting.LocalURL + "api/internal/ssh/log"
|
reqURL := setting.LocalURL + "api/internal/ssh/log"
|
||||||
req := newInternalRequest(ctx, reqURL, "POST", &SSHLogOption{IsError: isErr, Message: msg})
|
req := newInternalRequestAPI(ctx, reqURL, "POST", &SSHLogOption{IsError: isErr, Message: msg})
|
||||||
_, extra := requestJSONResp(req, &ResponseText{})
|
_, extra := requestJSONResp(req, &ResponseText{})
|
||||||
return extra.Error
|
return extra.Error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,12 +34,16 @@ func getClientIP() string {
|
|||||||
return strings.Fields(sshConnEnv)[0]
|
return strings.Fields(sshConnEnv)[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func newInternalRequest(ctx context.Context, url, method string, body ...any) *httplib.Request {
|
func NewInternalRequest(ctx context.Context, url, method string) *httplib.Request {
|
||||||
if setting.InternalToken == "" {
|
if setting.InternalToken == "" {
|
||||||
log.Fatal(`The INTERNAL_TOKEN setting is missing from the configuration file: %q.
|
log.Fatal(`The INTERNAL_TOKEN setting is missing from the configuration file: %q.
|
||||||
Ensure you are running in the correct environment or set the correct configuration file with -c.`, setting.CustomConf)
|
Ensure you are running in the correct environment or set the correct configuration file with -c.`, setting.CustomConf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(url, setting.LocalURL) {
|
||||||
|
log.Fatal("Invalid internal request URL: %q", url)
|
||||||
|
}
|
||||||
|
|
||||||
req := httplib.NewRequest(url, method).
|
req := httplib.NewRequest(url, method).
|
||||||
SetContext(ctx).
|
SetContext(ctx).
|
||||||
Header("X-Real-IP", getClientIP()).
|
Header("X-Real-IP", getClientIP()).
|
||||||
@@ -82,13 +86,17 @@ Ensure you are running in the correct environment or set the correct configurati
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func newInternalRequestAPI(ctx context.Context, url, method string, body ...any) *httplib.Request {
|
||||||
|
req := NewInternalRequest(ctx, url, method)
|
||||||
if len(body) == 1 {
|
if len(body) == 1 {
|
||||||
req.Header("Content-Type", "application/json")
|
req.Header("Content-Type", "application/json")
|
||||||
jsonBytes, _ := json.Marshal(body[0])
|
jsonBytes, _ := json.Marshal(body[0])
|
||||||
req.Body(jsonBytes)
|
req.Body(jsonBytes)
|
||||||
} else if len(body) > 1 {
|
} else if len(body) > 1 {
|
||||||
log.Fatal("Too many arguments for newInternalRequest")
|
log.Fatal("Too many arguments for newInternalRequestAPI")
|
||||||
}
|
}
|
||||||
|
|
||||||
req.SetTimeout(10*time.Second, 60*time.Second)
|
req.SetTimeout(10*time.Second, 60*time.Second)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
func UpdatePublicKeyInRepo(ctx context.Context, keyID, repoID int64) error {
|
func UpdatePublicKeyInRepo(ctx context.Context, keyID, repoID int64) error {
|
||||||
// Ask for running deliver hook and test pull request tasks.
|
// Ask for running deliver hook and test pull request tasks.
|
||||||
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/ssh/%d/update/%d", keyID, repoID)
|
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/ssh/%d/update/%d", keyID, repoID)
|
||||||
req := newInternalRequest(ctx, reqURL, "POST")
|
req := newInternalRequestAPI(ctx, reqURL, "POST")
|
||||||
_, extra := requestJSONResp(req, &ResponseText{})
|
_, extra := requestJSONResp(req, &ResponseText{})
|
||||||
return extra.Error
|
return extra.Error
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@ func UpdatePublicKeyInRepo(ctx context.Context, keyID, repoID int64) error {
|
|||||||
func AuthorizedPublicKeyByContent(ctx context.Context, content string) (*ResponseText, ResponseExtra) {
|
func AuthorizedPublicKeyByContent(ctx context.Context, content string) (*ResponseText, ResponseExtra) {
|
||||||
// Ask for running deliver hook and test pull request tasks.
|
// Ask for running deliver hook and test pull request tasks.
|
||||||
reqURL := setting.LocalURL + "api/internal/ssh/authorized_keys"
|
reqURL := setting.LocalURL + "api/internal/ssh/authorized_keys"
|
||||||
req := newInternalRequest(ctx, reqURL, "POST")
|
req := newInternalRequestAPI(ctx, reqURL, "POST")
|
||||||
req.Param("content", content)
|
req.Param("content", content)
|
||||||
return requestJSONResp(req, &ResponseText{})
|
return requestJSONResp(req, &ResponseText{})
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user