mirror of
https://codeberg.org/forgejo-aneksajo/forgejo-aneksajo.git
synced 2025-07-08 23:00:03 +02:00
Compare commits
308 commits
forgejo
...
v9.0.3-git
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0a767458c5 | ||
![]() |
676aa26f51 | ||
![]() |
7e26a31d40 | ||
![]() |
0060a01037 | ||
![]() |
d420606d5b | ||
![]() |
2156267087 | ||
![]() |
9397f06242 | ||
![]() |
c5cab5db55 | ||
![]() |
2aebfe3041 | ||
![]() |
ecadd69dc1 | ||
![]() |
06e14867ec | ||
![]() |
9b509264e4 | ||
![]() |
92694c3eb7 | ||
![]() |
5b10daf327 | ||
![]() |
0bd997ac3a | ||
![]() |
a930583ff3 | ||
![]() |
fedaedc758 | ||
![]() |
d42570d616 | ||
![]() |
67824b8917 | ||
![]() |
1b64d1a5be | ||
![]() |
60b3b755ae | ||
![]() |
c48a1bc3c1 | ||
![]() |
363748d684 | ||
![]() |
5ad3cd4f10 | ||
![]() |
522f17fc03 | ||
![]() |
8453d0f55a | ||
![]() |
f1dd61c94f | ||
![]() |
e25811bafd | ||
![]() |
dda21c92b9 | ||
![]() |
09c61ca291 | ||
![]() |
aba4fdb268 | ||
![]() |
25e0ad154f | ||
![]() |
a641732624 | ||
![]() |
e055a4e2b5 | ||
![]() |
42d2e534d8 | ||
![]() |
2561339dfa | ||
![]() |
fa7b28d0a7 | ||
![]() |
1917545953 | ||
![]() |
4cc80314c9 | ||
![]() |
4979f33677 | ||
![]() |
0d1f0e0c05 | ||
![]() |
d595393bb6 | ||
![]() |
83b614e1e8 | ||
![]() |
dc15aefa3e | ||
![]() |
29a0b0131e | ||
![]() |
71dc491a09 | ||
![]() |
d39571dc53 | ||
![]() |
591b1f54a3 | ||
![]() |
a5e26e3ad0 | ||
![]() |
8e3d076750 | ||
![]() |
2b66ddf352 | ||
![]() |
d8ca6f0581 | ||
![]() |
bf84528109 | ||
![]() |
485ffc4bae | ||
![]() |
bda9ee9b83 | ||
![]() |
f5a32bd4fd | ||
![]() |
02844b48c3 | ||
![]() |
3beefb29b9 | ||
![]() |
8fa76300ae | ||
![]() |
e741d0a068 | ||
![]() |
ad1aad7b1a | ||
![]() |
48fa9c96a7 | ||
![]() |
1de8d5b450 | ||
![]() |
0ce1c56612 | ||
![]() |
9b29e8add1 | ||
![]() |
4069e1c934 | ||
![]() |
74e1fd16b0 | ||
![]() |
532c35c25a | ||
![]() |
eeb3451a89 | ||
![]() |
fe6f4fde20 | ||
![]() |
c98bd3a11d | ||
![]() |
5cffc09c37 | ||
![]() |
d3e5d887ee | ||
![]() |
72cbefe63e | ||
![]() |
a167d7b91c | ||
![]() |
c5f37b6cd8 | ||
![]() |
a494510972 | ||
![]() |
714308506e | ||
![]() |
2374f8f47d | ||
![]() |
1f9a1537a5 | ||
![]() |
48872d11ca | ||
![]() |
1c04f8f10a | ||
![]() |
bf520f5184 | ||
![]() |
c089228bfa | ||
![]() |
934e92c346 | ||
![]() |
219d07dc96 | ||
![]() |
90db3f6132 | ||
![]() |
73d9e14e80 | ||
![]() |
ca45316707 | ||
![]() |
6f825ab156 | ||
![]() |
0b0eefd42b | ||
![]() |
3e967fa4a0 | ||
![]() |
ee753450a7 | ||
![]() |
616348fc6f | ||
![]() |
5b2db9d3ca | ||
![]() |
53c5469511 | ||
![]() |
0ca5b8496b | ||
![]() |
35435c573a | ||
![]() |
8cec637d08 | ||
![]() |
9f05c76b7b | ||
![]() |
6ac04b8c7d | ||
![]() |
004fe296cc | ||
![]() |
978542cae4 | ||
![]() |
dc785fdae5 | ||
![]() |
c9a3e963ec | ||
![]() |
a887612b75 | ||
![]() |
a27e4bb586 | ||
![]() |
5058c76f3e | ||
![]() |
90e05e7d52 | ||
![]() |
6569f1f25f | ||
![]() |
2f72bec100 | ||
![]() |
42f3644409 | ||
![]() |
1770117178 | ||
![]() |
1379914c45 | ||
![]() |
254bded75e | ||
![]() |
a88e3e6ac0 | ||
![]() |
6c75d1a504 | ||
![]() |
36300be94e | ||
![]() |
c8c8377acb | ||
![]() |
fd4a68b4de | ||
![]() |
78f69040fc | ||
![]() |
3465f73e2c | ||
![]() |
86496d701d | ||
![]() |
de389f2ecc | ||
![]() |
e43533cd1b | ||
![]() |
2a78dba95b | ||
![]() |
e9cd753b98 | ||
![]() |
dac13b7fc3 | ||
![]() |
0db515dfec | ||
![]() |
336ccf45c8 | ||
![]() |
70aefc810c | ||
![]() |
6025b93664 | ||
![]() |
e823122f19 | ||
![]() |
ef9df01cd2 | ||
![]() |
2e114bcaa0 | ||
![]() |
91a12abdaf | ||
![]() |
79bc6e8c35 | ||
![]() |
770fa89dc8 | ||
![]() |
9a7b0c3f02 | ||
![]() |
8c51053739 | ||
![]() |
3a4612cb2b | ||
![]() |
c0113bfbbe | ||
![]() |
08396d566b | ||
![]() |
66b6917923 | ||
![]() |
397b3cf88f | ||
![]() |
bcb72df356 | ||
![]() |
ed2d5f6b73 | ||
![]() |
eda6b436dc | ||
![]() |
09a35a7cb8 | ||
![]() |
a68a37f59c | ||
![]() |
2b86ff6768 | ||
![]() |
8a65c4d28d | ||
![]() |
d624a5edd6 | ||
![]() |
11f71dcb09 | ||
![]() |
7ec30b6ee9 | ||
![]() |
13a5d9f3af | ||
![]() |
a429dbad98 | ||
![]() |
0c0fd333f3 | ||
![]() |
d96cef1ac4 | ||
![]() |
3f58b8d1bd | ||
![]() |
908bd64238 | ||
![]() |
be36f91bb7 | ||
![]() |
8e4536fd98 | ||
![]() |
f043fb4495 | ||
![]() |
1dc03cc1c3 | ||
![]() |
e4dac6a6ab | ||
![]() |
ff585d0a20 | ||
![]() |
5f9a2ad1db | ||
![]() |
618eb8e72a | ||
![]() |
d763886dae | ||
![]() |
768402c884 | ||
![]() |
9c6f2a132d | ||
![]() |
d77096071d | ||
![]() |
f0abba3eef | ||
![]() |
5d211c101f | ||
![]() |
01e9ac0561 | ||
![]() |
a4e5b1b6bc | ||
![]() |
1f62fe8ae0 | ||
![]() |
96f0c76648 | ||
![]() |
e37a344ce5 | ||
![]() |
887a9576b8 | ||
![]() |
edd468323f | ||
![]() |
4b7f369290 | ||
![]() |
ef8f366734 | ||
![]() |
c5e4694327 | ||
![]() |
9471083571 | ||
![]() |
804051b9dd | ||
![]() |
893d0941a8 | ||
![]() |
1913399d81 | ||
![]() |
4fe311e7c0 | ||
![]() |
b8ffb88d1d | ||
![]() |
fd8565c91a | ||
![]() |
c87ff7dc1d | ||
![]() |
7d3d8ef142 | ||
![]() |
032bb17899 | ||
![]() |
fa307f06ac | ||
![]() |
d5c6036c53 | ||
![]() |
f3b16e1363 | ||
![]() |
240fbc2661 | ||
![]() |
642dd61446 | ||
![]() |
c7e52852bb | ||
![]() |
5d85dc2d91 | ||
![]() |
b692da7f6f | ||
![]() |
2c5d47ec1f | ||
![]() |
e740aa05a4 | ||
![]() |
fb21899097 | ||
![]() |
0f7020cbef | ||
![]() |
e491b05935 | ||
![]() |
7845659322 | ||
![]() |
9011f73da3 | ||
![]() |
b1ffd0f58f | ||
![]() |
f5d83f395f | ||
![]() |
348e083227 | ||
![]() |
704910c7e9 | ||
![]() |
12a277ed65 | ||
![]() |
e43b9edc36 | ||
![]() |
acd7e57295 | ||
![]() |
c131de73a5 | ||
![]() |
b8fc56885e | ||
![]() |
7f4efb1c34 | ||
![]() |
779ed6cf3f | ||
![]() |
11bb77313e | ||
![]() |
c2f99a5a1f | ||
![]() |
d1c4670e45 | ||
![]() |
fe35a17dbe | ||
![]() |
d66a184f45 | ||
![]() |
932801ae18 | ||
![]() |
d6d6561295 | ||
![]() |
18b60db6ae | ||
![]() |
1b36e34fc4 | ||
![]() |
d2d161ad28 | ||
![]() |
f2f2d7dab2 | ||
![]() |
1667fece88 | ||
![]() |
044cd5cf7e | ||
![]() |
dfd1b2fdcd | ||
![]() |
bf1839aed3 | ||
![]() |
26f0a7e779 | ||
![]() |
a7f4346f5e | ||
![]() |
d2ee58fb2a | ||
![]() |
aec4a0dd59 | ||
![]() |
5ffa1ee883 | ||
![]() |
3cd20d7d37 | ||
![]() |
d8c8fa9bae | ||
![]() |
d7e2fd555c | ||
![]() |
1f9104d96f | ||
![]() |
ea5a8c7809 | ||
![]() |
5ae3b81f3c | ||
![]() |
6d2c29ae85 | ||
![]() |
0496e72d15 | ||
![]() |
e90a48fd4b | ||
![]() |
e2ffe12e50 | ||
![]() |
2c0c6f408e | ||
![]() |
092cb967b0 | ||
![]() |
d62cbfe923 | ||
![]() |
43bad93715 | ||
![]() |
eb80c9429e | ||
![]() |
946a10a8d5 | ||
![]() |
a7165d1fb0 | ||
![]() |
47b67fcafc | ||
![]() |
7d3a013e5e | ||
![]() |
4cb10ff28a | ||
![]() |
00e5c68060 | ||
![]() |
700e9f027b | ||
![]() |
9ee88e965e | ||
![]() |
0ae05e1000 | ||
![]() |
2ff9e77dba | ||
![]() |
4ddf4a8fd3 | ||
![]() |
4c7fef22f6 | ||
![]() |
01e7095968 | ||
![]() |
f1e413eb7c | ||
![]() |
67b9b0c76e | ||
![]() |
56f9ddc9af | ||
![]() |
00749b3a8f | ||
![]() |
8da48fead3 | ||
![]() |
b835f0a1b0 | ||
![]() |
f8c0a352ab | ||
![]() |
859fa4e489 | ||
![]() |
e8a67571a1 | ||
![]() |
28c8a889bb | ||
![]() |
3003195ad7 | ||
![]() |
c400f26e6c | ||
![]() |
0a0a3cea1b | ||
![]() |
cb88d55837 | ||
![]() |
e1e7299bd9 | ||
![]() |
d8ae7d9e96 | ||
![]() |
b28a070a52 | ||
![]() |
b2483b2ae0 | ||
![]() |
14c7055494 | ||
![]() |
81b9977540 | ||
![]() |
a75862bd7d | ||
![]() |
99baeb47e5 | ||
![]() |
7d45c1c6c7 | ||
![]() |
5442b0a6b1 | ||
![]() |
da0c4ab199 | ||
![]() |
658ed564cb | ||
![]() |
eb4f1de8ec | ||
![]() |
ba7da0af31 | ||
![]() |
74712e3400 | ||
![]() |
6c16834d28 | ||
![]() |
81308159fd | ||
![]() |
2f1a737769 | ||
![]() |
5b6d8a303d | ||
![]() |
d26b7902ec | ||
![]() |
1a8f1482af | ||
![]() |
84718e7b17 | ||
![]() |
232179aa3d | ||
![]() |
300e01f733 | ||
![]() |
d727757cfb |
351 changed files with 18693 additions and 4527 deletions
2
.forgejo/testdata/build-release/Dockerfile
vendored
2
.forgejo/testdata/build-release/Dockerfile
vendored
|
@ -3,4 +3,4 @@ ARG RELEASE_VERSION=unkown
|
||||||
LABEL maintainer="contact@forgejo.org" \
|
LABEL maintainer="contact@forgejo.org" \
|
||||||
org.opencontainers.image.version="${RELEASE_VERSION}"
|
org.opencontainers.image.version="${RELEASE_VERSION}"
|
||||||
RUN mkdir -p /app/gitea
|
RUN mkdir -p /app/gitea
|
||||||
RUN ( echo '#!/bin/sh' ; echo "echo forgejo v$RELEASE_VERSION" ) > /app/gitea/forgejo-cli ; chmod +x /app/gitea/forgejo-cli
|
RUN ( echo '#!/bin/sh' ; echo "echo forgejo v$RELEASE_VERSION" ) > /app/gitea/gitea ; chmod +x /app/gitea/gitea
|
||||||
|
|
2
.forgejo/testdata/build-release/go.mod
vendored
2
.forgejo/testdata/build-release/go.mod
vendored
|
@ -1,3 +1,3 @@
|
||||||
module code.gitea.io/gitea
|
module code.gitea.io/gitea
|
||||||
|
|
||||||
go 1.23.1
|
go 1.23.2
|
||||||
|
|
|
@ -31,7 +31,7 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
backporting:
|
backporting:
|
||||||
if: >
|
if: >
|
||||||
!startsWith(vars.ROLE, 'forgejo-') && (
|
( vars.ROLE == 'forgejo-coding' ) && (
|
||||||
github.event.pull_request.merged
|
github.event.pull_request.merged
|
||||||
&&
|
&&
|
||||||
contains(toJSON(github.event.pull_request.labels), 'backport/v')
|
contains(toJSON(github.event.pull_request.labels), 'backport/v')
|
||||||
|
|
41
.forgejo/workflows/build-oci-image.yml
Normal file
41
.forgejo/workflows/build-oci-image.yml
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'forgejo'
|
||||||
|
tags:
|
||||||
|
- '*-git-annex*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-oci-image:
|
||||||
|
runs-on: docker
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
type: ["rootful", "rootless"]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # fetch the full history so that the Forgejo version is determined properly
|
||||||
|
- name: Determine registry and username
|
||||||
|
id: determine-registry-and-username
|
||||||
|
run: |
|
||||||
|
echo "registry=${GITHUB_SERVER_URL#https://}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "username=${GITHUB_REPOSITORY%/*}" >> "$GITHUB_OUTPUT"
|
||||||
|
- name: Install Docker
|
||||||
|
run: curl -fsSL https://get.docker.com | sh
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ steps.determine-registry-and-username.outputs.registry }}
|
||||||
|
username: ${{ steps.determine-registry-and-username.outputs.username }}
|
||||||
|
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ${{ (matrix.type == 'rootful' && 'Dockerfile') || (matrix.type == 'rootless' && 'Dockerfile.rootless') }}
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.determine-registry-and-username.outputs.registry }}/${{ github.repository }}:${{ github.ref_name }}${{ (matrix.type == 'rootful' && ' ') || (matrix.type == 'rootless' && '-rootless') }}
|
|
@ -22,10 +22,10 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-simulation:
|
release-simulation:
|
||||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
if: vars.ROLE == 'forgejo-coding'
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- id: forgejo
|
- id: forgejo
|
||||||
uses: https://code.forgejo.org/actions/setup-forgejo@v1
|
uses: https://code.forgejo.org/actions/setup-forgejo@v1
|
||||||
|
|
|
@ -27,7 +27,7 @@ jobs:
|
||||||
# root is used for testing, allow it
|
# root is used for testing, allow it
|
||||||
if: vars.ROLE == 'forgejo-integration' || github.repository_owner == 'root'
|
if: vars.ROLE == 'forgejo-integration' || github.repository_owner == 'root'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ jobs:
|
||||||
repository="${{ github.repository }}"
|
repository="${{ github.repository }}"
|
||||||
echo "value=${repository##*/}" >> "$GITHUB_OUTPUT"
|
echo "value=${repository##*/}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- uses: https://code.forgejo.org/actions/setup-node@v3
|
- uses: https://code.forgejo.org/actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ jobs:
|
||||||
|
|
||||||
- name: cache node_modules
|
- name: cache node_modules
|
||||||
id: node
|
id: node
|
||||||
uses: https://code.forgejo.org/actions/cache@v3
|
uses: https://code.forgejo.org/actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
node_modules
|
node_modules
|
||||||
|
@ -170,7 +170,7 @@ jobs:
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v6
|
platforms: linux/amd64,linux/arm64,linux/arm/v6
|
||||||
release-notes: "${{ steps.release-notes.outputs.value }}"
|
release-notes: "${{ steps.release-notes.outputs.value }}"
|
||||||
binary-name: forgejo
|
binary-name: forgejo
|
||||||
binary-path: /app/gitea/forgejo-cli
|
binary-path: /app/gitea/gitea
|
||||||
override: "${{ steps.release-info.outputs.override }}"
|
override: "${{ steps.release-info.outputs.override }}"
|
||||||
verify-labels: "maintainer=contact@forgejo.org,org.opencontainers.image.version=${{ steps.release-info.outputs.version }}"
|
verify-labels: "maintainer=contact@forgejo.org,org.opencontainers.image.version=${{ steps.release-info.outputs.version }}"
|
||||||
verbose: ${{ vars.VERBOSE || secrets.VERBOSE || 'false' }}
|
verbose: ${{ vars.VERBOSE || secrets.VERBOSE || 'false' }}
|
||||||
|
@ -194,7 +194,7 @@ jobs:
|
||||||
verbose: ${{ vars.VERBOSE || secrets.VERBOSE || 'false' }}
|
verbose: ${{ vars.VERBOSE || secrets.VERBOSE || 'false' }}
|
||||||
|
|
||||||
- name: end-to-end tests
|
- name: end-to-end tests
|
||||||
if: ${{ secrets.TOKEN != '' && vars.ROLE == 'forgejo-integration' }}
|
if: ${{ secrets.TOKEN != '' && vars.ROLE == 'forgejo-integration' && vars.SKIP_END_TO_END != 'true' }}
|
||||||
uses: https://code.forgejo.org/actions/cascading-pr@v2
|
uses: https://code.forgejo.org/actions/cascading-pr@v2
|
||||||
with:
|
with:
|
||||||
origin-url: ${{ env.GITHUB_SERVER_URL }}
|
origin-url: ${{ env.GITHUB_SERVER_URL }}
|
||||||
|
|
|
@ -24,7 +24,7 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
info:
|
info:
|
||||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
if: vars.ROLE == 'forgejo-coding'
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: code.forgejo.org/oci/node:20-bookworm
|
image: code.forgejo.org/oci/node:20-bookworm
|
||||||
|
@ -44,7 +44,7 @@ jobs:
|
||||||
|
|
||||||
cascade:
|
cascade:
|
||||||
if: >
|
if: >
|
||||||
!startsWith(vars.ROLE, 'forgejo-') && (
|
vars.ROLE == 'forgejo-coding' && (
|
||||||
github.event_name == 'push' ||
|
github.event_name == 'push' ||
|
||||||
(
|
(
|
||||||
github.event.action == 'label_updated' && contains(github.event.pull_request.labels.*.name, 'run-end-to-end-tests')
|
github.event.action == 'label_updated' && contains(github.event.pull_request.labels.*.name, 'run-end-to-end-tests')
|
||||||
|
|
|
@ -14,7 +14,7 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test-e2e:
|
test-e2e:
|
||||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: 'code.forgejo.org/oci/playwright:latest'
|
image: 'code.forgejo.org/oci/playwright:latest'
|
||||||
|
|
24
.forgejo/workflows/milestone.yml
Normal file
24
.forgejo/workflows/milestone.yml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Copyright 2024 The Forgejo Authors
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
#
|
||||||
|
name: milestone
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types:
|
||||||
|
- closed
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
set:
|
||||||
|
if: vars.ROLE == 'forgejo-coding' && github.event.pull_request.merged
|
||||||
|
runs-on: docker
|
||||||
|
container:
|
||||||
|
image: 'code.forgejo.org/oci/ci:1'
|
||||||
|
steps:
|
||||||
|
- uses: https://code.forgejo.org/forgejo/set-milestone@v1.0.0
|
||||||
|
with:
|
||||||
|
forgejo: https://codeberg.org
|
||||||
|
repository: forgejo/forgejo
|
||||||
|
token: ${{ secrets.SET_MILESTONE_TOKEN }}
|
||||||
|
pr-number: ${{ github.event.pull_request.number }}
|
||||||
|
verbose: ${{ vars.SET_MILESTONE_VERBOSE }}
|
|
@ -39,7 +39,7 @@ jobs:
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
if: vars.DOER != '' && vars.FORGEJO != '' && vars.TO_OWNER != '' && vars.FROM_OWNER != '' && secrets.TOKEN != ''
|
if: vars.DOER != '' && vars.FORGEJO != '' && vars.TO_OWNER != '' && vars.FROM_OWNER != '' && secrets.TOKEN != ''
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: copy & sign
|
- name: copy & sign
|
||||||
uses: https://code.forgejo.org/forgejo/forgejo-build-publish/publish@v5
|
uses: https://code.forgejo.org/forgejo/forgejo-build-publish/publish@v5
|
||||||
|
@ -59,30 +59,28 @@ jobs:
|
||||||
gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||||
verbose: ${{ vars.VERBOSE }}
|
verbose: ${{ vars.VERBOSE }}
|
||||||
|
|
||||||
- name: upgrade v*.next.forgejo.org
|
- name: get trigger mirror issue
|
||||||
run: |
|
id: mirror
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
uses: https://code.forgejo.org/infrastructure/issue-action/get@v1.1.0
|
||||||
apt-get update -qq
|
with:
|
||||||
apt-get -q install -y -qq curl
|
forgejo: https://code.forgejo.org
|
||||||
version="${{ github.ref_name }}"
|
repository: forgejo/forgejo
|
||||||
version=${version##*v}
|
labels: mirror-trigger
|
||||||
major=$(echo $version | sed -E -e 's/^([0-9]+).*/\1/')
|
|
||||||
# https://forgejo.org/docs/next/developer/infrastructure
|
|
||||||
curl -o /dev/null -sS https://v$major.next.forgejo.org/.well-known/wakeup-on-logs/forgejo-v$major
|
|
||||||
|
|
||||||
- name: set up go for the DNS update below
|
- name: trigger the mirror
|
||||||
if: vars.ROLE == 'forgejo-experimental' && secrets.OVH_APP_KEY != ''
|
uses: https://code.forgejo.org/infrastructure/issue-action/set@v1.1.0
|
||||||
uses: https://code.forgejo.org/actions/setup-go@v4
|
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
forgejo: https://code.forgejo.org
|
||||||
- name: update the _release.experimental DNS record
|
repository: forgejo/forgejo
|
||||||
if: vars.ROLE == 'forgejo-experimental' && secrets.OVH_APP_KEY != ''
|
token: ${{ secrets.LABEL_ISSUE_FORGEJO_MIRROR_TOKEN }}
|
||||||
uses: https://code.forgejo.org/actions/ovh-dns-update@v1
|
numbers: ${{ steps.mirror.outputs.numbers }}
|
||||||
|
label-wait-if-exists: 3600
|
||||||
|
label: trigger
|
||||||
|
|
||||||
|
- name: upgrade v*.next.forgejo.org
|
||||||
|
uses: https://code.forgejo.org/infrastructure/next-digest@v1.1.0
|
||||||
with:
|
with:
|
||||||
subdomain: _release.experimental
|
url: https://placeholder:${{ secrets.TOKEN_NEXT_DIGEST }}@code.forgejo.org/infrastructure/next-digest
|
||||||
domain: forgejo.com # there is a CNAME from .org to .com (for security reasons)
|
ref_name: '${{ github.ref_name }}'
|
||||||
record-id: 5283602601
|
image: 'codeberg.org/forgejo-experimental/forgejo'
|
||||||
value: v=${{ github.ref_name }}
|
tag_suffix: '-rootless'
|
||||||
ovh-app-key: ${{ secrets.OVH_APP_KEY }}
|
|
||||||
ovh-app-secret: ${{ secrets.OVH_APP_SECRET }}
|
|
||||||
ovh-consumer-key: ${{ secrets.OVH_CON_KEY }}
|
|
||||||
|
|
|
@ -6,12 +6,12 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-notes:
|
release-notes:
|
||||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-')
|
if: vars.ROLE == 'forgejo-coding'
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||||
steps:
|
steps:
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||||
|
|
||||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -7,12 +7,12 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-notes:
|
release-notes:
|
||||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') && contains(github.event.pull_request.labels.*.name, 'worth a release-note') }}
|
if: ( vars.ROLE == 'forgejo-coding' ) && contains(github.event.pull_request.labels.*.name, 'worth a release-note')
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||||
steps:
|
steps:
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||||
|
|
||||||
- name: event
|
- name: event
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -8,7 +8,9 @@ name: renovate
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'renovate/**' # self-test updates
|
- renovate/** # self-test updates
|
||||||
|
paths:
|
||||||
|
- .forgejo/workflows/renovate.yml
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 0/2 * * *'
|
- cron: '0 0/2 * * *'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
@ -19,7 +21,7 @@ env:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
renovate:
|
renovate:
|
||||||
if: ${{ secrets.RENOVATE_TOKEN != '' }}
|
if: vars.ROLE == 'forgejo-coding' && secrets.RENOVATE_TOKEN != ''
|
||||||
|
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
|
|
|
@ -9,7 +9,6 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
backend-checks:
|
backend-checks:
|
||||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||||
|
@ -19,7 +18,7 @@ jobs:
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
${{ toJSON(github) }}
|
${{ toJSON(github) }}
|
||||||
EOF
|
EOF
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
|
@ -34,19 +33,17 @@ jobs:
|
||||||
path: '/workspace/forgejo/forgejo/gitea'
|
path: '/workspace/forgejo/forgejo/gitea'
|
||||||
key: backend-build-${{ github.sha }}
|
key: backend-build-${{ github.sha }}
|
||||||
frontend-checks:
|
frontend-checks:
|
||||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||||
steps:
|
steps:
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||||
- run: make deps-frontend
|
- run: make deps-frontend
|
||||||
- run: make lint-frontend
|
- run: make lint-frontend
|
||||||
- run: make checks-frontend
|
- run: make checks-frontend
|
||||||
- run: make test-frontend-coverage
|
- run: make test-frontend-coverage
|
||||||
- run: make frontend
|
- run: make frontend
|
||||||
test-unit:
|
test-unit:
|
||||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
needs: [backend-checks, frontend-checks]
|
needs: [backend-checks, frontend-checks]
|
||||||
container:
|
container:
|
||||||
|
@ -66,7 +63,7 @@ jobs:
|
||||||
MINIO_ROOT_USER: 123456
|
MINIO_ROOT_USER: 123456
|
||||||
MINIO_ROOT_PASSWORD: 12345678
|
MINIO_ROOT_PASSWORD: 12345678
|
||||||
steps:
|
steps:
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
|
@ -106,7 +103,6 @@ jobs:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
TEST_ELASTICSEARCH_URL: http://elasticsearch:9200
|
TEST_ELASTICSEARCH_URL: http://elasticsearch:9200
|
||||||
test-remote-cacher:
|
test-remote-cacher:
|
||||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
needs: [backend-checks, frontend-checks]
|
needs: [backend-checks, frontend-checks]
|
||||||
container:
|
container:
|
||||||
|
@ -131,7 +127,7 @@ jobs:
|
||||||
image: ${{ matrix.cacher.image }}
|
image: ${{ matrix.cacher.image }}
|
||||||
options: ${{ matrix.cacher.options }}
|
options: ${{ matrix.cacher.options }}
|
||||||
steps:
|
steps:
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
|
@ -167,7 +163,6 @@ jobs:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
TEST_REDIS_SERVER: cacher:${{ matrix.cacher.port }}
|
TEST_REDIS_SERVER: cacher:${{ matrix.cacher.port }}
|
||||||
test-mysql:
|
test-mysql:
|
||||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
needs: [backend-checks, frontend-checks]
|
needs: [backend-checks, frontend-checks]
|
||||||
container:
|
container:
|
||||||
|
@ -183,7 +178,7 @@ jobs:
|
||||||
#
|
#
|
||||||
MYSQL_EXTRA_FLAGS: --innodb-adaptive-flushing=OFF --innodb-buffer-pool-size=4G --innodb-log-buffer-size=128M --innodb-flush-log-at-trx-commit=0 --innodb-flush-log-at-timeout=30 --innodb-flush-method=nosync --innodb-fsync-threshold=1000000000
|
MYSQL_EXTRA_FLAGS: --innodb-adaptive-flushing=OFF --innodb-buffer-pool-size=4G --innodb-log-buffer-size=128M --innodb-flush-log-at-trx-commit=0 --innodb-flush-log-at-timeout=30 --innodb-flush-method=nosync --innodb-fsync-threshold=1000000000
|
||||||
steps:
|
steps:
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
|
@ -195,6 +190,16 @@ jobs:
|
||||||
apt-get install --no-install-recommends -qq -y git git-lfs
|
apt-get install --no-install-recommends -qq -y git git-lfs
|
||||||
rm /etc/apt/sources.list.d/testing.list
|
rm /etc/apt/sources.list.d/testing.list
|
||||||
apt-get update -qq
|
apt-get update -qq
|
||||||
|
cd /
|
||||||
|
wget https://downloads.kitenet.net/git-annex/linux/current/git-annex-standalone-amd64.tar.gz
|
||||||
|
tar xzvf git-annex-standalone-amd64.tar.gz
|
||||||
|
ln -s \
|
||||||
|
/git-annex.linux/git-annex \
|
||||||
|
/git-annex.linux/git-annex-shell \
|
||||||
|
/git-annex.linux/git-annex-webapp \
|
||||||
|
/git-annex.linux/git-remote-annex \
|
||||||
|
bin
|
||||||
|
cd -
|
||||||
- name: setup user and permissions
|
- name: setup user and permissions
|
||||||
run: |
|
run: |
|
||||||
git config --add safe.directory '*'
|
git config --add safe.directory '*'
|
||||||
|
@ -214,11 +219,9 @@ jobs:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
- run: |
|
- run: |
|
||||||
su forgejo -c 'make test-mysql-migration test-mysql'
|
su forgejo -c 'make test-mysql-migration test-mysql'
|
||||||
timeout-minutes: 50
|
|
||||||
env:
|
env:
|
||||||
USE_REPO_TEST_DIR: 1
|
USE_REPO_TEST_DIR: 1
|
||||||
test-pgsql:
|
test-pgsql:
|
||||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
needs: [backend-checks, frontend-checks]
|
needs: [backend-checks, frontend-checks]
|
||||||
container:
|
container:
|
||||||
|
@ -237,7 +240,7 @@ jobs:
|
||||||
POSTGRES_DB: test
|
POSTGRES_DB: test
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
steps:
|
steps:
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
|
@ -249,6 +252,16 @@ jobs:
|
||||||
apt-get install --no-install-recommends -qq -y git git-lfs
|
apt-get install --no-install-recommends -qq -y git git-lfs
|
||||||
rm /etc/apt/sources.list.d/testing.list
|
rm /etc/apt/sources.list.d/testing.list
|
||||||
apt-get update -qq
|
apt-get update -qq
|
||||||
|
cd /
|
||||||
|
wget https://downloads.kitenet.net/git-annex/linux/current/git-annex-standalone-amd64.tar.gz
|
||||||
|
tar xzvf git-annex-standalone-amd64.tar.gz
|
||||||
|
ln -s \
|
||||||
|
/git-annex.linux/git-annex \
|
||||||
|
/git-annex.linux/git-annex-shell \
|
||||||
|
/git-annex.linux/git-annex-webapp \
|
||||||
|
/git-annex.linux/git-remote-annex \
|
||||||
|
bin
|
||||||
|
cd -
|
||||||
- name: setup user and permissions
|
- name: setup user and permissions
|
||||||
run: |
|
run: |
|
||||||
git config --add safe.directory '*'
|
git config --add safe.directory '*'
|
||||||
|
@ -268,19 +281,17 @@ jobs:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
- run: |
|
- run: |
|
||||||
su forgejo -c 'make test-pgsql-migration test-pgsql'
|
su forgejo -c 'make test-pgsql-migration test-pgsql'
|
||||||
timeout-minutes: 50
|
|
||||||
env:
|
env:
|
||||||
RACE_ENABLED: true
|
RACE_ENABLED: true
|
||||||
USE_REPO_TEST_DIR: 1
|
USE_REPO_TEST_DIR: 1
|
||||||
TEST_LDAP: 1
|
TEST_LDAP: 1
|
||||||
test-sqlite:
|
test-sqlite:
|
||||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
needs: [backend-checks, frontend-checks]
|
needs: [backend-checks, frontend-checks]
|
||||||
container:
|
container:
|
||||||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||||
steps:
|
steps:
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
|
@ -292,6 +303,16 @@ jobs:
|
||||||
apt-get install --no-install-recommends -qq -y git git-lfs
|
apt-get install --no-install-recommends -qq -y git git-lfs
|
||||||
rm /etc/apt/sources.list.d/testing.list
|
rm /etc/apt/sources.list.d/testing.list
|
||||||
apt-get update -qq
|
apt-get update -qq
|
||||||
|
cd /
|
||||||
|
wget https://downloads.kitenet.net/git-annex/linux/current/git-annex-standalone-amd64.tar.gz
|
||||||
|
tar xzvf git-annex-standalone-amd64.tar.gz
|
||||||
|
ln -s \
|
||||||
|
/git-annex.linux/git-annex \
|
||||||
|
/git-annex.linux/git-annex-shell \
|
||||||
|
/git-annex.linux/git-annex-webapp \
|
||||||
|
/git-annex.linux/git-remote-annex \
|
||||||
|
bin
|
||||||
|
cd -
|
||||||
- name: setup user and permissions
|
- name: setup user and permissions
|
||||||
run: |
|
run: |
|
||||||
git config --add safe.directory '*'
|
git config --add safe.directory '*'
|
||||||
|
@ -311,14 +332,12 @@ jobs:
|
||||||
TAGS: bindata sqlite sqlite_unlock_notify
|
TAGS: bindata sqlite sqlite_unlock_notify
|
||||||
- run: |
|
- run: |
|
||||||
su forgejo -c 'make test-sqlite-migration test-sqlite'
|
su forgejo -c 'make test-sqlite-migration test-sqlite'
|
||||||
timeout-minutes: 50
|
|
||||||
env:
|
env:
|
||||||
TAGS: sqlite sqlite_unlock_notify
|
TAGS: sqlite sqlite_unlock_notify
|
||||||
RACE_ENABLED: true
|
RACE_ENABLED: true
|
||||||
TEST_TAGS: sqlite sqlite_unlock_notify
|
TEST_TAGS: sqlite sqlite_unlock_notify
|
||||||
USE_REPO_TEST_DIR: 1
|
USE_REPO_TEST_DIR: 1
|
||||||
security-check:
|
security-check:
|
||||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
needs:
|
needs:
|
||||||
- test-sqlite
|
- test-sqlite
|
||||||
|
@ -329,7 +348,7 @@ jobs:
|
||||||
container:
|
container:
|
||||||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||||
steps:
|
steps:
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
|
|
|
@ -51,7 +51,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
|
||||||
/go/src/code.gitea.io/gitea/environment-to-ini
|
/go/src/code.gitea.io/gitea/environment-to-ini
|
||||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
||||||
|
|
||||||
FROM code.forgejo.org/oci/golang:1.23-alpine3.20
|
FROM code.forgejo.org/oci/alpine:3.20
|
||||||
ARG RELEASE_VERSION
|
ARG RELEASE_VERSION
|
||||||
LABEL maintainer="contact@forgejo.org" \
|
LABEL maintainer="contact@forgejo.org" \
|
||||||
org.opencontainers.image.authors="Forgejo" \
|
org.opencontainers.image.authors="Forgejo" \
|
||||||
|
@ -78,6 +78,7 @@ RUN apk --no-cache add \
|
||||||
sqlite \
|
sqlite \
|
||||||
su-exec \
|
su-exec \
|
||||||
gnupg \
|
gnupg \
|
||||||
|
git-annex \
|
||||||
&& rm -rf /var/cache/apk/*
|
&& rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
RUN addgroup \
|
RUN addgroup \
|
||||||
|
@ -103,6 +104,6 @@ CMD ["/bin/s6-svscan", "/etc/s6"]
|
||||||
COPY --from=build-env /tmp/local /
|
COPY --from=build-env /tmp/local /
|
||||||
RUN cd /usr/local/bin ; ln -s gitea forgejo
|
RUN cd /usr/local/bin ; ln -s gitea forgejo
|
||||||
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
|
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
|
||||||
RUN ln /app/gitea/gitea /app/gitea/forgejo-cli
|
RUN ln -s /app/gitea/gitea /app/gitea/forgejo-cli
|
||||||
COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
|
COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
|
||||||
COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh
|
COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh
|
||||||
|
|
|
@ -49,7 +49,7 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
|
||||||
/go/src/code.gitea.io/gitea/environment-to-ini
|
/go/src/code.gitea.io/gitea/environment-to-ini
|
||||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
||||||
|
|
||||||
FROM code.forgejo.org/oci/golang:1.23-alpine3.20
|
FROM code.forgejo.org/oci/alpine:3.20
|
||||||
LABEL maintainer="contact@forgejo.org" \
|
LABEL maintainer="contact@forgejo.org" \
|
||||||
org.opencontainers.image.authors="Forgejo" \
|
org.opencontainers.image.authors="Forgejo" \
|
||||||
org.opencontainers.image.url="https://forgejo.org" \
|
org.opencontainers.image.url="https://forgejo.org" \
|
||||||
|
@ -71,6 +71,7 @@ RUN apk --no-cache add \
|
||||||
git \
|
git \
|
||||||
curl \
|
curl \
|
||||||
gnupg \
|
gnupg \
|
||||||
|
git-annex \
|
||||||
&& rm -rf /var/cache/apk/*
|
&& rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
RUN addgroup \
|
RUN addgroup \
|
||||||
|
@ -90,7 +91,7 @@ RUN chown git:git /var/lib/gitea /etc/gitea
|
||||||
COPY --from=build-env /tmp/local /
|
COPY --from=build-env /tmp/local /
|
||||||
RUN cd /usr/local/bin ; ln -s gitea forgejo
|
RUN cd /usr/local/bin ; ln -s gitea forgejo
|
||||||
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
|
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
|
||||||
RUN ln /app/gitea/gitea /app/gitea/forgejo-cli
|
RUN ln -s /app/gitea/gitea /app/gitea/forgejo-cli
|
||||||
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
|
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
|
||||||
COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh
|
COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh
|
||||||
|
|
||||||
|
|
4
Makefile
4
Makefile
|
@ -8,7 +8,7 @@ self := $(location)
|
||||||
@tmpdir=`mktemp --tmpdir -d` ; \
|
@tmpdir=`mktemp --tmpdir -d` ; \
|
||||||
echo Using temporary directory $$tmpdir for test repositories ; \
|
echo Using temporary directory $$tmpdir for test repositories ; \
|
||||||
USE_REPO_TEST_DIR= $(MAKE) -f $(self) --no-print-directory REPO_TEST_DIR=$$tmpdir/ $@ ; \
|
USE_REPO_TEST_DIR= $(MAKE) -f $(self) --no-print-directory REPO_TEST_DIR=$$tmpdir/ $@ ; \
|
||||||
STATUS=$$? ; rm -r "$$tmpdir" ; exit $$STATUS
|
STATUS=$$? ; chmod -R +w "$$tmpdir" && rm -r "$$tmpdir" ; exit $$STATUS
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ else
|
||||||
FORGEJO_VERSION_API ?= $(GITEA_VERSION)+${GITEA_COMPATIBILITY}
|
FORGEJO_VERSION_API ?= $(GITEA_VERSION)+${GITEA_COMPATIBILITY}
|
||||||
else
|
else
|
||||||
# drop the "g" prefix prepended by git describe to the commit hash
|
# drop the "g" prefix prepended by git describe to the commit hash
|
||||||
FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//' | sed 's/\-g/-/')+${GITEA_COMPATIBILITY}
|
FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//' | sed 's/\-g/-/2')+${GITEA_COMPATIBILITY}
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
FORGEJO_VERSION_MAJOR=$(shell echo $(FORGEJO_VERSION) | sed -e 's/\..*//')
|
FORGEJO_VERSION_MAJOR=$(shell echo $(FORGEJO_VERSION) | sed -e 's/\..*//')
|
||||||
|
|
87
cmd/serv.go
87
cmd/serv.go
|
@ -38,6 +38,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
lfsAuthenticateVerb = "git-lfs-authenticate"
|
lfsAuthenticateVerb = "git-lfs-authenticate"
|
||||||
|
gitAnnexShellVerb = "git-annex-shell"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdServ represents the available serv sub-command.
|
// CmdServ represents the available serv sub-command.
|
||||||
|
@ -79,6 +80,7 @@ var (
|
||||||
"git-upload-archive": perm.AccessModeRead,
|
"git-upload-archive": perm.AccessModeRead,
|
||||||
"git-receive-pack": perm.AccessModeWrite,
|
"git-receive-pack": perm.AccessModeWrite,
|
||||||
lfsAuthenticateVerb: perm.AccessModeNone,
|
lfsAuthenticateVerb: perm.AccessModeNone,
|
||||||
|
gitAnnexShellVerb: perm.AccessModeNone, // annex permissions are enforced by GIT_ANNEX_SHELL_READONLY, rather than the Gitea API
|
||||||
}
|
}
|
||||||
alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
|
alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
|
||||||
)
|
)
|
||||||
|
@ -147,6 +149,12 @@ func runServ(c *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
_ = fail(ctx, "Internal Server Error", "Panic: %v\n%s", err, log.Stack(2))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
keys := strings.Split(c.Args().First(), "-")
|
keys := strings.Split(c.Args().First(), "-")
|
||||||
if len(keys) != 2 || keys[0] != "key" {
|
if len(keys) != 2 || keys[0] != "key" {
|
||||||
return fail(ctx, "Key ID format error", "Invalid key argument: %s", c.Args().First())
|
return fail(ctx, "Key ID format error", "Invalid key argument: %s", c.Args().First())
|
||||||
|
@ -193,10 +201,7 @@ func runServ(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
verb := words[0]
|
verb := words[0]
|
||||||
repoPath := words[1]
|
repoPath := strings.TrimPrefix(words[1], "/")
|
||||||
if repoPath[0] == '/' {
|
|
||||||
repoPath = repoPath[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
var lfsVerb string
|
var lfsVerb string
|
||||||
if verb == lfsAuthenticateVerb {
|
if verb == lfsAuthenticateVerb {
|
||||||
|
@ -209,6 +214,28 @@ func runServ(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if verb == gitAnnexShellVerb {
|
||||||
|
if !setting.Annex.Enabled {
|
||||||
|
return fail(ctx, "Unknown git command", "git-annex request over SSH denied, git-annex support is disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(words) < 3 {
|
||||||
|
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// git-annex always puts the repo in words[2], unlike most other
|
||||||
|
// git subcommands; and it sometimes names repos like /~/, as if
|
||||||
|
// $HOME should get expanded while also being rooted. e.g.:
|
||||||
|
// git-annex-shell 'configlist' '/~/user/repo'
|
||||||
|
// git-annex-shell 'sendkey' '/user/repo 'key'
|
||||||
|
repoPath = words[2]
|
||||||
|
repoPath = strings.TrimPrefix(repoPath, "/")
|
||||||
|
repoPath = strings.TrimPrefix(repoPath, "~/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevent directory traversal attacks
|
||||||
|
repoPath = filepath.Clean("/" + repoPath)[1:]
|
||||||
|
|
||||||
rr := strings.SplitN(repoPath, "/", 2)
|
rr := strings.SplitN(repoPath, "/", 2)
|
||||||
if len(rr) != 2 {
|
if len(rr) != 2 {
|
||||||
return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath)
|
return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath)
|
||||||
|
@ -222,6 +249,18 @@ func runServ(c *cli.Context) error {
|
||||||
// so that username and reponame are not affected.
|
// so that username and reponame are not affected.
|
||||||
repoPath = strings.ToLower(strings.TrimSpace(repoPath))
|
repoPath = strings.ToLower(strings.TrimSpace(repoPath))
|
||||||
|
|
||||||
|
// put the sanitized repoPath back into the argument list for later
|
||||||
|
if verb == gitAnnexShellVerb {
|
||||||
|
// git-annex-shell demands an absolute path
|
||||||
|
absRepoPath, err := filepath.Abs(filepath.Join(setting.RepoRootPath, repoPath))
|
||||||
|
if err != nil {
|
||||||
|
return fail(ctx, "Error locating repoPath", "%v", err)
|
||||||
|
}
|
||||||
|
words[2] = absRepoPath
|
||||||
|
} else {
|
||||||
|
words[1] = repoPath
|
||||||
|
}
|
||||||
|
|
||||||
if alphaDashDotPattern.MatchString(reponame) {
|
if alphaDashDotPattern.MatchString(reponame) {
|
||||||
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
|
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
|
||||||
}
|
}
|
||||||
|
@ -300,21 +339,45 @@ func runServ(c *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var gitcmd *exec.Cmd
|
gitBinVerb, err := exec.LookPath(verb)
|
||||||
gitBinPath := filepath.Dir(git.GitExecutable) // e.g. /usr/bin
|
if err != nil {
|
||||||
gitBinVerb := filepath.Join(gitBinPath, verb) // e.g. /usr/bin/git-upload-pack
|
|
||||||
if _, err := os.Stat(gitBinVerb); err != nil {
|
|
||||||
// if the command "git-upload-pack" doesn't exist, try to split "git-upload-pack" to use the sub-command with git
|
// if the command "git-upload-pack" doesn't exist, try to split "git-upload-pack" to use the sub-command with git
|
||||||
// ps: Windows only has "git.exe" in the bin path, so Windows always uses this way
|
// ps: Windows only has "git.exe" in the bin path, so Windows always uses this way
|
||||||
|
// ps: git-annex-shell and other extensions may not necessarily be in gitBinPath,
|
||||||
|
// but '{gitBinPath}/git annex-shell' should be able to find them on $PATH.
|
||||||
verbFields := strings.SplitN(verb, "-", 2)
|
verbFields := strings.SplitN(verb, "-", 2)
|
||||||
if len(verbFields) == 2 {
|
if len(verbFields) == 2 {
|
||||||
// use git binary with the sub-command part: "C:\...\bin\git.exe", "upload-pack", ...
|
// use git binary with the sub-command part: "C:\...\bin\git.exe", "upload-pack", ...
|
||||||
gitcmd = exec.CommandContext(ctx, git.GitExecutable, verbFields[1], repoPath)
|
gitBinVerb = git.GitExecutable
|
||||||
|
words = append([]string{verbFields[1]}, words...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if gitcmd == nil {
|
|
||||||
// by default, use the verb (it has been checked above by allowedCommands)
|
// by default, use the verb (it has been checked above by allowedCommands)
|
||||||
gitcmd = exec.CommandContext(ctx, gitBinVerb, repoPath)
|
gitcmd := exec.CommandContext(ctx, gitBinVerb, words[1:]...)
|
||||||
|
|
||||||
|
if verb == gitAnnexShellVerb {
|
||||||
|
// This doesn't get its own isolated section like LFS does, because LFS
|
||||||
|
// is handled by internal Gitea routines, but git-annex has to be shelled out
|
||||||
|
// to like other git subcommands, so we need to build up gitcmd.
|
||||||
|
|
||||||
|
// TODO: does this work on Windows?
|
||||||
|
gitcmd.Env = append(gitcmd.Env,
|
||||||
|
// "If set, disallows running git-shell to handle unknown commands."
|
||||||
|
// - git-annex-shell(1)
|
||||||
|
"GIT_ANNEX_SHELL_LIMITED=True",
|
||||||
|
// "If set, git-annex-shell will refuse to run commands
|
||||||
|
// that do not operate on the specified directory."
|
||||||
|
// - git-annex-shell(1)
|
||||||
|
fmt.Sprintf("GIT_ANNEX_SHELL_DIRECTORY=%s", words[2]),
|
||||||
|
)
|
||||||
|
if results.UserMode < perm.AccessModeWrite {
|
||||||
|
// "If set, disallows any action that could modify the git-annex repository."
|
||||||
|
// - git-annex-shell(1)
|
||||||
|
// We set this when the backend API has told us that we don't have write permission to this repo.
|
||||||
|
log.Debug("Setting GIT_ANNEX_SHELL_READONLY=True")
|
||||||
|
gitcmd.Env = append(gitcmd.Env, "GIT_ANNEX_SHELL_READONLY=True")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
process.SetSysProcAttribute(gitcmd)
|
process.SetSysProcAttribute(gitcmd)
|
||||||
|
|
11
cmd/web.go
11
cmd/web.go
|
@ -9,6 +9,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -247,6 +248,12 @@ func runWeb(ctx *cli.Context) error {
|
||||||
createPIDFile(ctx.String("pid"))
|
createPIDFile(ctx.String("pid"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if setting.Annex.Enabled {
|
||||||
|
if _, err := exec.LookPath("git-annex"); err != nil {
|
||||||
|
log.Fatal("You have enabled git-annex support but git-annex is not installed. Please make sure that Forgejo's PATH contains the git-annex executable.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !setting.InstallLock {
|
if !setting.InstallLock {
|
||||||
if err := serveInstall(ctx); err != nil {
|
if err := serveInstall(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -311,6 +318,10 @@ func listen(m http.Handler, handleRedirector bool) error {
|
||||||
log.Info("LFS server enabled")
|
log.Info("LFS server enabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if setting.Annex.Enabled {
|
||||||
|
log.Info("git-annex enabled")
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
switch setting.Protocol {
|
switch setting.Protocol {
|
||||||
case setting.HTTP:
|
case setting.HTTP:
|
||||||
|
|
|
@ -529,7 +529,8 @@ INTERNAL_TOKEN =
|
||||||
;; HMAC to encode urls with, it **is required** if camo is enabled.
|
;; HMAC to encode urls with, it **is required** if camo is enabled.
|
||||||
;HMAC_KEY =
|
;HMAC_KEY =
|
||||||
;; Set to true to use camo for https too lese only non https urls are proxyed
|
;; Set to true to use camo for https too lese only non https urls are proxyed
|
||||||
;ALLWAYS = false
|
;; ALLWAYS is deprecated and will be removed in the future
|
||||||
|
;ALWAYS = false
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
@ -2653,6 +2654,17 @@ LEVEL = Info
|
||||||
;; override the minio base path if storage type is minio
|
;; override the minio base path if storage type is minio
|
||||||
;MINIO_BASE_PATH = lfs/
|
;MINIO_BASE_PATH = lfs/
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;[annex]
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;
|
||||||
|
;; Whether git-annex is enabled; defaults to false
|
||||||
|
;ENABLED = false
|
||||||
|
;; Whether to disable p2phttp support; default is the same as repository.DISABLE_HTTP_GIT
|
||||||
|
;DISABLE_P2PHTTP = false
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; settings for packages, will override storage setting
|
;; settings for packages, will override storage setting
|
||||||
|
|
18
go.mod
18
go.mod
|
@ -1,6 +1,6 @@
|
||||||
module code.gitea.io/gitea
|
module code.gitea.io/gitea
|
||||||
|
|
||||||
go 1.23.1
|
go 1.23.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
code.forgejo.org/f3/gof3/v3 v3.7.0
|
code.forgejo.org/f3/gof3/v3 v3.7.0
|
||||||
|
@ -45,6 +45,7 @@ require (
|
||||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e
|
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e
|
||||||
github.com/go-git/go-git/v5 v5.11.0
|
github.com/go-git/go-git/v5 v5.11.0
|
||||||
github.com/go-ldap/ldap/v3 v3.4.6
|
github.com/go-ldap/ldap/v3 v3.4.6
|
||||||
|
github.com/go-openapi/spec v0.20.14
|
||||||
github.com/go-sql-driver/mysql v1.8.1
|
github.com/go-sql-driver/mysql v1.8.1
|
||||||
github.com/go-swagger/go-swagger v0.30.5
|
github.com/go-swagger/go-swagger v0.30.5
|
||||||
github.com/go-testfixtures/testfixtures/v3 v3.12.0
|
github.com/go-testfixtures/testfixtures/v3 v3.12.0
|
||||||
|
@ -75,7 +76,7 @@ require (
|
||||||
github.com/meilisearch/meilisearch-go v0.28.0
|
github.com/meilisearch/meilisearch-go v0.28.0
|
||||||
github.com/mholt/archiver/v3 v3.5.1
|
github.com/mholt/archiver/v3 v3.5.1
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27
|
github.com/microcosm-cc/bluemonday v1.0.27
|
||||||
github.com/minio/minio-go/v7 v7.0.74
|
github.com/minio/minio-go/v7 v7.0.77
|
||||||
github.com/msteinert/pam v1.2.0
|
github.com/msteinert/pam v1.2.0
|
||||||
github.com/nektos/act v0.2.52
|
github.com/nektos/act v0.2.52
|
||||||
github.com/niklasfasching/go-org v1.7.0
|
github.com/niklasfasching/go-org v1.7.0
|
||||||
|
@ -101,12 +102,12 @@ require (
|
||||||
github.com/yuin/goldmark v1.7.4
|
github.com/yuin/goldmark v1.7.4
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||||
go.uber.org/mock v0.4.0
|
go.uber.org/mock v0.4.0
|
||||||
golang.org/x/crypto v0.27.0
|
golang.org/x/crypto v0.31.0
|
||||||
golang.org/x/image v0.20.0
|
golang.org/x/image v0.20.0
|
||||||
golang.org/x/net v0.29.0
|
golang.org/x/net v0.29.0
|
||||||
golang.org/x/oauth2 v0.23.0
|
golang.org/x/oauth2 v0.23.0
|
||||||
golang.org/x/sys v0.25.0
|
golang.org/x/sys v0.28.0
|
||||||
golang.org/x/text v0.18.0
|
golang.org/x/text v0.21.0
|
||||||
golang.org/x/tools v0.25.0
|
golang.org/x/tools v0.25.0
|
||||||
google.golang.org/grpc v1.66.2
|
google.golang.org/grpc v1.66.2
|
||||||
google.golang.org/protobuf v1.34.2
|
google.golang.org/protobuf v1.34.2
|
||||||
|
@ -187,7 +188,6 @@ require (
|
||||||
github.com/go-openapi/jsonreference v0.20.4 // indirect
|
github.com/go-openapi/jsonreference v0.20.4 // indirect
|
||||||
github.com/go-openapi/loads v0.21.5 // indirect
|
github.com/go-openapi/loads v0.21.5 // indirect
|
||||||
github.com/go-openapi/runtime v0.26.2 // indirect
|
github.com/go-openapi/runtime v0.26.2 // indirect
|
||||||
github.com/go-openapi/spec v0.20.14 // indirect
|
|
||||||
github.com/go-openapi/strfmt v0.22.0 // indirect
|
github.com/go-openapi/strfmt v0.22.0 // indirect
|
||||||
github.com/go-openapi/swag v0.22.7 // indirect
|
github.com/go-openapi/swag v0.22.7 // indirect
|
||||||
github.com/go-openapi/validate v0.22.6 // indirect
|
github.com/go-openapi/validate v0.22.6 // indirect
|
||||||
|
@ -250,7 +250,7 @@ require (
|
||||||
github.com/rhysd/actionlint v1.6.27 // indirect
|
github.com/rhysd/actionlint v1.6.27 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||||
github.com/rs/xid v1.5.0 // indirect
|
github.com/rs/xid v1.6.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
|
@ -281,7 +281,7 @@ require (
|
||||||
go.uber.org/zap v1.27.0 // indirect
|
go.uber.org/zap v1.27.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
|
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
|
||||||
golang.org/x/mod v0.21.0 // indirect
|
golang.org/x/mod v0.21.0 // indirect
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
golang.org/x/sync v0.10.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
|
@ -296,3 +296,5 @@ replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142
|
||||||
replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.21.3
|
replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.21.3
|
||||||
|
|
||||||
replace github.com/mholt/archiver/v3 => code.forgejo.org/forgejo/archiver/v3 v3.5.1
|
replace github.com/mholt/archiver/v3 => code.forgejo.org/forgejo/archiver/v3 v3.5.1
|
||||||
|
|
||||||
|
replace github.com/gliderlabs/ssh => code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616
|
||||||
|
|
32
go.sum
32
go.sum
|
@ -10,6 +10,8 @@ code.forgejo.org/forgejo/archiver/v3 v3.5.1 h1:UmmbA7D5550uf71SQjarmrn6yKwOGxtEj
|
||||||
code.forgejo.org/forgejo/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
code.forgejo.org/forgejo/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||||
code.forgejo.org/forgejo/reply v1.0.2 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCdgFuQ=
|
code.forgejo.org/forgejo/reply v1.0.2 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCdgFuQ=
|
||||||
code.forgejo.org/forgejo/reply v1.0.2/go.mod h1:RyZUfzQLc+fuLIGjTSQWDAJWPiL4WtKXB/FifT5fM7U=
|
code.forgejo.org/forgejo/reply v1.0.2/go.mod h1:RyZUfzQLc+fuLIGjTSQWDAJWPiL4WtKXB/FifT5fM7U=
|
||||||
|
code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616 h1:kEZL84+02jY9RxXM4zHBWZ3Fml0B09cmP1LGkDsCfIA=
|
||||||
|
code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||||
code.forgejo.org/go-chi/cache v0.0.0-20240912103640-dcb08fba860d h1:nOu/2GX571t4intmtfvpctS148OqsBYrGUySVm93ifc=
|
code.forgejo.org/go-chi/cache v0.0.0-20240912103640-dcb08fba860d h1:nOu/2GX571t4intmtfvpctS148OqsBYrGUySVm93ifc=
|
||||||
code.forgejo.org/go-chi/cache v0.0.0-20240912103640-dcb08fba860d/go.mod h1:OVlZ/TqDYJ+RUJ+R+J+OLxtlyjo3pbjBeK7LAWAB+Vk=
|
code.forgejo.org/go-chi/cache v0.0.0-20240912103640-dcb08fba860d/go.mod h1:OVlZ/TqDYJ+RUJ+R+J+OLxtlyjo3pbjBeK7LAWAB+Vk=
|
||||||
code.forgejo.org/go-chi/captcha v0.0.0-20240905153133-df43b9250ed5 h1:A7P1liXCpJBHEJ5KIDsF0ujnQ8FQ/aX1UixTW0vGrDQ=
|
code.forgejo.org/go-chi/captcha v0.0.0-20240905153133-df43b9250ed5 h1:A7P1liXCpJBHEJ5KIDsF0ujnQ8FQ/aX1UixTW0vGrDQ=
|
||||||
|
@ -225,8 +227,6 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||||
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
|
||||||
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
|
||||||
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9 h1:j2TrkUG/NATGi/EQS+MvEoF79CxiRUmT16ErFroNcKI=
|
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9 h1:j2TrkUG/NATGi/EQS+MvEoF79CxiRUmT16ErFroNcKI=
|
||||||
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9/go.mod h1:cJ9Ye0ZNSMN7RzZDBRY3E+8M3Bpf/R1JX22Ir9yX6WI=
|
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9/go.mod h1:cJ9Ye0ZNSMN7RzZDBRY3E+8M3Bpf/R1JX22Ir9yX6WI=
|
||||||
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 h1:I2nuhyVI/48VXoRCCZR2hYBgnSXa+EuDJf/VyX06TC0=
|
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 h1:I2nuhyVI/48VXoRCCZR2hYBgnSXa+EuDJf/VyX06TC0=
|
||||||
|
@ -502,8 +502,8 @@ github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
||||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||||
github.com/minio/minio-go/v7 v7.0.74 h1:fTo/XlPBTSpo3BAMshlwKL5RspXRv9us5UeHEGYCFe0=
|
github.com/minio/minio-go/v7 v7.0.77 h1:GaGghJRg9nwDVlNbwYjSDJT1rqltQkBFDsypWX1v3Bw=
|
||||||
github.com/minio/minio-go/v7 v7.0.74/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8=
|
github.com/minio/minio-go/v7 v7.0.77/go.mod h1:AVM3IUN6WwKzmwBxVdjzhH8xq+f57JSbbvzqvUzR6eg=
|
||||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||||
|
@ -599,8 +599,8 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||||
|
@ -734,8 +734,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
|
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
|
||||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
||||||
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
|
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
|
||||||
|
@ -772,8 +772,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -803,8 +803,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
|
@ -814,8 +814,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
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=
|
||||||
|
@ -827,8 +827,8 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.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.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.5.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=
|
||||||
|
|
|
@ -69,7 +69,7 @@ func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPa
|
||||||
OwnerID: t.OwnerID,
|
OwnerID: t.OwnerID,
|
||||||
CommitSHA: t.CommitSHA,
|
CommitSHA: t.CommitSHA,
|
||||||
Status: int64(ArtifactStatusUploadPending),
|
Status: int64(ArtifactStatusUploadPending),
|
||||||
ExpiredUnix: timeutil.TimeStamp(time.Now().Unix() + 3600*24*expiredDays),
|
ExpiredUnix: timeutil.TimeStamp(time.Now().Unix() + timeutil.Day*expiredDays),
|
||||||
}
|
}
|
||||||
if _, err := db.GetEngine(ctx).Insert(artifact); err != nil {
|
if _, err := db.GetEngine(ctx).Insert(artifact); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -78,6 +78,13 @@ func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPa
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := db.GetEngine(ctx).ID(artifact.ID).Cols("expired_unix").Update(&ActionArtifact{
|
||||||
|
ExpiredUnix: timeutil.TimeStamp(time.Now().Unix() + timeutil.Day*expiredDays),
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return artifact, nil
|
return artifact, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -146,7 +146,11 @@ func (run *ActionRun) GetPushEventPayload() (*api.PushPayload, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, error) {
|
func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, error) {
|
||||||
if run.Event == webhook_module.HookEventPullRequest || run.Event == webhook_module.HookEventPullRequestSync {
|
if run.Event == webhook_module.HookEventPullRequest ||
|
||||||
|
run.Event == webhook_module.HookEventPullRequestSync ||
|
||||||
|
run.Event == webhook_module.HookEventPullRequestAssign ||
|
||||||
|
run.Event == webhook_module.HookEventPullRequestMilestone ||
|
||||||
|
run.Event == webhook_module.HookEventPullRequestLabel {
|
||||||
var payload api.PullRequestPayload
|
var payload api.PullRequestPayload
|
||||||
if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil {
|
if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -118,21 +118,23 @@ 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, cancelPreviousJobs bool) 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 fmt.Errorf("DeleteCronTaskByRepo: %v", err)
|
||||||
}
|
}
|
||||||
// cancel running cron jobs of this repository and delete old schedules
|
if cancelPreviousJobs {
|
||||||
if err := CancelPreviousJobs(
|
// cancel running cron jobs of this repository and delete old schedules
|
||||||
ctx,
|
if err := CancelPreviousJobs(
|
||||||
repo.ID,
|
ctx,
|
||||||
repo.DefaultBranch,
|
repo.ID,
|
||||||
"",
|
repo.DefaultBranch,
|
||||||
webhook_module.HookEventSchedule,
|
"",
|
||||||
); err != nil {
|
webhook_module.HookEventSchedule,
|
||||||
return fmt.Errorf("CancelPreviousJobs: %v", err)
|
); err != nil {
|
||||||
|
return fmt.Errorf("CancelPreviousJobs: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -250,6 +250,9 @@ func (a *Action) GetActDisplayNameTitle(ctx context.Context) string {
|
||||||
// GetRepoUserName returns the name of the action repository owner.
|
// GetRepoUserName returns the name of the action repository owner.
|
||||||
func (a *Action) GetRepoUserName(ctx context.Context) string {
|
func (a *Action) GetRepoUserName(ctx context.Context) string {
|
||||||
a.loadRepo(ctx)
|
a.loadRepo(ctx)
|
||||||
|
if a.Repo == nil {
|
||||||
|
return "(non-existing-repo)"
|
||||||
|
}
|
||||||
return a.Repo.OwnerName
|
return a.Repo.OwnerName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,6 +265,9 @@ func (a *Action) ShortRepoUserName(ctx context.Context) string {
|
||||||
// GetRepoName returns the name of the action repository.
|
// GetRepoName returns the name of the action repository.
|
||||||
func (a *Action) GetRepoName(ctx context.Context) string {
|
func (a *Action) GetRepoName(ctx context.Context) string {
|
||||||
a.loadRepo(ctx)
|
a.loadRepo(ctx)
|
||||||
|
if a.Repo == nil {
|
||||||
|
return "(non-existing-repo)"
|
||||||
|
}
|
||||||
return a.Repo.Name
|
return a.Repo.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ type ActivityStats struct {
|
||||||
OpenedPRAuthorCount int64
|
OpenedPRAuthorCount int64
|
||||||
MergedPRs issues_model.PullRequestList
|
MergedPRs issues_model.PullRequestList
|
||||||
MergedPRAuthorCount int64
|
MergedPRAuthorCount int64
|
||||||
|
ActiveIssues issues_model.IssueList
|
||||||
OpenedIssues issues_model.IssueList
|
OpenedIssues issues_model.IssueList
|
||||||
OpenedIssueAuthorCount int64
|
OpenedIssueAuthorCount int64
|
||||||
ClosedIssues issues_model.IssueList
|
ClosedIssues issues_model.IssueList
|
||||||
|
@ -172,7 +173,7 @@ func (stats *ActivityStats) MergedPRPerc() int {
|
||||||
|
|
||||||
// ActiveIssueCount returns total active issue count
|
// ActiveIssueCount returns total active issue count
|
||||||
func (stats *ActivityStats) ActiveIssueCount() int {
|
func (stats *ActivityStats) ActiveIssueCount() int {
|
||||||
return stats.OpenedIssueCount() + stats.ClosedIssueCount()
|
return len(stats.ActiveIssues)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenedIssueCount returns open issue count
|
// OpenedIssueCount returns open issue count
|
||||||
|
@ -285,13 +286,21 @@ func (stats *ActivityStats) FillIssues(ctx context.Context, repoID int64, fromTi
|
||||||
stats.ClosedIssueAuthorCount = count
|
stats.ClosedIssueAuthorCount = count
|
||||||
|
|
||||||
// New issues
|
// New issues
|
||||||
sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false)
|
sess = newlyCreatedIssues(ctx, repoID, fromTime)
|
||||||
sess.OrderBy("issue.created_unix ASC")
|
sess.OrderBy("issue.created_unix ASC")
|
||||||
stats.OpenedIssues = make(issues_model.IssueList, 0)
|
stats.OpenedIssues = make(issues_model.IssueList, 0)
|
||||||
if err = sess.Find(&stats.OpenedIssues); err != nil {
|
if err = sess.Find(&stats.OpenedIssues); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Active issues
|
||||||
|
sess = activeIssues(ctx, repoID, fromTime)
|
||||||
|
sess.OrderBy("issue.created_unix ASC")
|
||||||
|
stats.ActiveIssues = make(issues_model.IssueList, 0)
|
||||||
|
if err = sess.Find(&stats.ActiveIssues); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Opened issue authors
|
// Opened issue authors
|
||||||
sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false)
|
sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false)
|
||||||
if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("issue").Get(&count); err != nil {
|
if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("issue").Get(&count); err != nil {
|
||||||
|
@ -317,6 +326,22 @@ func (stats *ActivityStats) FillUnresolvedIssues(ctx context.Context, repoID int
|
||||||
return sess.Find(&stats.UnresolvedIssues)
|
return sess.Find(&stats.UnresolvedIssues)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newlyCreatedIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session {
|
||||||
|
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
|
||||||
|
And("issue.is_pull = ?", false). // Retain the is_pull check to exclude pull requests
|
||||||
|
And("issue.created_unix >= ?", fromTime.Unix()) // Include all issues created after fromTime
|
||||||
|
|
||||||
|
return sess
|
||||||
|
}
|
||||||
|
|
||||||
|
func activeIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session {
|
||||||
|
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
|
||||||
|
And("issue.is_pull = ?", false).
|
||||||
|
And("issue.created_unix >= ? OR issue.closed_unix >= ?", fromTime.Unix(), fromTime.Unix())
|
||||||
|
|
||||||
|
return sess
|
||||||
|
}
|
||||||
|
|
||||||
func issuesForActivityStatement(ctx context.Context, repoID int64, fromTime time.Time, closed, unresolved bool) *xorm.Session {
|
func issuesForActivityStatement(ctx context.Context, repoID int64, fromTime time.Time, closed, unresolved bool) *xorm.Session {
|
||||||
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
|
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
|
||||||
And("issue.is_closed = ?", closed)
|
And("issue.is_closed = ?", closed)
|
||||||
|
|
30
models/activities/repo_activity_test.go
Normal file
30
models/activities/repo_activity_test.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package activities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetActivityStats(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
|
||||||
|
stats, err := GetActivityStats(db.DefaultContext, repo, time.Unix(0, 0), true, true, true, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.EqualValues(t, 2, stats.ActiveIssueCount())
|
||||||
|
assert.EqualValues(t, 2, stats.OpenedIssueCount())
|
||||||
|
assert.EqualValues(t, 0, stats.ClosedIssueCount())
|
||||||
|
assert.EqualValues(t, 3, stats.ActivePRCount())
|
||||||
|
}
|
|
@ -23,3 +23,11 @@
|
||||||
redirect_uris: '["http://127.0.0.1", "https://127.0.0.1"]'
|
redirect_uris: '["http://127.0.0.1", "https://127.0.0.1"]'
|
||||||
created_unix: 1712358091
|
created_unix: 1712358091
|
||||||
updated_unix: 1712358091
|
updated_unix: 1712358091
|
||||||
|
-
|
||||||
|
id: 1003
|
||||||
|
uid: 0
|
||||||
|
name: "Global Auth source that should be kept"
|
||||||
|
client_id: "2f3467c1-7b3b-463d-ab04-2ae2b2712826"
|
||||||
|
redirect_uris: '["http://example.com/globalapp", "https://example.com/globalapp"]'
|
||||||
|
created_unix: 1732387292
|
||||||
|
updated_unix: 1732387292
|
||||||
|
|
|
@ -15,12 +15,31 @@ import (
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type AuthorizationPurpose string
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Used to store long term authorization tokens.
|
||||||
|
LongTermAuthorization AuthorizationPurpose = "long_term_authorization"
|
||||||
|
|
||||||
|
// Used to activate a user account.
|
||||||
|
UserActivation AuthorizationPurpose = "user_activation"
|
||||||
|
|
||||||
|
// Used to reset the password.
|
||||||
|
PasswordReset AuthorizationPurpose = "password_reset"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Used to activate the specified email address for a user.
|
||||||
|
func EmailActivation(email string) AuthorizationPurpose {
|
||||||
|
return AuthorizationPurpose("email_activation:" + email)
|
||||||
|
}
|
||||||
|
|
||||||
// AuthorizationToken represents a authorization token to a user.
|
// AuthorizationToken represents a authorization token to a user.
|
||||||
type AuthorizationToken struct {
|
type AuthorizationToken struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
UID int64 `xorm:"INDEX"`
|
UID int64 `xorm:"INDEX"`
|
||||||
LookupKey string `xorm:"INDEX UNIQUE"`
|
LookupKey string `xorm:"INDEX UNIQUE"`
|
||||||
HashedValidator string
|
HashedValidator string
|
||||||
|
Purpose AuthorizationPurpose `xorm:"NOT NULL DEFAULT 'long_term_authorization'"`
|
||||||
Expiry timeutil.TimeStamp
|
Expiry timeutil.TimeStamp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +60,7 @@ func (authToken *AuthorizationToken) IsExpired() bool {
|
||||||
// GenerateAuthToken generates a new authentication token for the given user.
|
// GenerateAuthToken generates a new authentication token for the given user.
|
||||||
// It returns the lookup key and validator values that should be passed to the
|
// It returns the lookup key and validator values that should be passed to the
|
||||||
// user via a long-term cookie.
|
// user via a long-term cookie.
|
||||||
func GenerateAuthToken(ctx context.Context, userID int64, expiry timeutil.TimeStamp) (lookupKey, validator string, err error) {
|
func GenerateAuthToken(ctx context.Context, userID int64, expiry timeutil.TimeStamp, purpose AuthorizationPurpose) (lookupKey, validator string, err error) {
|
||||||
// Request 64 random bytes. The first 32 bytes will be used for the lookupKey
|
// Request 64 random bytes. The first 32 bytes will be used for the lookupKey
|
||||||
// and the other 32 bytes will be used for the validator.
|
// and the other 32 bytes will be used for the validator.
|
||||||
rBytes, err := util.CryptoRandomBytes(64)
|
rBytes, err := util.CryptoRandomBytes(64)
|
||||||
|
@ -56,14 +75,15 @@ func GenerateAuthToken(ctx context.Context, userID int64, expiry timeutil.TimeSt
|
||||||
Expiry: expiry,
|
Expiry: expiry,
|
||||||
LookupKey: lookupKey,
|
LookupKey: lookupKey,
|
||||||
HashedValidator: HashValidator(rBytes[32:]),
|
HashedValidator: HashValidator(rBytes[32:]),
|
||||||
|
Purpose: purpose,
|
||||||
})
|
})
|
||||||
return lookupKey, validator, err
|
return lookupKey, validator, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindAuthToken will find a authorization token via the lookup key.
|
// FindAuthToken will find a authorization token via the lookup key.
|
||||||
func FindAuthToken(ctx context.Context, lookupKey string) (*AuthorizationToken, error) {
|
func FindAuthToken(ctx context.Context, lookupKey string, purpose AuthorizationPurpose) (*AuthorizationToken, error) {
|
||||||
var authToken AuthorizationToken
|
var authToken AuthorizationToken
|
||||||
has, err := db.GetEngine(ctx).Where("lookup_key = ?", lookupKey).Get(&authToken)
|
has, err := db.GetEngine(ctx).Where("lookup_key = ? AND purpose = ?", lookupKey, purpose).Get(&authToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if !has {
|
} else if !has {
|
||||||
|
|
|
@ -657,6 +657,7 @@ func CountOrphanedOAuth2Applications(ctx context.Context) (int64, error) {
|
||||||
Table("`oauth2_application`").
|
Table("`oauth2_application`").
|
||||||
Join("LEFT", "`user`", "`oauth2_application`.`uid` = `user`.`id`").
|
Join("LEFT", "`user`", "`oauth2_application`.`uid` = `user`.`id`").
|
||||||
Where(builder.IsNull{"`user`.id"}).
|
Where(builder.IsNull{"`user`.id"}).
|
||||||
|
Where(builder.Neq{"uid": 0}). // exclude instance-wide admin applications
|
||||||
Where(builder.NotIn("`oauth2_application`.`client_id`", BuiltinApplicationsClientIDs())).
|
Where(builder.NotIn("`oauth2_application`.`client_id`", BuiltinApplicationsClientIDs())).
|
||||||
Select("COUNT(`oauth2_application`.`id`)").
|
Select("COUNT(`oauth2_application`.`id`)").
|
||||||
Count()
|
Count()
|
||||||
|
@ -668,6 +669,7 @@ func DeleteOrphanedOAuth2Applications(ctx context.Context) (int64, error) {
|
||||||
From("`oauth2_application`").
|
From("`oauth2_application`").
|
||||||
Join("LEFT", "`user`", "`oauth2_application`.`uid` = `user`.`id`").
|
Join("LEFT", "`user`", "`oauth2_application`.`uid` = `user`.`id`").
|
||||||
Where(builder.IsNull{"`user`.id"}).
|
Where(builder.IsNull{"`user`.id"}).
|
||||||
|
Where(builder.Neq{"uid": 0}). // exclude instance-wide admin applications
|
||||||
Where(builder.NotIn("`oauth2_application`.`client_id`", BuiltinApplicationsClientIDs()))
|
Where(builder.NotIn("`oauth2_application`.`client_id`", BuiltinApplicationsClientIDs()))
|
||||||
|
|
||||||
b := builder.Delete(builder.In("id", subQuery)).From("`oauth2_application`")
|
b := builder.Delete(builder.In("id", subQuery)).From("`oauth2_application`")
|
||||||
|
|
|
@ -296,4 +296,5 @@ func TestOrphanedOAuth2Applications(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.EqualValues(t, 0, count)
|
assert.EqualValues(t, 0, count)
|
||||||
unittest.AssertExistsIf(t, false, &auth_model.OAuth2Application{ID: 1002})
|
unittest.AssertExistsIf(t, false, &auth_model.OAuth2Application{ID: 1002})
|
||||||
|
unittest.AssertExistsIf(t, true, &auth_model.OAuth2Application{ID: 1003})
|
||||||
}
|
}
|
||||||
|
|
|
@ -216,7 +216,7 @@ func CreateSource(ctx context.Context, source *Source) error {
|
||||||
return ErrSourceAlreadyExist{source.Name}
|
return ErrSourceAlreadyExist{source.Name}
|
||||||
}
|
}
|
||||||
// Synchronization is only available with LDAP for now
|
// Synchronization is only available with LDAP for now
|
||||||
if !source.IsLDAP() && !source.IsOAuth2() {
|
if !source.IsLDAP() {
|
||||||
source.IsSyncEnabled = false
|
source.IsSyncEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,22 @@
|
||||||
|
-
|
||||||
|
id: 46
|
||||||
|
attempt: 3
|
||||||
|
runner_id: 1
|
||||||
|
status: 3 # 3 is the status code for "cancelled"
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
repo_id: 4
|
||||||
|
owner_id: 1
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
token_hash: 6d8ef48297195edcc8e22c70b3020eaa06c52976db67d39b4260c64a69a2cc1508825121b7b8394e48e00b1bf8718b2aaaaa
|
||||||
|
token_salt: eeeeeeee
|
||||||
|
token_last_eight: eeeeeeee
|
||||||
|
log_filename: artifact-test2/2f/47.log
|
||||||
|
log_in_storage: 1
|
||||||
|
log_length: 707
|
||||||
|
log_size: 90179
|
||||||
|
log_expired: 0
|
||||||
-
|
-
|
||||||
id: 47
|
id: 47
|
||||||
job_id: 192
|
job_id: 192
|
||||||
|
|
|
@ -94,3 +94,22 @@
|
||||||
content: "test markup light/dark-mode-only "
|
content: "test markup light/dark-mode-only "
|
||||||
created_unix: 946684813
|
created_unix: 946684813
|
||||||
updated_unix: 946684813
|
updated_unix: 946684813
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 11
|
||||||
|
type: 22 # review
|
||||||
|
poster_id: 5
|
||||||
|
issue_id: 3 # in repo_id 1
|
||||||
|
content: "reviewed by user5"
|
||||||
|
review_id: 21
|
||||||
|
created_unix: 946684816
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 12
|
||||||
|
type: 27 # review request
|
||||||
|
poster_id: 2
|
||||||
|
issue_id: 3 # in repo_id 1
|
||||||
|
content: "review request for user5"
|
||||||
|
review_id: 22
|
||||||
|
assignee_id: 5
|
||||||
|
created_unix: 946684817
|
||||||
|
|
|
@ -91,6 +91,8 @@
|
||||||
size: 0
|
size: 0
|
||||||
is_fsck_enabled: true
|
is_fsck_enabled: true
|
||||||
close_issues_via_commit_in_any_branch: false
|
close_issues_via_commit_in_any_branch: false
|
||||||
|
created_unix: 1700000001
|
||||||
|
updated_unix: 1700000001
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 4
|
id: 4
|
||||||
|
@ -152,6 +154,8 @@
|
||||||
size: 0
|
size: 0
|
||||||
is_fsck_enabled: true
|
is_fsck_enabled: true
|
||||||
close_issues_via_commit_in_any_branch: false
|
close_issues_via_commit_in_any_branch: false
|
||||||
|
created_unix: 1700000002
|
||||||
|
updated_unix: 1700000002
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 6
|
id: 6
|
||||||
|
@ -182,6 +186,8 @@
|
||||||
size: 0
|
size: 0
|
||||||
is_fsck_enabled: true
|
is_fsck_enabled: true
|
||||||
close_issues_via_commit_in_any_branch: false
|
close_issues_via_commit_in_any_branch: false
|
||||||
|
created_unix: 1710000001
|
||||||
|
updated_unix: 1710000001
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 7
|
id: 7
|
||||||
|
@ -212,6 +218,8 @@
|
||||||
size: 0
|
size: 0
|
||||||
is_fsck_enabled: true
|
is_fsck_enabled: true
|
||||||
close_issues_via_commit_in_any_branch: false
|
close_issues_via_commit_in_any_branch: false
|
||||||
|
created_unix: 1710000003
|
||||||
|
updated_unix: 1710000003
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 8
|
id: 8
|
||||||
|
@ -242,6 +250,8 @@
|
||||||
size: 0
|
size: 0
|
||||||
is_fsck_enabled: true
|
is_fsck_enabled: true
|
||||||
close_issues_via_commit_in_any_branch: false
|
close_issues_via_commit_in_any_branch: false
|
||||||
|
created_unix: 1710000002
|
||||||
|
updated_unix: 1710000002
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 9
|
id: 9
|
||||||
|
@ -968,6 +978,8 @@
|
||||||
size: 0
|
size: 0
|
||||||
is_fsck_enabled: true
|
is_fsck_enabled: true
|
||||||
close_issues_via_commit_in_any_branch: false
|
close_issues_via_commit_in_any_branch: false
|
||||||
|
created_unix: 1700000003
|
||||||
|
updated_unix: 1700000003
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 33
|
id: 33
|
||||||
|
@ -1811,4 +1823,4 @@
|
||||||
template_id: 0
|
template_id: 0
|
||||||
size: 0
|
size: 0
|
||||||
is_fsck_enabled: true
|
is_fsck_enabled: true
|
||||||
close_issues_via_commit_in_any_branch: false
|
close_issues_via_commit_in_any_branch: false
|
||||||
|
|
|
@ -179,3 +179,22 @@
|
||||||
content: "Review Comment"
|
content: "Review Comment"
|
||||||
updated_unix: 946684810
|
updated_unix: 946684810
|
||||||
created_unix: 946684810
|
created_unix: 946684810
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 21
|
||||||
|
type: 2
|
||||||
|
reviewer_id: 5
|
||||||
|
issue_id: 3
|
||||||
|
content: "reviewed by user5"
|
||||||
|
commit_id: 4a357436d925b5c974181ff12a994538ddc5a269
|
||||||
|
updated_unix: 946684816
|
||||||
|
created_unix: 946684816
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 22
|
||||||
|
type: 4
|
||||||
|
reviewer_id: 5
|
||||||
|
issue_id: 3
|
||||||
|
content: "review request for user5"
|
||||||
|
updated_unix: 946684817
|
||||||
|
created_unix: 946684817
|
||||||
|
|
|
@ -332,6 +332,7 @@
|
||||||
repo_admin_change_team_access: false
|
repo_admin_change_team_access: false
|
||||||
theme: ""
|
theme: ""
|
||||||
keep_activity_private: false
|
keep_activity_private: false
|
||||||
|
created_unix: 1730468968
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 10
|
id: 10
|
||||||
|
|
11
models/issues/TestGetUIDsAndStopwatch/stopwatch.yml
Normal file
11
models/issues/TestGetUIDsAndStopwatch/stopwatch.yml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
-
|
||||||
|
id: 3
|
||||||
|
user_id: 1
|
||||||
|
issue_id: 2
|
||||||
|
created_unix: 1500988004
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 4
|
||||||
|
user_id: 3
|
||||||
|
issue_id: 0
|
||||||
|
created_unix: 1500988003
|
|
@ -111,9 +111,7 @@ func NewIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *user_m
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
issue.isLabelsLoaded = false
|
if err = issue.ReloadLabels(ctx); err != nil {
|
||||||
issue.Labels = nil
|
|
||||||
if err = issue.LoadLabels(ctx); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,10 +159,7 @@ func NewIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *us
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// reload all labels
|
if err = issue.ReloadLabels(ctx); err != nil {
|
||||||
issue.isLabelsLoaded = false
|
|
||||||
issue.Labels = nil
|
|
||||||
if err = issue.LoadLabels(ctx); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,8 +200,7 @@ func DeleteIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *use
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
issue.Labels = nil
|
return issue.ReloadLabels(ctx)
|
||||||
return issue.LoadLabels(ctx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteLabelsByRepoID deletes labels of some repository
|
// DeleteLabelsByRepoID deletes labels of some repository
|
||||||
|
@ -326,14 +320,23 @@ func FixIssueLabelWithOutsideLabels(ctx context.Context) (int64, error) {
|
||||||
return res.RowsAffected()
|
return res.RowsAffected()
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadLabels loads labels
|
// LoadLabels only if they are not already set
|
||||||
func (issue *Issue) LoadLabels(ctx context.Context) (err error) {
|
func (issue *Issue) LoadLabels(ctx context.Context) (err error) {
|
||||||
if !issue.isLabelsLoaded && issue.Labels == nil && issue.ID != 0 {
|
if !issue.isLabelsLoaded && issue.Labels == nil {
|
||||||
|
if err := issue.ReloadLabels(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
issue.isLabelsLoaded = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (issue *Issue) ReloadLabels(ctx context.Context) (err error) {
|
||||||
|
if issue.ID != 0 {
|
||||||
issue.Labels, err = GetLabelsByIssueID(ctx, issue.ID)
|
issue.Labels, err = GetLabelsByIssueID(ctx, issue.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("getLabelsByIssueID [%d]: %w", issue.ID, err)
|
return fmt.Errorf("getLabelsByIssueID [%d]: %w", issue.ID, err)
|
||||||
}
|
}
|
||||||
issue.isLabelsLoaded = true
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -496,8 +499,7 @@ func ReplaceIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
issue.Labels = nil
|
if err = issue.ReloadLabels(ctx); err != nil {
|
||||||
if err = issue.LoadLabels(ctx); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,114 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestIssueNewIssueLabels(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
|
||||||
|
label1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
|
||||||
|
label2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 4})
|
||||||
|
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
|
label3 := &issues_model.Label{RepoID: 1, Name: "label3", Color: "#123"}
|
||||||
|
require.NoError(t, issues_model.NewLabel(db.DefaultContext, label3))
|
||||||
|
|
||||||
|
// label1 is already set, do nothing
|
||||||
|
// label3 is new, add it
|
||||||
|
require.NoError(t, issues_model.NewIssueLabels(db.DefaultContext, issue, []*issues_model.Label{label1, label3}, doer))
|
||||||
|
|
||||||
|
assert.Len(t, issue.Labels, 3)
|
||||||
|
// check that the pre-existing label1 is still present
|
||||||
|
assert.Equal(t, label1.ID, issue.Labels[0].ID)
|
||||||
|
// check that new label3 was added
|
||||||
|
assert.Equal(t, label3.ID, issue.Labels[1].ID)
|
||||||
|
// check that pre-existing label2 was not removed
|
||||||
|
assert.Equal(t, label2.ID, issue.Labels[2].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssueNewIssueLabel(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
|
||||||
|
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
|
label := &issues_model.Label{RepoID: 1, Name: "label3", Color: "#123"}
|
||||||
|
require.NoError(t, issues_model.NewLabel(db.DefaultContext, label))
|
||||||
|
|
||||||
|
require.NoError(t, issues_model.NewIssueLabel(db.DefaultContext, issue, label, doer))
|
||||||
|
|
||||||
|
assert.Len(t, issue.Labels, 1)
|
||||||
|
assert.Equal(t, label.ID, issue.Labels[0].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssueReplaceIssueLabels(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
|
||||||
|
label1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
|
||||||
|
label2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 4})
|
||||||
|
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
|
label3 := &issues_model.Label{RepoID: 1, Name: "label3", Color: "#123"}
|
||||||
|
require.NoError(t, issues_model.NewLabel(db.DefaultContext, label3))
|
||||||
|
|
||||||
|
issue.LoadLabels(db.DefaultContext)
|
||||||
|
assert.Len(t, issue.Labels, 2)
|
||||||
|
assert.Equal(t, label1.ID, issue.Labels[0].ID)
|
||||||
|
assert.Equal(t, label2.ID, issue.Labels[1].ID)
|
||||||
|
|
||||||
|
// label1 is already set, do nothing
|
||||||
|
// label3 is new, add it
|
||||||
|
// label2 is not in the list but already set, remove it
|
||||||
|
require.NoError(t, issues_model.ReplaceIssueLabels(db.DefaultContext, issue, []*issues_model.Label{label1, label3}, doer))
|
||||||
|
|
||||||
|
assert.Len(t, issue.Labels, 2)
|
||||||
|
assert.Equal(t, label1.ID, issue.Labels[0].ID)
|
||||||
|
assert.Equal(t, label3.ID, issue.Labels[1].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssueDeleteIssueLabel(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
|
||||||
|
label1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
|
||||||
|
label2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 4})
|
||||||
|
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
|
issue.LoadLabels(db.DefaultContext)
|
||||||
|
assert.Len(t, issue.Labels, 2)
|
||||||
|
assert.Equal(t, label1.ID, issue.Labels[0].ID)
|
||||||
|
assert.Equal(t, label2.ID, issue.Labels[1].ID)
|
||||||
|
|
||||||
|
require.NoError(t, issues_model.DeleteIssueLabel(db.DefaultContext, issue, label2, doer))
|
||||||
|
|
||||||
|
assert.Len(t, issue.Labels, 1)
|
||||||
|
assert.Equal(t, label1.ID, issue.Labels[0].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssueLoadLabels(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
|
||||||
|
label1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
|
||||||
|
label2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 4})
|
||||||
|
|
||||||
|
assert.Empty(t, issue.Labels)
|
||||||
|
issue.LoadLabels(db.DefaultContext)
|
||||||
|
assert.Len(t, issue.Labels, 2)
|
||||||
|
assert.Equal(t, label1.ID, issue.Labels[0].ID)
|
||||||
|
assert.Equal(t, label2.ID, issue.Labels[1].ID)
|
||||||
|
|
||||||
|
unittest.AssertSuccessfulDelete(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: label2.ID})
|
||||||
|
|
||||||
|
// the database change is not noticed because the labels are cached
|
||||||
|
issue.LoadLabels(db.DefaultContext)
|
||||||
|
assert.Len(t, issue.Labels, 2)
|
||||||
|
|
||||||
|
issue.ReloadLabels(db.DefaultContext)
|
||||||
|
assert.Len(t, issue.Labels, 1)
|
||||||
|
assert.Equal(t, label1.ID, issue.Labels[0].ID)
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewIssueLabelsScope(t *testing.T) {
|
func TestNewIssueLabelsScope(t *testing.T) {
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
|
|
@ -408,7 +408,7 @@ func (pr *PullRequest) getReviewedByLines(ctx context.Context, writer io.Writer)
|
||||||
|
|
||||||
// Note: This doesn't page as we only expect a very limited number of reviews
|
// Note: This doesn't page as we only expect a very limited number of reviews
|
||||||
reviews, err := FindLatestReviews(ctx, FindReviewOptions{
|
reviews, err := FindLatestReviews(ctx, FindReviewOptions{
|
||||||
Type: ReviewTypeApprove,
|
Types: []ReviewType{ReviewTypeApprove},
|
||||||
IssueID: pr.IssueID,
|
IssueID: pr.IssueID,
|
||||||
OfficialOnly: setting.Repository.PullRequest.DefaultMergeMessageOfficialApproversOnly,
|
OfficialOnly: setting.Repository.PullRequest.DefaultMergeMessageOfficialApproversOnly,
|
||||||
})
|
})
|
||||||
|
|
|
@ -364,7 +364,7 @@ func GetCurrentReview(ctx context.Context, reviewer *user_model.User, issue *Iss
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
reviews, err := FindReviews(ctx, FindReviewOptions{
|
reviews, err := FindReviews(ctx, FindReviewOptions{
|
||||||
Type: ReviewTypePending,
|
Types: []ReviewType{ReviewTypePending},
|
||||||
IssueID: issue.ID,
|
IssueID: issue.ID,
|
||||||
ReviewerID: reviewer.ID,
|
ReviewerID: reviewer.ID,
|
||||||
})
|
})
|
||||||
|
|
|
@ -92,7 +92,7 @@ func (reviews ReviewList) LoadIssues(ctx context.Context) error {
|
||||||
// FindReviewOptions represent possible filters to find reviews
|
// FindReviewOptions represent possible filters to find reviews
|
||||||
type FindReviewOptions struct {
|
type FindReviewOptions struct {
|
||||||
db.ListOptions
|
db.ListOptions
|
||||||
Type ReviewType
|
Types []ReviewType
|
||||||
IssueID int64
|
IssueID int64
|
||||||
ReviewerID int64
|
ReviewerID int64
|
||||||
OfficialOnly bool
|
OfficialOnly bool
|
||||||
|
@ -107,8 +107,8 @@ func (opts *FindReviewOptions) toCond() builder.Cond {
|
||||||
if opts.ReviewerID > 0 {
|
if opts.ReviewerID > 0 {
|
||||||
cond = cond.And(builder.Eq{"reviewer_id": opts.ReviewerID})
|
cond = cond.And(builder.Eq{"reviewer_id": opts.ReviewerID})
|
||||||
}
|
}
|
||||||
if opts.Type != ReviewTypeUnknown {
|
if len(opts.Types) > 0 {
|
||||||
cond = cond.And(builder.Eq{"type": opts.Type})
|
cond = cond.And(builder.In("type", opts.Types))
|
||||||
}
|
}
|
||||||
if opts.OfficialOnly {
|
if opts.OfficialOnly {
|
||||||
cond = cond.And(builder.Eq{"official": true})
|
cond = cond.And(builder.Eq{"official": true})
|
||||||
|
|
|
@ -64,7 +64,7 @@ func TestReviewType_Icon(t *testing.T) {
|
||||||
func TestFindReviews(t *testing.T) {
|
func TestFindReviews(t *testing.T) {
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
reviews, err := issues_model.FindReviews(db.DefaultContext, issues_model.FindReviewOptions{
|
reviews, err := issues_model.FindReviews(db.DefaultContext, issues_model.FindReviewOptions{
|
||||||
Type: issues_model.ReviewTypeApprove,
|
Types: []issues_model.ReviewType{issues_model.ReviewTypeApprove},
|
||||||
IssueID: 2,
|
IssueID: 2,
|
||||||
ReviewerID: 1,
|
ReviewerID: 1,
|
||||||
})
|
})
|
||||||
|
@ -76,7 +76,7 @@ func TestFindReviews(t *testing.T) {
|
||||||
func TestFindLatestReviews(t *testing.T) {
|
func TestFindLatestReviews(t *testing.T) {
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
reviews, err := issues_model.FindLatestReviews(db.DefaultContext, issues_model.FindReviewOptions{
|
reviews, err := issues_model.FindLatestReviews(db.DefaultContext, issues_model.FindReviewOptions{
|
||||||
Type: issues_model.ReviewTypeApprove,
|
Types: []issues_model.ReviewType{issues_model.ReviewTypeApprove},
|
||||||
IssueID: 11,
|
IssueID: 11,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -60,34 +60,19 @@ func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, ex
|
||||||
return sw, exists, err
|
return sw, exists, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserIDCount is a simple coalition of UserID and Count
|
|
||||||
type UserStopwatch struct {
|
|
||||||
UserID int64
|
|
||||||
StopWatches []*Stopwatch
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUIDsAndNotificationCounts between the two provided times
|
// GetUIDsAndNotificationCounts between the two provided times
|
||||||
func GetUIDsAndStopwatch(ctx context.Context) ([]*UserStopwatch, error) {
|
func GetUIDsAndStopwatch(ctx context.Context) (map[int64][]*Stopwatch, error) {
|
||||||
sws := []*Stopwatch{}
|
sws := []*Stopwatch{}
|
||||||
if err := db.GetEngine(ctx).Where("issue_id != 0").Find(&sws); err != nil {
|
if err := db.GetEngine(ctx).Where("issue_id != 0").Find(&sws); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
res := map[int64][]*Stopwatch{}
|
||||||
if len(sws) == 0 {
|
if len(sws) == 0 {
|
||||||
return []*UserStopwatch{}, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
lastUserID := int64(-1)
|
|
||||||
res := []*UserStopwatch{}
|
|
||||||
for _, sw := range sws {
|
for _, sw := range sws {
|
||||||
if lastUserID == sw.UserID {
|
res[sw.UserID] = append(res[sw.UserID], sw)
|
||||||
lastUserStopwatch := res[len(res)-1]
|
|
||||||
lastUserStopwatch.StopWatches = append(lastUserStopwatch.StopWatches, sw)
|
|
||||||
} else {
|
|
||||||
res = append(res, &UserStopwatch{
|
|
||||||
UserID: sw.UserID,
|
|
||||||
StopWatches: []*Stopwatch{sw},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,14 @@
|
||||||
package issues_test
|
package issues_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -77,3 +79,41 @@ func TestCreateOrStopIssueStopwatch(t *testing.T) {
|
||||||
unittest.AssertNotExistsBean(t, &issues_model.Stopwatch{UserID: 2, IssueID: 2})
|
unittest.AssertNotExistsBean(t, &issues_model.Stopwatch{UserID: 2, IssueID: 2})
|
||||||
unittest.AssertExistsAndLoadBean(t, &issues_model.TrackedTime{UserID: 2, IssueID: 2})
|
unittest.AssertExistsAndLoadBean(t, &issues_model.TrackedTime{UserID: 2, IssueID: 2})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetUIDsAndStopwatch(t *testing.T) {
|
||||||
|
defer unittest.OverrideFixtures(
|
||||||
|
unittest.FixturesOptions{
|
||||||
|
Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"),
|
||||||
|
Base: setting.AppWorkPath,
|
||||||
|
Dirs: []string{"models/issues/TestGetUIDsAndStopwatch/"},
|
||||||
|
},
|
||||||
|
)()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
uidStopwatches, err := issues_model.GetUIDsAndStopwatch(db.DefaultContext)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, map[int64][]*issues_model.Stopwatch{
|
||||||
|
1: {
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
UserID: 1,
|
||||||
|
IssueID: 1,
|
||||||
|
CreatedUnix: timeutil.TimeStamp(1500988001),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 3,
|
||||||
|
UserID: 1,
|
||||||
|
IssueID: 2,
|
||||||
|
CreatedUnix: timeutil.TimeStamp(1500988004),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
{
|
||||||
|
ID: 2,
|
||||||
|
UserID: 2,
|
||||||
|
IssueID: 2,
|
||||||
|
CreatedUnix: timeutil.TimeStamp(1500988002),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, uidStopwatches)
|
||||||
|
}
|
||||||
|
|
10
models/organization/TestInconsistentOwnerTeam/team.yml
Normal file
10
models/organization/TestInconsistentOwnerTeam/team.yml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
-
|
||||||
|
id: 1000
|
||||||
|
org_id: 1000
|
||||||
|
lower_name: owners
|
||||||
|
name: Owners
|
||||||
|
authorize: 4 # owner
|
||||||
|
num_repos: 0
|
||||||
|
num_members: 0
|
||||||
|
includes_all_repositories: true
|
||||||
|
can_create_org_repo: true
|
59
models/organization/TestInconsistentOwnerTeam/team_unit.yml
Normal file
59
models/organization/TestInconsistentOwnerTeam/team_unit.yml
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
-
|
||||||
|
id: 1000
|
||||||
|
team_id: 1000
|
||||||
|
type: 1
|
||||||
|
access_mode: 0 # None
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 1001
|
||||||
|
team_id: 1000
|
||||||
|
type: 2
|
||||||
|
access_mode: 0
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 1002
|
||||||
|
team_id: 1000
|
||||||
|
type: 3
|
||||||
|
access_mode: 0
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 1003
|
||||||
|
team_id: 1000
|
||||||
|
type: 4
|
||||||
|
access_mode: 0
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 1004
|
||||||
|
team_id: 1000
|
||||||
|
type: 5
|
||||||
|
access_mode: 0
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 1005
|
||||||
|
team_id: 1000
|
||||||
|
type: 6
|
||||||
|
access_mode: 0
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 1006
|
||||||
|
team_id: 1000
|
||||||
|
type: 7
|
||||||
|
access_mode: 0
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 1007
|
||||||
|
team_id: 1000
|
||||||
|
type: 8
|
||||||
|
access_mode: 0
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 1008
|
||||||
|
team_id: 1000
|
||||||
|
type: 9
|
||||||
|
access_mode: 0
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 1009
|
||||||
|
team_id: 1000
|
||||||
|
type: 10
|
||||||
|
access_mode: 0
|
|
@ -264,7 +264,7 @@ func (org *Organization) UnitPermission(ctx context.Context, doer *user_model.Us
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if org.Visibility.IsPublic() {
|
if org.Visibility.IsPublic() || (org.Visibility.IsLimited() && doer != nil) {
|
||||||
return perm.AccessModeRead
|
return perm.AccessModeRead
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,9 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
|
"code.gitea.io/gitea/models/perm"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unit"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
@ -299,8 +301,8 @@ func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, expectedRepoIDs, repoIDs)
|
assert.Equal(t, expectedRepoIDs, repoIDs)
|
||||||
}
|
}
|
||||||
testSuccess(2, []int64{3, 5, 32})
|
testSuccess(2, []int64{32, 5, 3})
|
||||||
testSuccess(4, []int64{3, 32})
|
testSuccess(4, []int64{32, 3})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccessibleReposEnv_Repos(t *testing.T) {
|
func TestAccessibleReposEnv_Repos(t *testing.T) {
|
||||||
|
@ -318,8 +320,8 @@ func TestAccessibleReposEnv_Repos(t *testing.T) {
|
||||||
}
|
}
|
||||||
assert.Equal(t, expectedRepos, repos)
|
assert.Equal(t, expectedRepos, repos)
|
||||||
}
|
}
|
||||||
testSuccess(2, []int64{3, 5, 32})
|
testSuccess(2, []int64{32, 5, 3})
|
||||||
testSuccess(4, []int64{3, 32})
|
testSuccess(4, []int64{32, 3})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
|
func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
|
||||||
|
@ -512,3 +514,35 @@ func TestCreateOrganization4(t *testing.T) {
|
||||||
assert.True(t, db.IsErrNameReserved(err))
|
assert.True(t, db.IsErrNameReserved(err))
|
||||||
unittest.CheckConsistencyFor(t, &organization.Organization{}, &organization.Team{})
|
unittest.CheckConsistencyFor(t, &organization.Organization{}, &organization.Team{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnitPermission(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
publicOrg := &organization.Organization{ID: 1001, Visibility: structs.VisibleTypePublic}
|
||||||
|
limitedOrg := &organization.Organization{ID: 1001, Visibility: structs.VisibleTypeLimited}
|
||||||
|
privateOrg := &organization.Organization{ID: 1001, Visibility: structs.VisibleTypePrivate}
|
||||||
|
user := &user_model.User{ID: 1001}
|
||||||
|
t.Run("Anonymous", func(t *testing.T) {
|
||||||
|
t.Run("Public", func(t *testing.T) {
|
||||||
|
assert.EqualValues(t, perm.AccessModeRead, publicOrg.UnitPermission(db.DefaultContext, nil, unit.TypeCode))
|
||||||
|
})
|
||||||
|
t.Run("Limited", func(t *testing.T) {
|
||||||
|
assert.EqualValues(t, perm.AccessModeNone, limitedOrg.UnitPermission(db.DefaultContext, nil, unit.TypeCode))
|
||||||
|
})
|
||||||
|
t.Run("Private", func(t *testing.T) {
|
||||||
|
assert.EqualValues(t, perm.AccessModeNone, privateOrg.UnitPermission(db.DefaultContext, nil, unit.TypeCode))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Logged in", func(t *testing.T) {
|
||||||
|
t.Run("Public", func(t *testing.T) {
|
||||||
|
assert.EqualValues(t, perm.AccessModeRead, publicOrg.UnitPermission(db.DefaultContext, user, unit.TypeCode))
|
||||||
|
})
|
||||||
|
t.Run("Limited", func(t *testing.T) {
|
||||||
|
assert.EqualValues(t, perm.AccessModeRead, limitedOrg.UnitPermission(db.DefaultContext, user, unit.TypeCode))
|
||||||
|
})
|
||||||
|
t.Run("Private", func(t *testing.T) {
|
||||||
|
assert.EqualValues(t, perm.AccessModeNone, privateOrg.UnitPermission(db.DefaultContext, user, unit.TypeCode))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -268,3 +268,43 @@ func IncrTeamRepoNum(ctx context.Context, teamID int64) error {
|
||||||
_, err := db.GetEngine(ctx).Incr("num_repos").ID(teamID).Update(new(Team))
|
_, err := db.GetEngine(ctx).Incr("num_repos").ID(teamID).Update(new(Team))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CountInconsistentOwnerTeams returns the amount of owner teams that have all of
|
||||||
|
// their access modes set to "None".
|
||||||
|
func CountInconsistentOwnerTeams(ctx context.Context) (int64, error) {
|
||||||
|
return db.GetEngine(ctx).Table("team").
|
||||||
|
Join("INNER", "team_unit", "`team`.id = `team_unit`.team_id").
|
||||||
|
Where("`team`.lower_name = ?", strings.ToLower(OwnerTeamName)).
|
||||||
|
GroupBy("`team_unit`.team_id").
|
||||||
|
Having("SUM(`team_unit`.access_mode) = 0").
|
||||||
|
Count()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FixInconsistentOwnerTeams fixes inconsistent owner teams that have all of
|
||||||
|
// their access modes set to "None", it sets it back to "Owner".
|
||||||
|
func FixInconsistentOwnerTeams(ctx context.Context) (int64, error) {
|
||||||
|
teamIDs := []int64{}
|
||||||
|
if err := db.GetEngine(ctx).Table("team").
|
||||||
|
Select("`team`.id").
|
||||||
|
Join("INNER", "team_unit", "`team`.id = `team_unit`.team_id").
|
||||||
|
Where("`team`.lower_name = ?", strings.ToLower(OwnerTeamName)).
|
||||||
|
GroupBy("`team_unit`.team_id").
|
||||||
|
Having("SUM(`team_unit`.access_mode) = 0").
|
||||||
|
Find(&teamIDs); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Iterate(ctx, builder.In("team_id", teamIDs), func(ctx context.Context, bean *TeamUnit) error {
|
||||||
|
if bean.Type == unit.TypeExternalTracker || bean.Type == unit.TypeExternalWiki {
|
||||||
|
bean.AccessMode = perm.AccessModeRead
|
||||||
|
} else {
|
||||||
|
bean.AccessMode = perm.AccessModeOwner
|
||||||
|
}
|
||||||
|
_, err := db.GetEngine(ctx).ID(bean.ID).Table("team_unit").Cols("access_mode").Update(bean)
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(len(teamIDs)), nil
|
||||||
|
}
|
||||||
|
|
|
@ -4,11 +4,14 @@
|
||||||
package organization_test
|
package organization_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
|
"code.gitea.io/gitea/models/perm"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -198,3 +201,50 @@ func TestUsersInTeamsCount(t *testing.T) {
|
||||||
test([]int64{1, 2, 3, 4, 5}, []int64{2, 5}, 2) // userid 2,4
|
test([]int64{1, 2, 3, 4, 5}, []int64{2, 5}, 2) // userid 2,4
|
||||||
test([]int64{1, 2, 3, 4, 5}, []int64{2, 3, 5}, 3) // userid 2,4,5
|
test([]int64{1, 2, 3, 4, 5}, []int64{2, 3, 5}, 3) // userid 2,4,5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInconsistentOwnerTeam(t *testing.T) {
|
||||||
|
defer unittest.OverrideFixtures(
|
||||||
|
unittest.FixturesOptions{
|
||||||
|
Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"),
|
||||||
|
Base: setting.AppWorkPath,
|
||||||
|
Dirs: []string{"models/organization/TestInconsistentOwnerTeam/"},
|
||||||
|
},
|
||||||
|
)()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1000, TeamID: 1000, AccessMode: perm.AccessModeNone})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1001, TeamID: 1000, AccessMode: perm.AccessModeNone})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1002, TeamID: 1000, AccessMode: perm.AccessModeNone})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1003, TeamID: 1000, AccessMode: perm.AccessModeNone})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1004, TeamID: 1000, AccessMode: perm.AccessModeNone})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1005, TeamID: 1000, AccessMode: perm.AccessModeNone})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1006, TeamID: 1000, AccessMode: perm.AccessModeNone})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1007, TeamID: 1000, AccessMode: perm.AccessModeNone})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1008, TeamID: 1000, AccessMode: perm.AccessModeNone})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1009, TeamID: 1000, AccessMode: perm.AccessModeNone})
|
||||||
|
|
||||||
|
count, err := organization.CountInconsistentOwnerTeams(db.DefaultContext)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 1, count)
|
||||||
|
|
||||||
|
count, err = organization.FixInconsistentOwnerTeams(db.DefaultContext)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 1, count)
|
||||||
|
|
||||||
|
count, err = organization.CountInconsistentOwnerTeams(db.DefaultContext)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 0, count)
|
||||||
|
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1000, AccessMode: perm.AccessModeOwner})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1001, AccessMode: perm.AccessModeOwner})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1002, AccessMode: perm.AccessModeOwner})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1003, AccessMode: perm.AccessModeOwner})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1004, AccessMode: perm.AccessModeOwner})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1007, AccessMode: perm.AccessModeOwner})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1008, AccessMode: perm.AccessModeOwner})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1009, AccessMode: perm.AccessModeOwner})
|
||||||
|
|
||||||
|
// External wiki and issue
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1005, AccessMode: perm.AccessModeRead})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1006, AccessMode: perm.AccessModeRead})
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/packages"
|
"code.gitea.io/gitea/models/packages"
|
||||||
debian_module "code.gitea.io/gitea/modules/packages/debian"
|
debian_module "code.gitea.io/gitea/modules/packages/debian"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
@ -76,25 +77,41 @@ func ExistPackages(ctx context.Context, opts *PackageSearchOptions) (bool, error
|
||||||
|
|
||||||
// SearchPackages gets the packages matching the search options
|
// SearchPackages gets the packages matching the search options
|
||||||
func SearchPackages(ctx context.Context, opts *PackageSearchOptions, iter func(*packages.PackageFileDescriptor)) error {
|
func SearchPackages(ctx context.Context, opts *PackageSearchOptions, iter func(*packages.PackageFileDescriptor)) error {
|
||||||
return db.GetEngine(ctx).
|
var start int
|
||||||
Table("package_file").
|
batchSize := setting.Database.IterateBufferSize
|
||||||
Select("package_file.*").
|
for {
|
||||||
Join("INNER", "package_version", "package_version.id = package_file.version_id").
|
select {
|
||||||
Join("INNER", "package", "package.id = package_version.package_id").
|
case <-ctx.Done():
|
||||||
Where(opts.toCond()).
|
return ctx.Err()
|
||||||
Asc("package.lower_name", "package_version.created_unix").
|
default:
|
||||||
Iterate(new(packages.PackageFile), func(_ int, bean any) error {
|
beans := make([]*packages.PackageFile, 0, batchSize)
|
||||||
pf := bean.(*packages.PackageFile)
|
|
||||||
|
|
||||||
pfd, err := packages.GetPackageFileDescriptor(ctx, pf)
|
if err := db.GetEngine(ctx).
|
||||||
if err != nil {
|
Table("package_file").
|
||||||
|
Select("package_file.*").
|
||||||
|
Join("INNER", "package_version", "package_version.id = package_file.version_id").
|
||||||
|
Join("INNER", "package", "package.id = package_version.package_id").
|
||||||
|
Where(opts.toCond()).
|
||||||
|
Asc("package.lower_name", "package_version.created_unix").
|
||||||
|
Limit(batchSize, start).
|
||||||
|
Find(&beans); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if len(beans) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
start += len(beans)
|
||||||
|
|
||||||
iter(pfd)
|
for _, bean := range beans {
|
||||||
|
pfd, err := packages.GetPackageFileDescriptor(ctx, bean)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
iter(pfd)
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDistributions gets all available distributions
|
// GetDistributions gets all available distributions
|
||||||
|
|
93
models/packages/debian/search_test.go
Normal file
93
models/packages/debian/search_test.go
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package debian
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/packages"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
packages_service "code.gitea.io/gitea/services/packages"
|
||||||
|
|
||||||
|
_ "code.gitea.io/gitea/models"
|
||||||
|
_ "code.gitea.io/gitea/models/actions"
|
||||||
|
_ "code.gitea.io/gitea/models/activities"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
unittest.MainTest(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func preparePackage(t *testing.T, owner *user_model.User, name string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
data, err := packages.CreateHashedBufferFromReader(strings.NewReader("data"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, _, err = packages_service.CreatePackageOrAddFileToExisting(
|
||||||
|
db.DefaultContext,
|
||||||
|
&packages_service.PackageCreationInfo{
|
||||||
|
PackageInfo: packages_service.PackageInfo{
|
||||||
|
Owner: owner,
|
||||||
|
PackageType: packages_model.TypeDebian,
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
Creator: owner,
|
||||||
|
},
|
||||||
|
&packages_service.PackageFileCreationInfo{
|
||||||
|
PackageFileInfo: packages_service.PackageFileInfo{
|
||||||
|
Filename: name,
|
||||||
|
},
|
||||||
|
Data: data,
|
||||||
|
Creator: owner,
|
||||||
|
IsLead: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchPackages(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
defer test.MockVariableValue(&setting.Database.IterateBufferSize, 1)()
|
||||||
|
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||||
|
|
||||||
|
preparePackage(t, user2, "debian-1")
|
||||||
|
preparePackage(t, user2, "debian-2")
|
||||||
|
preparePackage(t, user3, "debian-1")
|
||||||
|
|
||||||
|
packageFiles := []string{}
|
||||||
|
require.NoError(t, SearchPackages(db.DefaultContext, &PackageSearchOptions{
|
||||||
|
OwnerID: user2.ID,
|
||||||
|
}, func(pfd *packages_model.PackageFileDescriptor) {
|
||||||
|
assert.NotNil(t, pfd)
|
||||||
|
packageFiles = append(packageFiles, pfd.File.Name)
|
||||||
|
}))
|
||||||
|
|
||||||
|
assert.Len(t, packageFiles, 2)
|
||||||
|
assert.Contains(t, packageFiles, "debian-1")
|
||||||
|
assert.Contains(t, packageFiles, "debian-2")
|
||||||
|
|
||||||
|
packageFiles = []string{}
|
||||||
|
require.NoError(t, SearchPackages(db.DefaultContext, &PackageSearchOptions{
|
||||||
|
OwnerID: user3.ID,
|
||||||
|
}, func(pfd *packages_model.PackageFileDescriptor) {
|
||||||
|
assert.NotNil(t, pfd)
|
||||||
|
packageFiles = append(packageFiles, pfd.File.Name)
|
||||||
|
}))
|
||||||
|
|
||||||
|
assert.Len(t, packageFiles, 1)
|
||||||
|
assert.Contains(t, packageFiles, "debian-1")
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
-
|
||||||
|
id: 1001
|
||||||
|
owner_id: 33
|
||||||
|
owner_name: user33
|
||||||
|
lower_name: repo1001
|
||||||
|
name: repo1001
|
||||||
|
default_branch: main
|
||||||
|
num_watches: 0
|
||||||
|
num_stars: 0
|
||||||
|
num_forks: 0
|
||||||
|
num_issues: 0
|
||||||
|
num_closed_issues: 0
|
||||||
|
num_pulls: 0
|
||||||
|
num_closed_pulls: 0
|
||||||
|
num_milestones: 0
|
||||||
|
num_closed_milestones: 0
|
||||||
|
num_projects: 0
|
||||||
|
num_closed_projects: 0
|
||||||
|
is_private: false
|
||||||
|
is_empty: false
|
||||||
|
is_archived: false
|
||||||
|
is_mirror: false
|
||||||
|
status: 0
|
||||||
|
is_fork: false
|
||||||
|
fork_id: 0
|
||||||
|
is_template: false
|
||||||
|
template_id: 0
|
||||||
|
size: 0
|
||||||
|
is_fsck_enabled: true
|
||||||
|
close_issues_via_commit_in_any_branch: false
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
|
@ -54,9 +55,9 @@ func GetUserFork(ctx context.Context, repoID, userID int64) (*Repository, error)
|
||||||
return &forkedRepo, nil
|
return &forkedRepo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetForks returns all the forks of the repository
|
// GetForks returns all the forks of the repository that are visible to the user.
|
||||||
func GetForks(ctx context.Context, repo *Repository, listOptions db.ListOptions) ([]*Repository, error) {
|
func GetForks(ctx context.Context, repo *Repository, user *user_model.User, listOptions db.ListOptions) ([]*Repository, int64, error) {
|
||||||
sess := db.GetEngine(ctx)
|
sess := db.GetEngine(ctx).Where(AccessibleRepositoryCondition(user, unit.TypeInvalid))
|
||||||
|
|
||||||
var forks []*Repository
|
var forks []*Repository
|
||||||
if listOptions.Page == 0 {
|
if listOptions.Page == 0 {
|
||||||
|
@ -66,7 +67,8 @@ func GetForks(ctx context.Context, repo *Repository, listOptions db.ListOptions)
|
||||||
sess = db.SetSessionPagination(sess, &listOptions)
|
sess = db.SetSessionPagination(sess, &listOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
return forks, sess.Find(&forks, &Repository{ForkID: repo.ID})
|
count, err := sess.FindAndCount(&forks, &Repository{ForkID: repo.ID})
|
||||||
|
return forks, count, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// IncrementRepoForkNum increment repository fork number
|
// IncrementRepoForkNum increment repository fork number
|
||||||
|
|
|
@ -641,12 +641,9 @@ func AccessibleRepositoryCondition(user *user_model.User, unitType unit.Type) bu
|
||||||
// 1. Be able to see all non-private repositories that either:
|
// 1. Be able to see all non-private repositories that either:
|
||||||
cond = cond.Or(builder.And(
|
cond = cond.Or(builder.And(
|
||||||
builder.Eq{"`repository`.is_private": false},
|
builder.Eq{"`repository`.is_private": false},
|
||||||
// 2. Aren't in an private organisation or limited organisation if we're not logged in
|
// 2. Aren't in an private organisation/user or limited organisation/user if the doer is not logged in.
|
||||||
builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where(
|
builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where(
|
||||||
builder.And(
|
builder.In("visibility", orgVisibilityLimit)))))
|
||||||
builder.Eq{"type": user_model.UserTypeOrganization},
|
|
||||||
builder.In("visibility", orgVisibilityLimit)),
|
|
||||||
))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if user != nil {
|
if user != nil {
|
||||||
|
|
|
@ -4,13 +4,18 @@
|
||||||
package repo_test
|
package repo_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
"code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -403,3 +408,43 @@ func TestSearchRepositoryByTopicName(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSearchRepositoryIDsByCondition(t *testing.T) {
|
||||||
|
defer unittest.OverrideFixtures(
|
||||||
|
unittest.FixturesOptions{
|
||||||
|
Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"),
|
||||||
|
Base: setting.AppWorkPath,
|
||||||
|
Dirs: []string{"models/repo/TestSearchRepositoryIDsByCondition/"},
|
||||||
|
},
|
||||||
|
)()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
// Sanity check of the database
|
||||||
|
limitedUser := unittest.AssertExistsAndLoadBean(t, &user.User{ID: 33, Visibility: structs.VisibleTypeLimited})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1001, OwnerID: limitedUser.ID})
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
user *user.User
|
||||||
|
repoIDs []int64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
user: nil,
|
||||||
|
repoIDs: []int64{1, 4, 8, 9, 10, 11, 12, 14, 17, 18, 21, 23, 25, 27, 29, 32, 33, 34, 35, 36, 37, 42, 44, 45, 46, 47, 48, 49, 50, 51, 53, 57, 58, 60, 61, 62, 1059},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
user: unittest.AssertExistsAndLoadBean(t, &user.User{ID: 4}),
|
||||||
|
repoIDs: []int64{1, 3, 4, 8, 9, 10, 11, 12, 14, 17, 18, 21, 23, 25, 27, 29, 32, 33, 34, 35, 36, 37, 38, 40, 42, 44, 45, 46, 47, 48, 49, 50, 51, 53, 57, 58, 60, 61, 62, 1001, 1059},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
user: unittest.AssertExistsAndLoadBean(t, &user.User{ID: 5}),
|
||||||
|
repoIDs: []int64{1, 4, 8, 9, 10, 11, 12, 14, 17, 18, 21, 23, 25, 27, 29, 32, 33, 34, 35, 36, 37, 38, 40, 42, 44, 45, 46, 47, 48, 49, 50, 51, 53, 57, 58, 60, 61, 62, 1001, 1059},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
repoIDs, err := repo_model.FindUserCodeAccessibleRepoIDs(db.DefaultContext, testCase.user)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
slices.Sort(repoIDs)
|
||||||
|
assert.EqualValues(t, testCase.repoIDs, repoIDs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ var OrderByMap = map[string]map[string]db.SearchOrderBy{
|
||||||
var OrderByFlatMap = map[string]db.SearchOrderBy{
|
var OrderByFlatMap = map[string]db.SearchOrderBy{
|
||||||
"newest": OrderByMap["desc"]["created"],
|
"newest": OrderByMap["desc"]["created"],
|
||||||
"oldest": OrderByMap["asc"]["created"],
|
"oldest": OrderByMap["asc"]["created"],
|
||||||
|
"recentupdate": OrderByMap["desc"]["updated"],
|
||||||
"leastupdate": OrderByMap["asc"]["updated"],
|
"leastupdate": OrderByMap["asc"]["updated"],
|
||||||
"reversealphabetically": OrderByMap["desc"]["alpha"],
|
"reversealphabetically": OrderByMap["desc"]["alpha"],
|
||||||
"alphabetically": OrderByMap["asc"]["alpha"],
|
"alphabetically": OrderByMap["asc"]["alpha"],
|
||||||
|
|
|
@ -10,10 +10,8 @@ import (
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/base"
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
@ -307,23 +305,6 @@ func updateActivation(ctx context.Context, email *EmailAddress, activate bool) e
|
||||||
return UpdateUserCols(ctx, user, "rands")
|
return UpdateUserCols(ctx, user, "rands")
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyActiveEmailCode verifies active email code when active account
|
|
||||||
func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddress {
|
|
||||||
if user := GetVerifyUser(ctx, code); user != nil {
|
|
||||||
// time limit code
|
|
||||||
prefix := code[:base.TimeLimitCodeLength]
|
|
||||||
data := fmt.Sprintf("%d%s%s%s%s", user.ID, email, user.LowerName, user.Passwd, user.Rands)
|
|
||||||
|
|
||||||
if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
|
|
||||||
emailAddress := &EmailAddress{UID: user.ID, Email: email}
|
|
||||||
if has, _ := db.GetEngine(ctx).Get(emailAddress); has {
|
|
||||||
return emailAddress
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchEmailOrderBy is used to sort the results from SearchEmails()
|
// SearchEmailOrderBy is used to sort the results from SearchEmails()
|
||||||
type SearchEmailOrderBy string
|
type SearchEmailOrderBy string
|
||||||
|
|
||||||
|
|
|
@ -160,34 +160,12 @@ func UpdateExternalUserByExternalID(ctx context.Context, external *ExternalLogin
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnsureLinkExternalToUser link the external user to the user
|
|
||||||
func EnsureLinkExternalToUser(ctx context.Context, external *ExternalLoginUser) error {
|
|
||||||
has, err := db.Exist[ExternalLoginUser](ctx, builder.Eq{
|
|
||||||
"external_id": external.ExternalID,
|
|
||||||
"login_source_id": external.LoginSourceID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if has {
|
|
||||||
_, err = db.GetEngine(ctx).Where("external_id=? AND login_source_id=?", external.ExternalID, external.LoginSourceID).AllCols().Update(external)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.GetEngine(ctx).Insert(external)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindExternalUserOptions represents an options to find external users
|
// FindExternalUserOptions represents an options to find external users
|
||||||
type FindExternalUserOptions struct {
|
type FindExternalUserOptions struct {
|
||||||
db.ListOptions
|
db.ListOptions
|
||||||
Provider string
|
Provider string
|
||||||
UserID int64
|
UserID int64
|
||||||
LoginSourceID int64
|
OrderBy string
|
||||||
HasRefreshToken bool
|
|
||||||
Expired bool
|
|
||||||
OrderBy string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts FindExternalUserOptions) ToConds() builder.Cond {
|
func (opts FindExternalUserOptions) ToConds() builder.Cond {
|
||||||
|
@ -198,22 +176,9 @@ func (opts FindExternalUserOptions) ToConds() builder.Cond {
|
||||||
if opts.UserID > 0 {
|
if opts.UserID > 0 {
|
||||||
cond = cond.And(builder.Eq{"user_id": opts.UserID})
|
cond = cond.And(builder.Eq{"user_id": opts.UserID})
|
||||||
}
|
}
|
||||||
if opts.Expired {
|
|
||||||
cond = cond.And(builder.Lt{"expires_at": time.Now()})
|
|
||||||
}
|
|
||||||
if opts.HasRefreshToken {
|
|
||||||
cond = cond.And(builder.Neq{"refresh_token": ""})
|
|
||||||
}
|
|
||||||
if opts.LoginSourceID != 0 {
|
|
||||||
cond = cond.And(builder.Eq{"login_source_id": opts.LoginSourceID})
|
|
||||||
}
|
|
||||||
return cond
|
return cond
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts FindExternalUserOptions) ToOrders() string {
|
func (opts FindExternalUserOptions) ToOrders() string {
|
||||||
return opts.OrderBy
|
return opts.OrderBy
|
||||||
}
|
}
|
||||||
|
|
||||||
func IterateExternalLogin(ctx context.Context, opts FindExternalUserOptions, f func(ctx context.Context, u *ExternalLoginUser) error) error {
|
|
||||||
return db.Iterate(ctx, opts.ToConds(), f)
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,7 +7,9 @@ package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/subtle"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -48,19 +50,19 @@ const (
|
||||||
UserTypeIndividual UserType = iota // Historic reason to make it starts at 0.
|
UserTypeIndividual UserType = iota // Historic reason to make it starts at 0.
|
||||||
|
|
||||||
// UserTypeOrganization defines an organization
|
// UserTypeOrganization defines an organization
|
||||||
UserTypeOrganization
|
UserTypeOrganization // 1
|
||||||
|
|
||||||
// UserTypeUserReserved reserves a (non-existing) user, i.e. to prevent a spam user from re-registering after being deleted, or to reserve the name until the user is actually created later on
|
// UserTypeUserReserved reserves a (non-existing) user, i.e. to prevent a spam user from re-registering after being deleted, or to reserve the name until the user is actually created later on
|
||||||
UserTypeUserReserved
|
UserTypeUserReserved // 2
|
||||||
|
|
||||||
// UserTypeOrganizationReserved reserves a (non-existing) organization, to be used in combination with UserTypeUserReserved
|
// UserTypeOrganizationReserved reserves a (non-existing) organization, to be used in combination with UserTypeUserReserved
|
||||||
UserTypeOrganizationReserved
|
UserTypeOrganizationReserved // 3
|
||||||
|
|
||||||
// UserTypeBot defines a bot user
|
// UserTypeBot defines a bot user
|
||||||
UserTypeBot
|
UserTypeBot // 4
|
||||||
|
|
||||||
// UserTypeRemoteUser defines a remote user for federated users
|
// UserTypeRemoteUser defines a remote user for federated users
|
||||||
UserTypeRemoteUser
|
UserTypeRemoteUser // 5
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -318,15 +320,14 @@ func (u *User) OrganisationLink() string {
|
||||||
return setting.AppSubURL + "/org/" + url.PathEscape(u.Name)
|
return setting.AppSubURL + "/org/" + url.PathEscape(u.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateEmailActivateCode generates an activate code based on user information and given e-mail.
|
// GenerateEmailAuthorizationCode generates an activation code based for the user for the specified purpose.
|
||||||
func (u *User) GenerateEmailActivateCode(email string) string {
|
// The standard expiry is ActiveCodeLives minutes.
|
||||||
code := base.CreateTimeLimitCode(
|
func (u *User) GenerateEmailAuthorizationCode(ctx context.Context, purpose auth.AuthorizationPurpose) (string, error) {
|
||||||
fmt.Sprintf("%d%s%s%s%s", u.ID, email, u.LowerName, u.Passwd, u.Rands),
|
lookup, validator, err := auth.GenerateAuthToken(ctx, u.ID, timeutil.TimeStampNow().Add(int64(setting.Service.ActiveCodeLives)*60), purpose)
|
||||||
setting.Service.ActiveCodeLives, time.Now(), nil)
|
if err != nil {
|
||||||
|
return "", err
|
||||||
// Add tail hex username
|
}
|
||||||
code += hex.EncodeToString([]byte(u.LowerName))
|
return lookup + ":" + validator, nil
|
||||||
return code
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserFollowers returns range of user's followers.
|
// GetUserFollowers returns range of user's followers.
|
||||||
|
@ -421,6 +422,10 @@ func (u *User) IsIndividual() bool {
|
||||||
return u.Type == UserTypeIndividual
|
return u.Type == UserTypeIndividual
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) IsUser() bool {
|
||||||
|
return u.Type == UserTypeIndividual || u.Type == UserTypeBot
|
||||||
|
}
|
||||||
|
|
||||||
// IsBot returns whether or not the user is of type bot
|
// IsBot returns whether or not the user is of type bot
|
||||||
func (u *User) IsBot() bool {
|
func (u *User) IsBot() bool {
|
||||||
return u.Type == UserTypeBot
|
return u.Type == UserTypeBot
|
||||||
|
@ -832,35 +837,50 @@ func countUsers(ctx context.Context, opts *CountUserFilter) int64 {
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVerifyUser get user by verify code
|
// VerifyUserActiveCode verifies that the code is valid for the given purpose for this user.
|
||||||
func GetVerifyUser(ctx context.Context, code string) (user *User) {
|
// If delete is specified, the token will be deleted.
|
||||||
if len(code) <= base.TimeLimitCodeLength {
|
func VerifyUserAuthorizationToken(ctx context.Context, code string, purpose auth.AuthorizationPurpose, delete bool) (*User, error) {
|
||||||
return nil
|
lookupKey, validator, found := strings.Cut(code, ":")
|
||||||
|
if !found {
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// use tail hex username query user
|
authToken, err := auth.FindAuthToken(ctx, lookupKey, purpose)
|
||||||
hexStr := code[base.TimeLimitCodeLength:]
|
if err != nil {
|
||||||
if b, err := hex.DecodeString(hexStr); err == nil {
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
if user, err = GetUserByName(ctx, string(b)); user != nil {
|
return nil, nil
|
||||||
return user
|
|
||||||
}
|
}
|
||||||
log.Error("user.getVerifyUser: %v", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if authToken.IsExpired() {
|
||||||
}
|
return nil, auth.DeleteAuthToken(ctx, authToken)
|
||||||
|
}
|
||||||
|
|
||||||
// VerifyUserActiveCode verifies active code when active account
|
rawValidator, err := hex.DecodeString(validator)
|
||||||
func VerifyUserActiveCode(ctx context.Context, code string) (user *User) {
|
if err != nil {
|
||||||
if user = GetVerifyUser(ctx, code); user != nil {
|
return nil, err
|
||||||
// time limit code
|
}
|
||||||
prefix := code[:base.TimeLimitCodeLength]
|
|
||||||
data := fmt.Sprintf("%d%s%s%s%s", user.ID, user.Email, user.LowerName, user.Passwd, user.Rands)
|
if subtle.ConstantTimeCompare([]byte(authToken.HashedValidator), []byte(auth.HashValidator(rawValidator))) == 0 {
|
||||||
if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
|
return nil, errors.New("validator doesn't match")
|
||||||
return user
|
}
|
||||||
|
|
||||||
|
u, err := GetUserByID(ctx, authToken.UID)
|
||||||
|
if err != nil {
|
||||||
|
if IsErrUserNotExist(err) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if delete {
|
||||||
|
if err := auth.DeleteAuthToken(ctx, authToken); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateUser check if user is valid to insert / update into database
|
// ValidateUser check if user is valid to insert / update into database
|
||||||
|
@ -897,7 +917,13 @@ func UpdateUserCols(ctx context.Context, u *User, cols ...string) error {
|
||||||
|
|
||||||
// GetInactiveUsers gets all inactive users
|
// GetInactiveUsers gets all inactive users
|
||||||
func GetInactiveUsers(ctx context.Context, olderThan time.Duration) ([]*User, error) {
|
func GetInactiveUsers(ctx context.Context, olderThan time.Duration) ([]*User, error) {
|
||||||
var cond builder.Cond = builder.Eq{"is_active": false}
|
cond := builder.And(
|
||||||
|
builder.Eq{"is_active": false},
|
||||||
|
builder.Or( // only plain user
|
||||||
|
builder.Eq{"`type`": UserTypeIndividual},
|
||||||
|
builder.Eq{"`type`": UserTypeUserReserved},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
if olderThan > 0 {
|
if olderThan > 0 {
|
||||||
cond = cond.And(builder.Lt{"created_unix": time.Now().Add(-olderThan).Unix()})
|
cond = cond.And(builder.Lt{"created_unix": time.Now().Add(-olderThan).Unix()})
|
||||||
|
|
|
@ -7,6 +7,7 @@ package user_test
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -21,7 +22,9 @@ import (
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -699,3 +702,80 @@ func TestDisabledUserFeatures(t *testing.T) {
|
||||||
assert.True(t, user_model.IsFeatureDisabledWithLoginType(user, f))
|
assert.True(t, user_model.IsFeatureDisabledWithLoginType(user, f))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGenerateEmailAuthorizationCode(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.Service.ActiveCodeLives, 2)()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
|
code, err := user.GenerateEmailAuthorizationCode(db.DefaultContext, auth.UserActivation)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
lookupKey, validator, ok := strings.Cut(code, ":")
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
rawValidator, err := hex.DecodeString(validator)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
authToken, err := auth.FindAuthToken(db.DefaultContext, lookupKey, auth.UserActivation)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.False(t, authToken.IsExpired())
|
||||||
|
assert.EqualValues(t, authToken.HashedValidator, auth.HashValidator(rawValidator))
|
||||||
|
|
||||||
|
authToken.Expiry = authToken.Expiry.Add(-int64(setting.Service.ActiveCodeLives) * 60)
|
||||||
|
assert.True(t, authToken.IsExpired())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyUserAuthorizationToken(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.Service.ActiveCodeLives, 2)()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
|
code, err := user.GenerateEmailAuthorizationCode(db.DefaultContext, auth.UserActivation)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
lookupKey, _, ok := strings.Cut(code, ":")
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
t.Run("Wrong purpose", func(t *testing.T) {
|
||||||
|
u, err := user_model.VerifyUserAuthorizationToken(db.DefaultContext, code, auth.PasswordReset, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Nil(t, u)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("No delete", func(t *testing.T) {
|
||||||
|
u, err := user_model.VerifyUserAuthorizationToken(db.DefaultContext, code, auth.UserActivation, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, user.ID, u.ID)
|
||||||
|
|
||||||
|
authToken, err := auth.FindAuthToken(db.DefaultContext, lookupKey, auth.UserActivation)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotNil(t, authToken)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Delete", func(t *testing.T) {
|
||||||
|
u, err := user_model.VerifyUserAuthorizationToken(db.DefaultContext, code, auth.UserActivation, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, user.ID, u.ID)
|
||||||
|
|
||||||
|
authToken, err := auth.FindAuthToken(db.DefaultContext, lookupKey, auth.UserActivation)
|
||||||
|
require.ErrorIs(t, err, util.ErrNotExist)
|
||||||
|
assert.Nil(t, authToken)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetInactiveUsers(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
// all inactive users
|
||||||
|
// user1's createdunix is 1730468968
|
||||||
|
users, err := user_model.GetInactiveUsers(db.DefaultContext, 0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, users, 1)
|
||||||
|
interval := time.Now().Unix() - 1730468968 + 3600*24
|
||||||
|
users, err = user_model.GetInactiveUsers(db.DefaultContext, time.Duration(interval*int64(time.Second)))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Empty(t, users)
|
||||||
|
}
|
||||||
|
|
|
@ -18,8 +18,32 @@ func FullSteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep {
|
||||||
return fullStepsOfEmptySteps(task)
|
return fullStepsOfEmptySteps(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
firstStep := task.Steps[0]
|
// firstStep is the first step that has run or running, not include preStep.
|
||||||
|
// For example,
|
||||||
|
// 1. preStep(Success) -> step1(Success) -> step2(Running) -> step3(Waiting) -> postStep(Waiting): firstStep is step1.
|
||||||
|
// 2. preStep(Success) -> step1(Skipped) -> step2(Success) -> postStep(Success): firstStep is step2.
|
||||||
|
// 3. preStep(Success) -> step1(Running) -> step2(Waiting) -> postStep(Waiting): firstStep is step1.
|
||||||
|
// 4. preStep(Success) -> step1(Skipped) -> step2(Skipped) -> postStep(Skipped): firstStep is nil.
|
||||||
|
// 5. preStep(Success) -> step1(Cancelled) -> step2(Cancelled) -> postStep(Cancelled): firstStep is nil.
|
||||||
|
var firstStep *actions_model.ActionTaskStep
|
||||||
|
// lastHasRunStep is the last step that has run.
|
||||||
|
// For example,
|
||||||
|
// 1. preStep(Success) -> step1(Success) -> step2(Running) -> step3(Waiting) -> postStep(Waiting): lastHasRunStep is step1.
|
||||||
|
// 2. preStep(Success) -> step1(Success) -> step2(Success) -> step3(Success) -> postStep(Success): lastHasRunStep is step3.
|
||||||
|
// 3. preStep(Success) -> step1(Success) -> step2(Failure) -> step3 -> postStep(Waiting): lastHasRunStep is step2.
|
||||||
|
// So its Stopped is the Started of postStep when there are no more steps to run.
|
||||||
|
var lastHasRunStep *actions_model.ActionTaskStep
|
||||||
|
|
||||||
var logIndex int64
|
var logIndex int64
|
||||||
|
for _, step := range task.Steps {
|
||||||
|
if firstStep == nil && (step.Status.HasRun() || step.Status.IsRunning()) {
|
||||||
|
firstStep = step
|
||||||
|
}
|
||||||
|
if step.Status.HasRun() {
|
||||||
|
lastHasRunStep = step
|
||||||
|
}
|
||||||
|
logIndex += step.LogLength
|
||||||
|
}
|
||||||
|
|
||||||
preStep := &actions_model.ActionTaskStep{
|
preStep := &actions_model.ActionTaskStep{
|
||||||
Name: preStepName,
|
Name: preStepName,
|
||||||
|
@ -28,32 +52,17 @@ func FullSteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep {
|
||||||
Status: actions_model.StatusRunning,
|
Status: actions_model.StatusRunning,
|
||||||
}
|
}
|
||||||
|
|
||||||
if firstStep.Status.HasRun() || firstStep.Status.IsRunning() {
|
// No step has run or is running, so preStep is equal to the task
|
||||||
|
if firstStep == nil {
|
||||||
|
preStep.Stopped = task.Stopped
|
||||||
|
preStep.Status = task.Status
|
||||||
|
} else {
|
||||||
preStep.LogLength = firstStep.LogIndex
|
preStep.LogLength = firstStep.LogIndex
|
||||||
preStep.Stopped = firstStep.Started
|
preStep.Stopped = firstStep.Started
|
||||||
preStep.Status = actions_model.StatusSuccess
|
preStep.Status = actions_model.StatusSuccess
|
||||||
} else if task.Status.IsDone() {
|
|
||||||
preStep.Stopped = task.Stopped
|
|
||||||
preStep.Status = actions_model.StatusFailure
|
|
||||||
if task.Status.IsSkipped() {
|
|
||||||
preStep.Status = actions_model.StatusSkipped
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
logIndex += preStep.LogLength
|
logIndex += preStep.LogLength
|
||||||
|
|
||||||
// lastHasRunStep is the last step that has run.
|
|
||||||
// For example,
|
|
||||||
// 1. preStep(Success) -> step1(Success) -> step2(Running) -> step3(Waiting) -> postStep(Waiting): lastHasRunStep is step1.
|
|
||||||
// 2. preStep(Success) -> step1(Success) -> step2(Success) -> step3(Success) -> postStep(Success): lastHasRunStep is step3.
|
|
||||||
// 3. preStep(Success) -> step1(Success) -> step2(Failure) -> step3 -> postStep(Waiting): lastHasRunStep is step2.
|
|
||||||
// So its Stopped is the Started of postStep when there are no more steps to run.
|
|
||||||
var lastHasRunStep *actions_model.ActionTaskStep
|
|
||||||
for _, step := range task.Steps {
|
|
||||||
if step.Status.HasRun() {
|
|
||||||
lastHasRunStep = step
|
|
||||||
}
|
|
||||||
logIndex += step.LogLength
|
|
||||||
}
|
|
||||||
if lastHasRunStep == nil {
|
if lastHasRunStep == nil {
|
||||||
lastHasRunStep = preStep
|
lastHasRunStep = preStep
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,6 +137,25 @@ func TestFullSteps(t *testing.T) {
|
||||||
{Name: postStepName, Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0},
|
{Name: postStepName, Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "first step is skipped",
|
||||||
|
task: &actions_model.ActionTask{
|
||||||
|
Steps: []*actions_model.ActionTaskStep{
|
||||||
|
{Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0},
|
||||||
|
{Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 10090},
|
||||||
|
},
|
||||||
|
Status: actions_model.StatusSuccess,
|
||||||
|
Started: 10000,
|
||||||
|
Stopped: 10100,
|
||||||
|
LogLength: 100,
|
||||||
|
},
|
||||||
|
want: []*actions_model.ActionTaskStep{
|
||||||
|
{Name: preStepName, Status: actions_model.StatusSuccess, LogIndex: 0, LogLength: 10, Started: 10000, Stopped: 10010},
|
||||||
|
{Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0},
|
||||||
|
{Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 10090},
|
||||||
|
{Name: postStepName, Status: actions_model.StatusSuccess, LogIndex: 90, LogLength: 10, Started: 10090, Stopped: 10100},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
137
modules/annex/annex.go
Normal file
137
modules/annex/annex.go
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// Unlike modules/lfs, which operates mainly on git.Blobs, this operates on git.TreeEntrys.
|
||||||
|
// The motivation for this is that TreeEntrys have an easy pointer to the on-disk repo path,
|
||||||
|
// while blobs do not (in fact, if building with TAGS=gogit, blobs might exist only in a mock
|
||||||
|
// filesystem, living only in process RAM). We must have the on-disk path to do anything
|
||||||
|
// useful with git-annex because all of its interesting data is on-disk under .git/annex/.
|
||||||
|
|
||||||
|
package annex
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrBlobIsNotAnnexed occurs if a blob does not contain a valid annex key
|
||||||
|
var ErrBlobIsNotAnnexed = errors.New("not a git-annex pointer")
|
||||||
|
|
||||||
|
func LookupKey(blob *git.Blob) (string, error) {
|
||||||
|
stdout, _, err := git.NewCommand(git.DefaultContext, "annex", "lookupkey", "--ref").AddDynamicArguments(blob.ID.String()).RunStdString(&git.RunOpts{Dir: blob.Repo().Path})
|
||||||
|
if err != nil {
|
||||||
|
return "", ErrBlobIsNotAnnexed
|
||||||
|
}
|
||||||
|
key := strings.TrimSpace(stdout)
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContentLocationFromKey(repoPath, key string) (string, error) {
|
||||||
|
contentLocation, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "contentlocation").AddDynamicArguments(key).RunStdString(&git.RunOpts{Dir: repoPath})
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("in %s: %s does not seem to be a valid annexed file: %w", repoPath, key, err)
|
||||||
|
}
|
||||||
|
contentLocation = strings.TrimSpace(contentLocation)
|
||||||
|
contentLocation = path.Clean("/" + contentLocation)[1:] // prevent directory traversals
|
||||||
|
contentLocation = path.Join(repoPath, contentLocation)
|
||||||
|
|
||||||
|
return contentLocation, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the absolute path of the content pointed to by the annex pointer stored in the git object
|
||||||
|
// errors if the content is not found in this repo
|
||||||
|
func ContentLocation(blob *git.Blob) (string, error) {
|
||||||
|
key, err := LookupKey(blob)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return ContentLocationFromKey(blob.Repo().Path, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns a stream open to the annex content
|
||||||
|
func Content(blob *git.Blob) (*os.File, error) {
|
||||||
|
contentLocation, err := ContentLocation(blob)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Open(contentLocation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// whether the object appears to be a valid annex pointer
|
||||||
|
// does *not* verify if the content is actually in this repo;
|
||||||
|
// for that, use ContentLocation()
|
||||||
|
func IsAnnexed(blob *git.Blob) (bool, error) {
|
||||||
|
if !setting.Annex.Enabled {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupKey is written to only return well-formed keys
|
||||||
|
// so the test is just to see if it errors
|
||||||
|
_, err := LookupKey(blob)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrBlobIsNotAnnexed) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAnnexRepo determines if repo is a git-annex enabled repository
|
||||||
|
func IsAnnexRepo(repo *git.Repository) bool {
|
||||||
|
_, _, err := git.NewCommand(repo.Ctx, "config", "annex.uuid").RunStdString(&git.RunOpts{Dir: repo.Path})
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var repoConfigFileRe = regexp.MustCompile("[^/]+/[^/]+.git/config$")
|
||||||
|
|
||||||
|
var (
|
||||||
|
uuid2repoPathCache = make(map[string]string)
|
||||||
|
repoPath2uuidCache = make(map[string]string)
|
||||||
|
)
|
||||||
|
|
||||||
|
func updateUUID2RepoPathCache() error {
|
||||||
|
return filepath.WalkDir(setting.RepoRootPath, func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err == nil && repoConfigFileRe.MatchString(path) {
|
||||||
|
thisRepoPath := strings.TrimSuffix(path, "/config")
|
||||||
|
_, ok := repoPath2uuidCache[thisRepoPath]
|
||||||
|
if ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
stdout, _, err := git.NewCommand(git.DefaultContext, "config", "annex.uuid").RunStdString(&git.RunOpts{Dir: thisRepoPath})
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
repoUUID := strings.TrimSpace(stdout)
|
||||||
|
if repoUUID != "" {
|
||||||
|
uuid2repoPathCache[repoUUID] = thisRepoPath
|
||||||
|
repoPath2uuidCache[thisRepoPath] = repoUUID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UUID2RepoPath(uuid string) (string, error) {
|
||||||
|
if repoPath, ok := uuid2repoPathCache[uuid]; ok {
|
||||||
|
return repoPath, nil
|
||||||
|
}
|
||||||
|
// If the cache didn't contain an entry for the UUID then update the cache and try again
|
||||||
|
if err := updateUUID2RepoPathCache(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if repoPath, ok := uuid2repoPathCache[uuid]; ok {
|
||||||
|
return repoPath, nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("no repository known for UUID '%s'", uuid)
|
||||||
|
}
|
|
@ -4,26 +4,21 @@
|
||||||
package base
|
package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/sha1"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/subtle"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/annex"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
)
|
)
|
||||||
|
@ -54,66 +49,6 @@ func BasicAuthDecode(encoded string) (string, string, error) {
|
||||||
return "", "", errors.New("invalid basic authentication")
|
return "", "", errors.New("invalid basic authentication")
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyTimeLimitCode verify time limit code
|
|
||||||
func VerifyTimeLimitCode(now time.Time, data string, minutes int, code string) bool {
|
|
||||||
if len(code) <= 18 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
startTimeStr := code[:12]
|
|
||||||
aliveTimeStr := code[12:18]
|
|
||||||
aliveTime, _ := strconv.Atoi(aliveTimeStr) // no need to check err, if anything wrong, the following code check will fail soon
|
|
||||||
|
|
||||||
// check code
|
|
||||||
retCode := CreateTimeLimitCode(data, aliveTime, startTimeStr, nil)
|
|
||||||
if subtle.ConstantTimeCompare([]byte(retCode), []byte(code)) != 1 {
|
|
||||||
retCode = CreateTimeLimitCode(data, aliveTime, startTimeStr, sha1.New()) // TODO: this is only for the support of legacy codes, remove this in/after 1.23
|
|
||||||
if subtle.ConstantTimeCompare([]byte(retCode), []byte(code)) != 1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check time is expired or not: startTime <= now && now < startTime + minutes
|
|
||||||
startTime, _ := time.ParseInLocation("200601021504", startTimeStr, time.Local)
|
|
||||||
return (startTime.Before(now) || startTime.Equal(now)) && now.Before(startTime.Add(time.Minute*time.Duration(minutes)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeLimitCodeLength default value for time limit code
|
|
||||||
const TimeLimitCodeLength = 12 + 6 + 40
|
|
||||||
|
|
||||||
// CreateTimeLimitCode create a time-limited code.
|
|
||||||
// Format: 12 length date time string + 6 minutes string (not used) + 40 hash string, some other code depends on this fixed length
|
|
||||||
// If h is nil, then use the default hmac hash.
|
|
||||||
func CreateTimeLimitCode[T time.Time | string](data string, minutes int, startTimeGeneric T, h hash.Hash) string {
|
|
||||||
const format = "200601021504"
|
|
||||||
|
|
||||||
var start time.Time
|
|
||||||
var startTimeAny any = startTimeGeneric
|
|
||||||
if t, ok := startTimeAny.(time.Time); ok {
|
|
||||||
start = t
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
start, err = time.ParseInLocation(format, startTimeAny.(string), time.Local)
|
|
||||||
if err != nil {
|
|
||||||
return "" // return an invalid code because the "parse" failed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
startStr := start.Format(format)
|
|
||||||
end := start.Add(time.Minute * time.Duration(minutes))
|
|
||||||
|
|
||||||
if h == nil {
|
|
||||||
h = hmac.New(sha1.New, setting.GetGeneralTokenSigningSecret())
|
|
||||||
}
|
|
||||||
_, _ = fmt.Fprintf(h, "%s%s%s%s%d", data, hex.EncodeToString(setting.GetGeneralTokenSigningSecret()), startStr, end.Format(format), minutes)
|
|
||||||
encoded := hex.EncodeToString(h.Sum(nil))
|
|
||||||
|
|
||||||
code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
|
|
||||||
if len(code) != TimeLimitCodeLength {
|
|
||||||
panic("there is a hard requirement for the length of time-limited code") // it shouldn't happen
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileSize calculates the file size and generate user-friendly string.
|
// FileSize calculates the file size and generate user-friendly string.
|
||||||
func FileSize(s int64) string {
|
func FileSize(s int64) string {
|
||||||
return humanize.IBytes(uint64(s))
|
return humanize.IBytes(uint64(s))
|
||||||
|
@ -167,6 +102,12 @@ func Int64sToStrings(ints []int64) []string {
|
||||||
|
|
||||||
// EntryIcon returns the octicon class for displaying files/directories
|
// EntryIcon returns the octicon class for displaying files/directories
|
||||||
func EntryIcon(entry *git.TreeEntry) string {
|
func EntryIcon(entry *git.TreeEntry) string {
|
||||||
|
isAnnexed, _ := annex.IsAnnexed(entry.Blob())
|
||||||
|
if isAnnexed {
|
||||||
|
// Show git-annex files as binary files to differentiate them from non-annexed files
|
||||||
|
// TODO: find a more suitable icon, maybe something related to git-annex
|
||||||
|
return "file-binary"
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case entry.IsLink():
|
case entry.IsLink():
|
||||||
te, _, err := entry.FollowLink()
|
te, _, err := entry.FollowLink()
|
||||||
|
|
|
@ -4,13 +4,7 @@
|
||||||
package base
|
package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
"code.gitea.io/gitea/modules/test"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -46,57 +40,6 @@ func TestBasicAuthDecode(t *testing.T) {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerifyTimeLimitCode(t *testing.T) {
|
|
||||||
defer test.MockVariableValue(&setting.InstallLock, true)()
|
|
||||||
initGeneralSecret := func(secret string) {
|
|
||||||
setting.InstallLock = true
|
|
||||||
setting.CfgProvider, _ = setting.NewConfigProviderFromData(fmt.Sprintf(`
|
|
||||||
[oauth2]
|
|
||||||
JWT_SECRET = %s
|
|
||||||
`, secret))
|
|
||||||
setting.LoadCommonSettings()
|
|
||||||
}
|
|
||||||
|
|
||||||
initGeneralSecret("KZb_QLUd4fYVyxetjxC4eZkrBgWM2SndOOWDNtgUUko")
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
t.Run("TestGenericParameter", func(t *testing.T) {
|
|
||||||
time2000 := time.Date(2000, 1, 2, 3, 4, 5, 0, time.Local)
|
|
||||||
assert.Equal(t, "2000010203040000026fa5221b2731b7cf80b1b506f5e39e38c115fee5", CreateTimeLimitCode("test-sha1", 2, time2000, sha1.New()))
|
|
||||||
assert.Equal(t, "2000010203040000026fa5221b2731b7cf80b1b506f5e39e38c115fee5", CreateTimeLimitCode("test-sha1", 2, "200001020304", sha1.New()))
|
|
||||||
assert.Equal(t, "2000010203040000024842227a2f87041ff82025199c0187410a9297bf", CreateTimeLimitCode("test-hmac", 2, time2000, nil))
|
|
||||||
assert.Equal(t, "2000010203040000024842227a2f87041ff82025199c0187410a9297bf", CreateTimeLimitCode("test-hmac", 2, "200001020304", nil))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("TestInvalidCode", func(t *testing.T) {
|
|
||||||
assert.False(t, VerifyTimeLimitCode(now, "data", 2, ""))
|
|
||||||
assert.False(t, VerifyTimeLimitCode(now, "data", 2, "invalid code"))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("TestCreateAndVerify", func(t *testing.T) {
|
|
||||||
code := CreateTimeLimitCode("data", 2, now, nil)
|
|
||||||
assert.False(t, VerifyTimeLimitCode(now.Add(-time.Minute), "data", 2, code)) // not started yet
|
|
||||||
assert.True(t, VerifyTimeLimitCode(now, "data", 2, code))
|
|
||||||
assert.True(t, VerifyTimeLimitCode(now.Add(time.Minute), "data", 2, code))
|
|
||||||
assert.False(t, VerifyTimeLimitCode(now.Add(time.Minute), "DATA", 2, code)) // invalid data
|
|
||||||
assert.False(t, VerifyTimeLimitCode(now.Add(2*time.Minute), "data", 2, code)) // expired
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("TestDifferentSecret", func(t *testing.T) {
|
|
||||||
// use another secret to ensure the code is invalid for different secret
|
|
||||||
verifyDataCode := func(c string) bool {
|
|
||||||
return VerifyTimeLimitCode(now, "data", 2, c)
|
|
||||||
}
|
|
||||||
code1 := CreateTimeLimitCode("data", 2, now, sha1.New())
|
|
||||||
code2 := CreateTimeLimitCode("data", 2, now, nil)
|
|
||||||
assert.True(t, verifyDataCode(code1))
|
|
||||||
assert.True(t, verifyDataCode(code2))
|
|
||||||
initGeneralSecret("000_QLUd4fYVyxetjxC4eZkrBgWM2SndOOWDNtgUUko")
|
|
||||||
assert.False(t, verifyDataCode(code1))
|
|
||||||
assert.False(t, verifyDataCode(code2))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFileSize(t *testing.T) {
|
func TestFileSize(t *testing.T) {
|
||||||
var size int64 = 512
|
var size int64 = 512
|
||||||
assert.Equal(t, "512 B", FileSize(size))
|
assert.Equal(t, "512 B", FileSize(size))
|
||||||
|
|
|
@ -90,8 +90,8 @@ loop:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, userStopwatches := range usersStopwatches {
|
for uid, stopwatches := range usersStopwatches {
|
||||||
apiSWs, err := convert.ToStopWatches(ctx, userStopwatches.StopWatches)
|
apiSWs, err := convert.ToStopWatches(ctx, stopwatches)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !issues_model.IsErrIssueNotExist(err) {
|
if !issues_model.IsErrIssueNotExist(err) {
|
||||||
log.Error("Unable to APIFormat stopwatches: %v", err)
|
log.Error("Unable to APIFormat stopwatches: %v", err)
|
||||||
|
@ -103,7 +103,7 @@ loop:
|
||||||
log.Error("Unable to marshal stopwatches: %v", err)
|
log.Error("Unable to marshal stopwatches: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m.SendMessage(userStopwatches.UserID, &Event{
|
m.SendMessage(uid, &Event{
|
||||||
Name: "stopwatches",
|
Name: "stopwatches",
|
||||||
Data: string(dataBs),
|
Data: string(dataBs),
|
||||||
})
|
})
|
||||||
|
|
|
@ -126,6 +126,10 @@ func (b *blobReader) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Blob) Repo() *Repository {
|
||||||
|
return b.repo
|
||||||
|
}
|
||||||
|
|
||||||
// Name returns name of the tree entry this blob object was created from (or empty string)
|
// Name returns name of the tree entry this blob object was created from (or empty string)
|
||||||
func (b *Blob) Name() string {
|
func (b *Blob) Name() string {
|
||||||
return b.name
|
return b.name
|
||||||
|
|
|
@ -457,12 +457,13 @@ func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllowLFSFiltersArgs return globalCommandArgs with lfs filter, it should only be used for tests
|
// AllowLFSFiltersArgs return globalCommandArgs with lfs filter, it should only be used for tests
|
||||||
|
// It also re-enables git-credential(1), which is used to test git-annex's HTTP support
|
||||||
func AllowLFSFiltersArgs() TrustedCmdArgs {
|
func AllowLFSFiltersArgs() TrustedCmdArgs {
|
||||||
// Now here we should explicitly allow lfs filters to run
|
// Now here we should explicitly allow lfs filters to run
|
||||||
filteredLFSGlobalArgs := make(TrustedCmdArgs, len(globalCommandArgs))
|
filteredLFSGlobalArgs := make(TrustedCmdArgs, len(globalCommandArgs))
|
||||||
j := 0
|
j := 0
|
||||||
for _, arg := range globalCommandArgs {
|
for _, arg := range globalCommandArgs {
|
||||||
if strings.Contains(string(arg), "lfs") {
|
if strings.Contains(string(arg), "lfs") || strings.Contains(string(arg), "credential") {
|
||||||
j--
|
j--
|
||||||
} else {
|
} else {
|
||||||
filteredLFSGlobalArgs[j] = arg
|
filteredLFSGlobalArgs[j] = arg
|
||||||
|
|
|
@ -17,6 +17,8 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Commit represents a git commit.
|
// Commit represents a git commit.
|
||||||
|
@ -365,53 +367,48 @@ func (c *Commit) GetSubModules() (*ObjectCache, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rd, err := entry.Blob().DataAsync()
|
content, err := entry.Blob().GetBlobContent(10 * 1024)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer rd.Close()
|
c.submoduleCache, err = parseSubmoduleContent([]byte(content))
|
||||||
scanner := bufio.NewScanner(rd)
|
if err != nil {
|
||||||
c.submoduleCache = newObjectCache()
|
return nil, err
|
||||||
var ismodule bool
|
|
||||||
var path string
|
|
||||||
for scanner.Scan() {
|
|
||||||
if strings.HasPrefix(scanner.Text(), "[submodule") {
|
|
||||||
ismodule = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ismodule {
|
|
||||||
fields := strings.Split(scanner.Text(), "=")
|
|
||||||
k := strings.TrimSpace(fields[0])
|
|
||||||
if k == "path" {
|
|
||||||
path = strings.TrimSpace(fields[1])
|
|
||||||
} else if k == "url" {
|
|
||||||
c.submoduleCache.Set(path, &SubModule{path, strings.TrimSpace(fields[1])})
|
|
||||||
ismodule = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err = scanner.Err(); err != nil {
|
|
||||||
return nil, fmt.Errorf("GetSubModules scan: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.submoduleCache, nil
|
return c.submoduleCache, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSubModule get the sub module according entryname
|
func parseSubmoduleContent(bs []byte) (*ObjectCache, error) {
|
||||||
func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {
|
cfg := config.NewModules()
|
||||||
|
if err := cfg.Unmarshal(bs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
submoduleCache := newObjectCache()
|
||||||
|
if len(cfg.Submodules) == 0 {
|
||||||
|
return nil, fmt.Errorf("no submodules found")
|
||||||
|
}
|
||||||
|
for _, subModule := range cfg.Submodules {
|
||||||
|
submoduleCache.Set(subModule.Path, subModule.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return submoduleCache, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSubModule returns the URL to the submodule according entryname
|
||||||
|
func (c *Commit) GetSubModule(entryname string) (string, error) {
|
||||||
modules, err := c.GetSubModules()
|
modules, err := c.GetSubModules()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if modules != nil {
|
if modules != nil {
|
||||||
module, has := modules.Get(entryname)
|
module, has := modules.Get(entryname)
|
||||||
if has {
|
if has {
|
||||||
return module.(*SubModule), nil
|
return module.(string), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
|
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
|
||||||
|
|
|
@ -72,17 +72,15 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
|
||||||
|
|
||||||
// If the entry if a submodule add a submodule file for this
|
// If the entry if a submodule add a submodule file for this
|
||||||
if entry.IsSubModule() {
|
if entry.IsSubModule() {
|
||||||
subModuleURL := ""
|
|
||||||
var fullPath string
|
var fullPath string
|
||||||
if len(treePath) > 0 {
|
if len(treePath) > 0 {
|
||||||
fullPath = treePath + "/" + entry.Name()
|
fullPath = treePath + "/" + entry.Name()
|
||||||
} else {
|
} else {
|
||||||
fullPath = entry.Name()
|
fullPath = entry.Name()
|
||||||
}
|
}
|
||||||
if subModule, err := commit.GetSubModule(fullPath); err != nil {
|
subModuleURL, err := commit.GetSubModule(fullPath)
|
||||||
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
} else if subModule != nil {
|
|
||||||
subModuleURL = subModule.URL
|
|
||||||
}
|
}
|
||||||
subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, subModuleURL, entry.ID.String())
|
subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, subModuleURL, entry.ID.String())
|
||||||
commitsInfo[i].SubModuleFile = subModuleFile
|
commitsInfo[i].SubModuleFile = subModuleFile
|
||||||
|
|
|
@ -369,3 +369,33 @@ func TestParseCommitRenames(t *testing.T) {
|
||||||
assert.Equal(t, testcase.renames, renames)
|
assert.Equal(t, testcase.renames, renames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_parseSubmoduleContent(t *testing.T) {
|
||||||
|
submoduleFiles := []struct {
|
||||||
|
fileContent string
|
||||||
|
expectedPath string
|
||||||
|
expectedURL string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
fileContent: `[submodule "jakarta-servlet"]
|
||||||
|
url = ../../ALP-pool/jakarta-servlet
|
||||||
|
path = jakarta-servlet`,
|
||||||
|
expectedPath: "jakarta-servlet",
|
||||||
|
expectedURL: "../../ALP-pool/jakarta-servlet",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fileContent: `[submodule "jakarta-servlet"]
|
||||||
|
path = jakarta-servlet
|
||||||
|
url = ../../ALP-pool/jakarta-servlet`,
|
||||||
|
expectedPath: "jakarta-servlet",
|
||||||
|
expectedURL: "../../ALP-pool/jakarta-servlet",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, kase := range submoduleFiles {
|
||||||
|
submodule, err := parseSubmoduleContent([]byte(kase.fileContent))
|
||||||
|
require.NoError(t, err)
|
||||||
|
v, ok := submodule.Get(kase.expectedPath)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, kase.expectedURL, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -272,6 +272,17 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
|
||||||
|
|
||||||
// GetAffectedFiles returns the affected files between two commits
|
// GetAffectedFiles returns the affected files between two commits
|
||||||
func GetAffectedFiles(repo *Repository, oldCommitID, newCommitID string, env []string) ([]string, error) {
|
func GetAffectedFiles(repo *Repository, oldCommitID, newCommitID string, env []string) ([]string, error) {
|
||||||
|
objectFormat, err := repo.GetObjectFormat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the oldCommitID is empty, then we must assume its a new branch, so diff
|
||||||
|
// against the empty tree. So all changes of this new branch are included.
|
||||||
|
if oldCommitID == objectFormat.EmptyObjectID().String() {
|
||||||
|
oldCommitID = objectFormat.EmptyTree().String()
|
||||||
|
}
|
||||||
|
|
||||||
stdoutReader, stdoutWriter, err := os.Pipe()
|
stdoutReader, stdoutWriter, err := os.Pipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to create os.Pipe for %s", repo.Path)
|
log.Error("Unable to create os.Pipe for %s", repo.Path)
|
||||||
|
|
|
@ -97,12 +97,12 @@ func SetExecutablePath(path string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if gitVersion.LessThan(versionRequired) {
|
if gitVersion.LessThan(versionRequired) {
|
||||||
moreHint := "get git: https://git-scm.com/download/"
|
moreHint := "get git: https://git-scm.com/downloads"
|
||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
// there are a lot of CentOS/RHEL users using old git, so we add a special hint for them
|
// there are a lot of CentOS/RHEL users using old git, so we add a special hint for them
|
||||||
if _, err = os.Stat("/etc/redhat-release"); err == nil {
|
if _, err = os.Stat("/etc/redhat-release"); err == nil {
|
||||||
// ius.io is the recommended official(git-scm.com) method to install git
|
// ius.io is the recommended official(git-scm.com) method to install git
|
||||||
moreHint = "get git: https://git-scm.com/download/linux and https://ius.io"
|
moreHint = "get git: https://git-scm.com/downloads/linux and https://ius.io"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", gitVersion.Original(), RequiredVersion, moreHint)
|
return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", gitVersion.Original(), RequiredVersion, moreHint)
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ type GrepResult struct {
|
||||||
type GrepOptions struct {
|
type GrepOptions struct {
|
||||||
RefName string
|
RefName string
|
||||||
MaxResultLimit int
|
MaxResultLimit int
|
||||||
MatchesPerFile int
|
MatchesPerFile int // >= git 2.38
|
||||||
ContextLineNumber int
|
ContextLineNumber int
|
||||||
IsFuzzy bool
|
IsFuzzy bool
|
||||||
PathSpec []setting.Glob
|
PathSpec []setting.Glob
|
||||||
|
@ -77,7 +78,14 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
|
||||||
"-I", "--null", "--break", "--heading", "--column",
|
"-I", "--null", "--break", "--heading", "--column",
|
||||||
"--fixed-strings", "--line-number", "--ignore-case", "--full-name")
|
"--fixed-strings", "--line-number", "--ignore-case", "--full-name")
|
||||||
cmd.AddOptionValues("--context", fmt.Sprint(opts.ContextLineNumber))
|
cmd.AddOptionValues("--context", fmt.Sprint(opts.ContextLineNumber))
|
||||||
cmd.AddOptionValues("--max-count", fmt.Sprint(opts.MatchesPerFile))
|
|
||||||
|
// --max-count requires at least git 2.38
|
||||||
|
if CheckGitVersionAtLeast("2.38.0") == nil {
|
||||||
|
cmd.AddOptionValues("--max-count", fmt.Sprint(opts.MatchesPerFile))
|
||||||
|
} else {
|
||||||
|
log.Warn("git-grep: --max-count requires at least git 2.38")
|
||||||
|
}
|
||||||
|
|
||||||
words := []string{search}
|
words := []string{search}
|
||||||
if opts.IsFuzzy {
|
if opts.IsFuzzy {
|
||||||
words = strings.Fields(search)
|
words = strings.Fields(search)
|
||||||
|
|
|
@ -254,7 +254,7 @@ func TestGitAttributeCheckerError(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = ac.CheckPath("i-am-a-python.p")
|
_, err = ac.CheckPath("i-am-a-python.p")
|
||||||
require.ErrorIs(t, err, context.Canceled)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Cancelled/DuringRun", func(t *testing.T) {
|
t.Run("Cancelled/DuringRun", func(t *testing.T) {
|
||||||
|
|
|
@ -96,7 +96,7 @@ func Code(fileName, language, code string) (output template.HTML, lexerName stri
|
||||||
}
|
}
|
||||||
|
|
||||||
if lexer == nil {
|
if lexer == nil {
|
||||||
lexer = lexers.Match(fileName)
|
lexer = lexers.Match(strings.ToLower(fileName))
|
||||||
if lexer == nil {
|
if lexer == nil {
|
||||||
lexer = lexers.Fallback
|
lexer = lexers.Fallback
|
||||||
}
|
}
|
||||||
|
@ -134,6 +134,12 @@ func CodeFromLexer(lexer chroma.Lexer, code string) template.HTML {
|
||||||
return template.HTML(strings.TrimSuffix(htmlbuf.String(), "\n"))
|
return template.HTML(strings.TrimSuffix(htmlbuf.String(), "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For the case where Enry recognizes the language, but doesn't use the naming
|
||||||
|
// that Chroma expects.
|
||||||
|
var normalizeEnryToChroma = map[string]string{
|
||||||
|
"F#": "FSharp",
|
||||||
|
}
|
||||||
|
|
||||||
// File returns a slice of chroma syntax highlighted HTML lines of code and the matched lexer name
|
// File returns a slice of chroma syntax highlighted HTML lines of code and the matched lexer name
|
||||||
func File(fileName, language string, code []byte) ([]template.HTML, string, error) {
|
func File(fileName, language string, code []byte) ([]template.HTML, string, error) {
|
||||||
NewContext()
|
NewContext()
|
||||||
|
@ -162,10 +168,13 @@ func File(fileName, language string, code []byte) ([]template.HTML, string, erro
|
||||||
|
|
||||||
if lexer == nil {
|
if lexer == nil {
|
||||||
guessLanguage := analyze.GetCodeLanguage(fileName, code)
|
guessLanguage := analyze.GetCodeLanguage(fileName, code)
|
||||||
|
if normalizedGuessLanguage, ok := normalizeEnryToChroma[guessLanguage]; ok {
|
||||||
|
guessLanguage = normalizedGuessLanguage
|
||||||
|
}
|
||||||
|
|
||||||
lexer = lexers.Get(guessLanguage)
|
lexer = lexers.Get(guessLanguage)
|
||||||
if lexer == nil {
|
if lexer == nil {
|
||||||
lexer = lexers.Match(fileName)
|
lexer = lexers.Match(strings.ToLower(fileName))
|
||||||
if lexer == nil {
|
if lexer == nil {
|
||||||
lexer = lexers.Fallback
|
lexer = lexers.Fallback
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,6 +109,18 @@ c=2
|
||||||
),
|
),
|
||||||
lexerName: "Python",
|
lexerName: "Python",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "DOS.PAS",
|
||||||
|
code: "",
|
||||||
|
want: lines(""),
|
||||||
|
lexerName: "ObjectPascal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test.fs",
|
||||||
|
code: "module Crypt = let generateCryptTable: array<uint32> =",
|
||||||
|
want: lines(`<span class="k">module</span> <span class="nn">Crypt</span> <span class="o">=</span> <span class="k">let</span> <span class="nv">generateCryptTable</span><span class="o">:</span> <span class="n">array</span><span class="o"><</span><span class="kt">uint32</span><span class="o">></span> <span class="o">=</span>`),
|
||||||
|
lexerName: "FSharp",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
|
@ -76,7 +76,8 @@ func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag s
|
||||||
w.Header().Set("Etag", etag)
|
w.Header().Set("Etag", etag)
|
||||||
}
|
}
|
||||||
if lastModified != nil && !lastModified.IsZero() {
|
if lastModified != nil && !lastModified.IsZero() {
|
||||||
w.Header().Set("Last-Modified", lastModified.Format(http.TimeFormat))
|
// http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat
|
||||||
|
w.Header().Set("Last-Modified", lastModified.UTC().Format(http.TimeFormat))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(etag) > 0 {
|
if len(etag) > 0 {
|
||||||
|
|
|
@ -79,6 +79,7 @@ func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) {
|
||||||
httpcache.SetCacheControlInHeader(header, duration)
|
httpcache.SetCacheControlInHeader(header, duration)
|
||||||
|
|
||||||
if !opts.LastModified.IsZero() {
|
if !opts.LastModified.IsZero() {
|
||||||
|
// http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat
|
||||||
header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat))
|
header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
|
indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
|
||||||
inner_elasticsearch "code.gitea.io/gitea/modules/indexer/internal/elasticsearch"
|
inner_elasticsearch "code.gitea.io/gitea/modules/indexer/internal/elasticsearch"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/typesniffer"
|
"code.gitea.io/gitea/modules/typesniffer"
|
||||||
|
@ -197,8 +198,33 @@ func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha st
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes indexes by ids
|
// Delete entries by repoId
|
||||||
func (b *Indexer) Delete(ctx context.Context, repoID int64) error {
|
func (b *Indexer) Delete(ctx context.Context, repoID int64) error {
|
||||||
|
if err := b.doDelete(ctx, repoID); err != nil {
|
||||||
|
// Maybe there is a conflict during the delete operation, so we should retry after a refresh
|
||||||
|
log.Warn("Deletion of entries of repo %v within index %v was erroneus. Trying to refresh index before trying again", repoID, b.inner.VersionedIndexName(), err)
|
||||||
|
if err := b.refreshIndex(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := b.doDelete(ctx, repoID); err != nil {
|
||||||
|
log.Error("Could not delete entries of repo %v within index %v", repoID, b.inner.VersionedIndexName())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Indexer) refreshIndex(ctx context.Context) error {
|
||||||
|
if _, err := b.inner.Client.Refresh(b.inner.VersionedIndexName()).Do(ctx); err != nil {
|
||||||
|
log.Error("Error while trying to refresh index %v", b.inner.VersionedIndexName(), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete entries by repoId
|
||||||
|
func (b *Indexer) doDelete(ctx context.Context, repoID int64) error {
|
||||||
_, err := b.inner.Client.DeleteByQuery(b.inner.VersionedIndexName()).
|
_, err := b.inner.Client.DeleteByQuery(b.inner.VersionedIndexName()).
|
||||||
Query(elastic.NewTermsQuery("repo_id", repoID)).
|
Query(elastic.NewTermsQuery("repo_id", repoID)).
|
||||||
Do(ctx)
|
Do(ctx)
|
||||||
|
|
|
@ -39,7 +39,7 @@ const (
|
||||||
// SanitizerRules implements markup.Renderer
|
// SanitizerRules implements markup.Renderer
|
||||||
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
||||||
return []setting.MarkupSanitizerRule{
|
return []setting.MarkupSanitizerRule{
|
||||||
{Element: "div", AllowAttr: "class", Regexp: regexp.MustCompile(playerClassName)},
|
{Element: "div", AllowAttr: "class", Regexp: regexp.MustCompile("^" + playerClassName + "$")},
|
||||||
{Element: "div", AllowAttr: playerSrcAttr},
|
{Element: "div", AllowAttr: playerSrcAttr},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ func camoHandleLink(link string) string {
|
||||||
if setting.Camo.Enabled {
|
if setting.Camo.Enabled {
|
||||||
lnkURL, err := url.Parse(link)
|
lnkURL, err := url.Parse(link)
|
||||||
if err == nil && lnkURL.IsAbs() && !strings.HasPrefix(link, setting.AppURL) &&
|
if err == nil && lnkURL.IsAbs() && !strings.HasPrefix(link, setting.AppURL) &&
|
||||||
(setting.Camo.Allways || lnkURL.Scheme != "https") {
|
(setting.Camo.Always || lnkURL.Scheme != "https") {
|
||||||
return CamoEncode(link)
|
return CamoEncode(link)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ func TestCamoHandleLink(t *testing.T) {
|
||||||
"https://image.proxy/eivin43gJwGVIjR9MiYYtFIk0mw/aHR0cDovL3Rlc3RpbWFnZXMub3JnL2ltZy5qcGc",
|
"https://image.proxy/eivin43gJwGVIjR9MiYYtFIk0mw/aHR0cDovL3Rlc3RpbWFnZXMub3JnL2ltZy5qcGc",
|
||||||
camoHandleLink("http://testimages.org/img.jpg"))
|
camoHandleLink("http://testimages.org/img.jpg"))
|
||||||
|
|
||||||
setting.Camo.Allways = true
|
setting.Camo.Always = true
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
"https://gitea.com/img.jpg",
|
"https://gitea.com/img.jpg",
|
||||||
camoHandleLink("https://gitea.com/img.jpg"))
|
camoHandleLink("https://gitea.com/img.jpg"))
|
||||||
|
|
|
@ -37,9 +37,9 @@ func (Renderer) Extensions() []string {
|
||||||
// SanitizerRules implements markup.Renderer
|
// SanitizerRules implements markup.Renderer
|
||||||
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
||||||
return []setting.MarkupSanitizerRule{
|
return []setting.MarkupSanitizerRule{
|
||||||
{Element: "table", AllowAttr: "class", Regexp: regexp.MustCompile(`data-table`)},
|
{Element: "table", AllowAttr: "class", Regexp: regexp.MustCompile(`^data-table$`)},
|
||||||
{Element: "th", AllowAttr: "class", Regexp: regexp.MustCompile(`line-num`)},
|
{Element: "th", AllowAttr: "class", Regexp: regexp.MustCompile(`^line-num$`)},
|
||||||
{Element: "td", AllowAttr: "class", Regexp: regexp.MustCompile(`line-num`)},
|
{Element: "td", AllowAttr: "class", Regexp: regexp.MustCompile(`^line-num$`)},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
25
modules/markup/external/external.go
vendored
25
modules/markup/external/external.go
vendored
|
@ -12,6 +12,7 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/annex"
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
@ -86,8 +87,22 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
|
||||||
commands = strings.Fields(command)
|
commands = strings.Fields(command)
|
||||||
args = commands[1:]
|
args = commands[1:]
|
||||||
)
|
)
|
||||||
|
isAnnexed, _ := annex.IsAnnexed(ctx.Blob)
|
||||||
if p.IsInputFile {
|
// if a renderer wants to read a file, and we have annexed content, we can
|
||||||
|
// provide the annex key file location directly to the renderer. git-annex
|
||||||
|
// takes care of having that location be read-only, so no critical
|
||||||
|
// protection layer is needed. Moreover, the file readily exists, and
|
||||||
|
// expensive temporary files can be avoided, also allowing an operator
|
||||||
|
// to raise MAX_DISPLAY_FILE_SIZE without much negative impact.
|
||||||
|
if p.IsInputFile && isAnnexed {
|
||||||
|
// look for annexed content, will be empty, if there is none
|
||||||
|
annexContentLocation, _ := annex.ContentLocation(ctx.Blob)
|
||||||
|
// we call the renderer, even if there is no annex content present.
|
||||||
|
// showing the pointer file content is not much use, and a topical
|
||||||
|
// renderer might be able to produce something useful from the
|
||||||
|
// filename alone (present in ENV)
|
||||||
|
args = append(args, annexContentLocation)
|
||||||
|
} else if p.IsInputFile {
|
||||||
// write to temp file
|
// write to temp file
|
||||||
f, err := os.CreateTemp("", "gitea_input")
|
f, err := os.CreateTemp("", "gitea_input")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -130,6 +145,12 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
|
||||||
os.Environ(),
|
os.Environ(),
|
||||||
"GITEA_PREFIX_SRC="+ctx.Links.SrcLink(),
|
"GITEA_PREFIX_SRC="+ctx.Links.SrcLink(),
|
||||||
"GITEA_PREFIX_RAW="+ctx.Links.RawLink(),
|
"GITEA_PREFIX_RAW="+ctx.Links.RawLink(),
|
||||||
|
// also communicate the relative path of the to-be-rendered item.
|
||||||
|
// this enables the renderer to make use of the original file name
|
||||||
|
// and path, e.g., to make rendering or dtype-detection decisions
|
||||||
|
// that go beyond the originally matched extension. Even if the
|
||||||
|
// content is directly streamed to STDIN
|
||||||
|
"GITEA_RELATIVE_PATH="+ctx.RelativePath,
|
||||||
)
|
)
|
||||||
if !p.IsInputFile {
|
if !p.IsInputFile {
|
||||||
cmd.Stdin = input
|
cmd.Stdin = input
|
||||||
|
|
|
@ -63,6 +63,14 @@ func (g *GitHubLegacyCalloutTransformer) Transform(node *ast.Document, reader te
|
||||||
attentionParagraph.AppendChild(attentionParagraph, calloutNode)
|
attentionParagraph.AppendChild(attentionParagraph, calloutNode)
|
||||||
firstParagraph.Parent().InsertBefore(firstParagraph.Parent(), firstParagraph, attentionParagraph)
|
firstParagraph.Parent().InsertBefore(firstParagraph.Parent(), firstParagraph, attentionParagraph)
|
||||||
firstParagraph.RemoveChild(firstParagraph, calloutNode)
|
firstParagraph.RemoveChild(firstParagraph, calloutNode)
|
||||||
|
|
||||||
|
// Remove softbreak line if there's one.
|
||||||
|
if firstParagraph.ChildCount() >= 1 {
|
||||||
|
softBreakNode, ok := firstParagraph.FirstChild().(*ast.Text)
|
||||||
|
if ok && softBreakNode.SoftLineBreak() {
|
||||||
|
firstParagraph.RemoveChild(firstParagraph, softBreakNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
|
|
|
@ -1356,4 +1356,10 @@ func TestCallout(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
test(">\n0", "<blockquote>\n</blockquote>\n<p>0</p>")
|
test(">\n0", "<blockquote>\n</blockquote>\n<p>0</p>")
|
||||||
|
test("> **Warning**\n> Bad stuff is brewing here", `<blockquote class="attention-header attention-warning"><p class="attention-title"><strong class="attention-warning">Warning</strong></p>
|
||||||
|
<p>Bad stuff is brewing here</p>
|
||||||
|
</blockquote>`)
|
||||||
|
test("> [!WARNING]\n> Bad stuff is brewing here", `<blockquote class="attention-header attention-warning"><p class="attention-title"><strong class="attention-warning">Warning</strong></p>
|
||||||
|
<p>Bad stuff is brewing here</p>
|
||||||
|
</blockquote>`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,14 +67,18 @@ type Header struct {
|
||||||
|
|
||||||
// RenderContext represents a render context
|
// RenderContext represents a render context
|
||||||
type RenderContext struct {
|
type RenderContext struct {
|
||||||
Ctx context.Context
|
Ctx context.Context
|
||||||
RelativePath string // relative path from tree root of the branch
|
RelativePath string // relative path from tree root of the branch
|
||||||
Type string
|
Type string
|
||||||
IsWiki bool
|
IsWiki bool
|
||||||
Links Links
|
Links Links
|
||||||
Metas map[string]string
|
Metas map[string]string
|
||||||
DefaultLink string
|
DefaultLink string
|
||||||
GitRepo *git.Repository
|
GitRepo *git.Repository
|
||||||
|
// reporting the target blob that is to-be-rendered enables
|
||||||
|
// deeper inspection in the handler for external renderer
|
||||||
|
// (i.e., more targeted handling of annexed files)
|
||||||
|
Blob *git.Blob
|
||||||
ShaExistCache map[string]bool
|
ShaExistCache map[string]bool
|
||||||
cancelFn func()
|
cancelFn func()
|
||||||
SidebarTocNode ast.Node
|
SidebarTocNode ast.Node
|
||||||
|
|
|
@ -94,10 +94,10 @@ func createDefaultPolicy() *bluemonday.Policy {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow classes for anchors
|
// Allow classes for anchors
|
||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`ref-issue( ref-external-issue)?`)).OnElements("a")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^ref-issue( ref-external-issue)?$`)).OnElements("a")
|
||||||
|
|
||||||
// Allow classes for task lists
|
// Allow classes for task lists
|
||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`task-list-item`)).OnElements("li")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^task-list-item$`)).OnElements("li")
|
||||||
|
|
||||||
// Allow classes for org mode list item status.
|
// Allow classes for org mode list item status.
|
||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(unchecked|checked|indeterminate)$`)).OnElements("li")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(unchecked|checked|indeterminate)$`)).OnElements("li")
|
||||||
|
@ -106,7 +106,7 @@ func createDefaultPolicy() *bluemonday.Policy {
|
||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^icon(\s+[\p{L}\p{N}_-]+)+$`)).OnElements("i")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^icon(\s+[\p{L}\p{N}_-]+)+$`)).OnElements("i")
|
||||||
|
|
||||||
// Allow classes for emojis
|
// Allow classes for emojis
|
||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`emoji`)).OnElements("img")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^emoji$`)).OnElements("img")
|
||||||
|
|
||||||
// Allow icons, emojis, chroma syntax and keyword markup on span
|
// Allow icons, emojis, chroma syntax and keyword markup on span
|
||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^((icon(\s+[\p{L}\p{N}_-]+)+)|(emoji)|(language-math display)|(language-math inline))$|^([a-z][a-z0-9]{0,2})$|^` + keywordClass + `$`)).OnElements("span")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^((icon(\s+[\p{L}\p{N}_-]+)+)|(emoji)|(language-math display)|(language-math inline))$|^([a-z][a-z0-9]{0,2})$|^` + keywordClass + `$`)).OnElements("span")
|
||||||
|
@ -122,13 +122,13 @@ func createDefaultPolicy() *bluemonday.Policy {
|
||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile("^header$")).OnElements("div")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^header$")).OnElements("div")
|
||||||
policy.AllowAttrs("data-line-number").Matching(regexp.MustCompile("^[0-9]+$")).OnElements("span")
|
policy.AllowAttrs("data-line-number").Matching(regexp.MustCompile("^[0-9]+$")).OnElements("span")
|
||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile("^text small grey$")).OnElements("span")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^text small grey$")).OnElements("span")
|
||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile("^file-preview*")).OnElements("table")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^file-preview$")).OnElements("table")
|
||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile("^lines-escape$")).OnElements("td")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^lines-escape$")).OnElements("td")
|
||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile("^toggle-escape-button btn interact-bg$")).OnElements("button")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^toggle-escape-button btn interact-bg$")).OnElements("button")
|
||||||
policy.AllowAttrs("title").OnElements("button")
|
policy.AllowAttrs("title").OnElements("button")
|
||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile("^ambiguous-code-point$")).OnElements("span")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^ambiguous-code-point$")).OnElements("span")
|
||||||
policy.AllowAttrs("data-tooltip-content").OnElements("span")
|
policy.AllowAttrs("data-tooltip-content").OnElements("span")
|
||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile("muted|(text black)")).OnElements("a")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^muted|(text black)$")).OnElements("a")
|
||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile("^ui warning message tw-text-left$")).OnElements("div")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^ui warning message tw-text-left$")).OnElements("div")
|
||||||
|
|
||||||
// Allow generally safe attributes
|
// Allow generally safe attributes
|
||||||
|
|
|
@ -39,8 +39,8 @@ const (
|
||||||
var (
|
var (
|
||||||
reName = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+$`)
|
reName = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+$`)
|
||||||
reVer = regexp.MustCompile(`^[a-zA-Z0-9:_.+]+-+[0-9]+$`)
|
reVer = regexp.MustCompile(`^[a-zA-Z0-9:_.+]+-+[0-9]+$`)
|
||||||
reOptDep = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?[a-zA-Z0-9@._+-]+)?(:.*)?$`)
|
reOptDep = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?([0-9]+:)?[a-zA-Z0-9@._+-]+)?(:.*)?$`)
|
||||||
rePkgVer = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?[a-zA-Z0-9@._+-]+)?$`)
|
rePkgVer = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?([0-9]+:)?[a-zA-Z0-9@._+-]+)?$`)
|
||||||
|
|
||||||
magicZSTD = []byte{0x28, 0xB5, 0x2F, 0xFD}
|
magicZSTD = []byte{0x28, 0xB5, 0x2F, 0xFD}
|
||||||
magicXZ = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A}
|
magicXZ = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A}
|
||||||
|
@ -71,7 +71,7 @@ type VersionMetadata struct {
|
||||||
Conflicts []string `json:"conflicts,omitempty"`
|
Conflicts []string `json:"conflicts,omitempty"`
|
||||||
Replaces []string `json:"replaces,omitempty"`
|
Replaces []string `json:"replaces,omitempty"`
|
||||||
Backup []string `json:"backup,omitempty"`
|
Backup []string `json:"backup,omitempty"`
|
||||||
Xdata []string `json:"xdata,omitempty"`
|
XData []string `json:"xdata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileMetadata Metadata related to specific package file.
|
// FileMetadata Metadata related to specific package file.
|
||||||
|
@ -125,7 +125,7 @@ func ParsePackage(r *packages.HashedBuffer) (*Package, error) {
|
||||||
defer tarball.Close()
|
defer tarball.Close()
|
||||||
|
|
||||||
var pkg *Package
|
var pkg *Package
|
||||||
var mtree bool
|
var mTree bool
|
||||||
|
|
||||||
for {
|
for {
|
||||||
f, err := tarball.Read()
|
f, err := tarball.Read()
|
||||||
|
@ -135,24 +135,24 @@ func ParsePackage(r *packages.HashedBuffer) (*Package, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
switch f.Name() {
|
switch f.Name() {
|
||||||
case ".PKGINFO":
|
case ".PKGINFO":
|
||||||
pkg, err = ParsePackageInfo(tarballType, f)
|
pkg, err = ParsePackageInfo(tarballType, f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = f.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case ".MTREE":
|
case ".MTREE":
|
||||||
mtree = true
|
mTree = true
|
||||||
}
|
}
|
||||||
|
_ = f.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
if pkg == nil {
|
if pkg == nil {
|
||||||
return nil, util.NewInvalidArgumentErrorf(".PKGINFO file not found")
|
return nil, util.NewInvalidArgumentErrorf(".PKGINFO file not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !mtree {
|
if !mTree {
|
||||||
return nil, util.NewInvalidArgumentErrorf(".MTREE file not found")
|
return nil, util.NewInvalidArgumentErrorf(".MTREE file not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +220,7 @@ func ParsePackageInfo(compressType string, r io.Reader) (*Package, error) {
|
||||||
case "replaces":
|
case "replaces":
|
||||||
p.VersionMetadata.Replaces = append(p.VersionMetadata.Replaces, value)
|
p.VersionMetadata.Replaces = append(p.VersionMetadata.Replaces, value)
|
||||||
case "xdata":
|
case "xdata":
|
||||||
p.VersionMetadata.Xdata = append(p.VersionMetadata.Xdata, value)
|
p.VersionMetadata.XData = append(p.VersionMetadata.XData, value)
|
||||||
case "builddate":
|
case "builddate":
|
||||||
bd, err := strconv.ParseInt(value, 10, 64)
|
bd, err := strconv.ParseInt(value, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -260,48 +260,43 @@ func ValidatePackageSpec(p *Package) error {
|
||||||
return util.NewInvalidArgumentErrorf("invalid project URL")
|
return util.NewInvalidArgumentErrorf("invalid project URL")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, cd := range p.VersionMetadata.CheckDepends {
|
for _, checkDepend := range p.VersionMetadata.CheckDepends {
|
||||||
if !rePkgVer.MatchString(cd) {
|
if !rePkgVer.MatchString(checkDepend) {
|
||||||
return util.NewInvalidArgumentErrorf("invalid check dependency: %s", cd)
|
return util.NewInvalidArgumentErrorf("invalid check dependency: %s", checkDepend)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, d := range p.VersionMetadata.Depends {
|
for _, depend := range p.VersionMetadata.Depends {
|
||||||
if !rePkgVer.MatchString(d) {
|
if !rePkgVer.MatchString(depend) {
|
||||||
return util.NewInvalidArgumentErrorf("invalid dependency: %s", d)
|
return util.NewInvalidArgumentErrorf("invalid dependency: %s", depend)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, md := range p.VersionMetadata.MakeDepends {
|
for _, makeDepend := range p.VersionMetadata.MakeDepends {
|
||||||
if !rePkgVer.MatchString(md) {
|
if !rePkgVer.MatchString(makeDepend) {
|
||||||
return util.NewInvalidArgumentErrorf("invalid make dependency: %s", md)
|
return util.NewInvalidArgumentErrorf("invalid make dependency: %s", makeDepend)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, p := range p.VersionMetadata.Provides {
|
for _, provide := range p.VersionMetadata.Provides {
|
||||||
if !rePkgVer.MatchString(p) {
|
if !rePkgVer.MatchString(provide) {
|
||||||
return util.NewInvalidArgumentErrorf("invalid provides: %s", p)
|
return util.NewInvalidArgumentErrorf("invalid provides: %s", provide)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, p := range p.VersionMetadata.Conflicts {
|
for _, conflict := range p.VersionMetadata.Conflicts {
|
||||||
if !rePkgVer.MatchString(p) {
|
if !rePkgVer.MatchString(conflict) {
|
||||||
return util.NewInvalidArgumentErrorf("invalid conflicts: %s", p)
|
return util.NewInvalidArgumentErrorf("invalid conflicts: %s", conflict)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, p := range p.VersionMetadata.Replaces {
|
for _, replace := range p.VersionMetadata.Replaces {
|
||||||
if !rePkgVer.MatchString(p) {
|
if !rePkgVer.MatchString(replace) {
|
||||||
return util.NewInvalidArgumentErrorf("invalid replaces: %s", p)
|
return util.NewInvalidArgumentErrorf("invalid replaces: %s", replace)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, p := range p.VersionMetadata.Replaces {
|
for _, optDepend := range p.VersionMetadata.OptDepends {
|
||||||
if !rePkgVer.MatchString(p) {
|
if !reOptDep.MatchString(optDepend) {
|
||||||
return util.NewInvalidArgumentErrorf("invalid xdata: %s", p)
|
return util.NewInvalidArgumentErrorf("invalid optional dependency: %s", optDepend)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, od := range p.VersionMetadata.OptDepends {
|
for _, b := range p.VersionMetadata.Backup {
|
||||||
if !reOptDep.MatchString(od) {
|
if strings.HasPrefix(b, "/") {
|
||||||
return util.NewInvalidArgumentErrorf("invalid optional dependency: %s", od)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, bf := range p.VersionMetadata.Backup {
|
|
||||||
if strings.HasPrefix(bf, "/") {
|
|
||||||
return util.NewInvalidArgumentErrorf("backup file contains leading forward slash")
|
return util.NewInvalidArgumentErrorf("backup file contains leading forward slash")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,7 @@ func parsePackage(r io.Reader) (*Package, error) {
|
||||||
Target *string `json:"target"`
|
Target *string `json:"target"`
|
||||||
Kind string `json:"kind"`
|
Kind string `json:"kind"`
|
||||||
Registry *string `json:"registry"`
|
Registry *string `json:"registry"`
|
||||||
ExplicitNameInToml string `json:"explicit_name_in_toml"`
|
ExplicitNameInToml *string `json:"explicit_name_in_toml"`
|
||||||
} `json:"deps"`
|
} `json:"deps"`
|
||||||
Features map[string][]string `json:"features"`
|
Features map[string][]string `json:"features"`
|
||||||
Authors []string `json:"authors"`
|
Authors []string `json:"authors"`
|
||||||
|
@ -136,8 +136,16 @@ func parsePackage(r io.Reader) (*Package, error) {
|
||||||
|
|
||||||
dependencies := make([]*Dependency, 0, len(meta.Deps))
|
dependencies := make([]*Dependency, 0, len(meta.Deps))
|
||||||
for _, dep := range meta.Deps {
|
for _, dep := range meta.Deps {
|
||||||
|
name := dep.Name
|
||||||
|
packageName := dep.ExplicitNameInToml
|
||||||
|
// If the explicit_name_in_toml field is set, the package is renamed and
|
||||||
|
// should be set accordingly.
|
||||||
|
if dep.ExplicitNameInToml != nil {
|
||||||
|
name = *dep.ExplicitNameInToml
|
||||||
|
packageName = &dep.Name
|
||||||
|
}
|
||||||
dependencies = append(dependencies, &Dependency{
|
dependencies = append(dependencies, &Dependency{
|
||||||
Name: dep.Name,
|
Name: name,
|
||||||
Req: dep.VersionReq,
|
Req: dep.VersionReq,
|
||||||
Features: dep.Features,
|
Features: dep.Features,
|
||||||
Optional: dep.Optional,
|
Optional: dep.Optional,
|
||||||
|
@ -145,6 +153,7 @@ func parsePackage(r io.Reader) (*Package, error) {
|
||||||
Target: dep.Target,
|
Target: dep.Target,
|
||||||
Kind: dep.Kind,
|
Kind: dep.Kind,
|
||||||
Registry: dep.Registry,
|
Registry: dep.Registry,
|
||||||
|
Package: packageName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParsePackage(t *testing.T) {
|
func TestParsePackage(t *testing.T) {
|
||||||
createPackage := func(name, version string) io.Reader {
|
createPackage := func(name, version, dependency string) io.Reader {
|
||||||
metadata := `{
|
metadata := `{
|
||||||
"name":"` + name + `",
|
"name":"` + name + `",
|
||||||
"vers":"` + version + `",
|
"vers":"` + version + `",
|
||||||
|
@ -32,7 +32,7 @@ func TestParsePackage(t *testing.T) {
|
||||||
{
|
{
|
||||||
"name":"dep",
|
"name":"dep",
|
||||||
"version_req":"1.0"
|
"version_req":"1.0"
|
||||||
}
|
}` + dependency + `
|
||||||
],
|
],
|
||||||
"homepage":"` + homepage + `",
|
"homepage":"` + homepage + `",
|
||||||
"license":"` + license + `"
|
"license":"` + license + `"
|
||||||
|
@ -48,7 +48,7 @@ func TestParsePackage(t *testing.T) {
|
||||||
|
|
||||||
t.Run("InvalidName", func(t *testing.T) {
|
t.Run("InvalidName", func(t *testing.T) {
|
||||||
for _, name := range []string{"", "0test", "-test", "_test", strings.Repeat("a", 65)} {
|
for _, name := range []string{"", "0test", "-test", "_test", strings.Repeat("a", 65)} {
|
||||||
data := createPackage(name, "1.0.0")
|
data := createPackage(name, "1.0.0", "")
|
||||||
|
|
||||||
cp, err := ParsePackage(data)
|
cp, err := ParsePackage(data)
|
||||||
assert.Nil(t, cp)
|
assert.Nil(t, cp)
|
||||||
|
@ -58,7 +58,7 @@ func TestParsePackage(t *testing.T) {
|
||||||
|
|
||||||
t.Run("InvalidVersion", func(t *testing.T) {
|
t.Run("InvalidVersion", func(t *testing.T) {
|
||||||
for _, version := range []string{"", "1.", "-1.0", "1.0.0/1"} {
|
for _, version := range []string{"", "1.", "-1.0", "1.0.0/1"} {
|
||||||
data := createPackage("test", version)
|
data := createPackage("test", version, "")
|
||||||
|
|
||||||
cp, err := ParsePackage(data)
|
cp, err := ParsePackage(data)
|
||||||
assert.Nil(t, cp)
|
assert.Nil(t, cp)
|
||||||
|
@ -67,7 +67,7 @@ func TestParsePackage(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Valid", func(t *testing.T) {
|
t.Run("Valid", func(t *testing.T) {
|
||||||
data := createPackage("test", "1.0.0")
|
data := createPackage("test", "1.0.0", "")
|
||||||
|
|
||||||
cp, err := ParsePackage(data)
|
cp, err := ParsePackage(data)
|
||||||
assert.NotNil(t, cp)
|
assert.NotNil(t, cp)
|
||||||
|
@ -84,4 +84,25 @@ func TestParsePackage(t *testing.T) {
|
||||||
content, _ := io.ReadAll(cp.Content)
|
content, _ := io.ReadAll(cp.Content)
|
||||||
assert.Equal(t, "test", string(content))
|
assert.Equal(t, "test", string(content))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Renamed dependency", func(t *testing.T) {
|
||||||
|
data := createPackage("test", "1.0.0", `, {"name":"v4l2-sys", "version":"0.3.0", "explicit_name_in_toml":"v4l2-sys-mit"}`)
|
||||||
|
|
||||||
|
cp, err := ParsePackage(data)
|
||||||
|
assert.NotNil(t, cp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "test", cp.Name)
|
||||||
|
assert.Equal(t, "1.0.0", cp.Version)
|
||||||
|
assert.Equal(t, description, cp.Metadata.Description)
|
||||||
|
assert.Equal(t, []string{author}, cp.Metadata.Authors)
|
||||||
|
assert.Len(t, cp.Metadata.Dependencies, 2)
|
||||||
|
assert.Equal(t, "dep", cp.Metadata.Dependencies[0].Name)
|
||||||
|
assert.EqualValues(t, "v4l2-sys-mit", cp.Metadata.Dependencies[1].Name)
|
||||||
|
assert.EqualValues(t, "v4l2-sys", *cp.Metadata.Dependencies[1].Package)
|
||||||
|
assert.Equal(t, homepage, cp.Metadata.ProjectURL)
|
||||||
|
assert.Equal(t, license, cp.Metadata.License)
|
||||||
|
content, _ := io.ReadAll(cp.Content)
|
||||||
|
assert.Equal(t, "test", string(content))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ type Metadata struct {
|
||||||
Homepage string `json:"homepage,omitempty"`
|
Homepage string `json:"homepage,omitempty"`
|
||||||
License Licenses `json:"license,omitempty"`
|
License Licenses `json:"license,omitempty"`
|
||||||
Authors []Author `json:"authors,omitempty"`
|
Authors []Author `json:"authors,omitempty"`
|
||||||
|
Bin []string `json:"bin,omitempty"`
|
||||||
Autoload map[string]any `json:"autoload,omitempty"`
|
Autoload map[string]any `json:"autoload,omitempty"`
|
||||||
AutoloadDev map[string]any `json:"autoload-dev,omitempty"`
|
AutoloadDev map[string]any `json:"autoload-dev,omitempty"`
|
||||||
Extra map[string]any `json:"extra,omitempty"`
|
Extra map[string]any `json:"extra,omitempty"`
|
||||||
|
|
|
@ -40,6 +40,7 @@ type ServCommandResults struct {
|
||||||
UserName string
|
UserName string
|
||||||
UserEmail string
|
UserEmail string
|
||||||
UserID int64
|
UserID int64
|
||||||
|
UserMode perm.AccessMode
|
||||||
OwnerName string
|
OwnerName string
|
||||||
RepoName string
|
RepoName string
|
||||||
RepoID int64
|
RepoID int64
|
||||||
|
|
25
modules/setting/annex.go
Normal file
25
modules/setting/annex.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Annex represents the configuration for git-annex
|
||||||
|
var Annex = struct {
|
||||||
|
Enabled bool `ini:"ENABLED"`
|
||||||
|
DisableP2PHTTP bool `ini:"DISABLE_P2PHTTP"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
func loadAnnexFrom(rootCfg ConfigProvider) {
|
||||||
|
sec := rootCfg.Section("annex")
|
||||||
|
if err := sec.MapTo(&Annex); err != nil {
|
||||||
|
log.Fatal("Failed to map Annex settings: %v", err)
|
||||||
|
}
|
||||||
|
if !sec.HasKey("DISABLE_P2PHTTP") {
|
||||||
|
// If DisableP2PHTTP is not explicitly set then use DisableHTTPGit as its default
|
||||||
|
Annex.DisableP2PHTTP = Repository.DisableHTTPGit
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,18 +3,28 @@
|
||||||
|
|
||||||
package setting
|
package setting
|
||||||
|
|
||||||
import "code.gitea.io/gitea/modules/log"
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
)
|
||||||
|
|
||||||
var Camo = struct {
|
var Camo = struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
ServerURL string `ini:"SERVER_URL"`
|
ServerURL string `ini:"SERVER_URL"`
|
||||||
HMACKey string `ini:"HMAC_KEY"`
|
HMACKey string `ini:"HMAC_KEY"`
|
||||||
Allways bool
|
Always bool
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
func loadCamoFrom(rootCfg ConfigProvider) {
|
func loadCamoFrom(rootCfg ConfigProvider) {
|
||||||
mustMapSetting(rootCfg, "camo", &Camo)
|
mustMapSetting(rootCfg, "camo", &Camo)
|
||||||
if Camo.Enabled {
|
if Camo.Enabled {
|
||||||
|
oldValue := rootCfg.Section("camo").Key("ALLWAYS").MustString("")
|
||||||
|
if oldValue != "" {
|
||||||
|
log.Warn("camo.ALLWAYS is deprecated, use camo.ALWAYS instead")
|
||||||
|
Camo.Always, _ = strconv.ParseBool(oldValue)
|
||||||
|
}
|
||||||
|
|
||||||
if Camo.ServerURL == "" || Camo.HMACKey == "" {
|
if Camo.ServerURL == "" || Camo.HMACKey == "" {
|
||||||
log.Fatal(`Camo settings require "SERVER_URL" and HMAC_KEY`)
|
log.Fatal(`Camo settings require "SERVER_URL" and HMAC_KEY`)
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue