mirror of
https://github.com/go-gitea/gitea.git
synced 2025-12-13 18:32:54 +00:00
Compare commits
205 Commits
e98d9bb93e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29057ea55f | ||
|
|
ac8308b5cb | ||
|
|
1e72b15639 | ||
|
|
3102c04c1e | ||
|
|
3e57ba5b36 | ||
|
|
4c06c98dda | ||
|
|
87b855bd15 | ||
|
|
906adff0c1 | ||
|
|
4cbcb91b7b | ||
|
|
bfbc38f40c | ||
|
|
d2a372fc59 | ||
|
|
f25409fab8 | ||
|
|
01351cc6c7 | ||
|
|
a440116a16 | ||
|
|
24b81ac8b9 | ||
|
|
1c69fdccdd | ||
|
|
ed698d1a61 | ||
|
|
d83a071db9 | ||
|
|
69700f9cdd | ||
|
|
98ef79d73a | ||
|
|
b41ccb0627 | ||
|
|
c287a8cdb5 | ||
|
|
ca8c4ebecd | ||
|
|
6675ddc117 | ||
|
|
5fdc84841a | ||
|
|
64960a18f9 | ||
|
|
cb5082f8fe | ||
|
|
ee365f5100 | ||
|
|
b49dd8e32f | ||
|
|
ee6e371e44 | ||
|
|
e30a130b9a | ||
|
|
97cb4409fb | ||
|
|
46d7adefe0 | ||
|
|
9f268edd2f | ||
|
|
ca4b21c305 | ||
|
|
a04a16dc2b | ||
|
|
1e777f92c7 | ||
|
|
5340db4dbe | ||
|
|
7d6861ac54 | ||
|
|
b54af8811e | ||
|
|
f4e38e6367 | ||
|
|
a36951aef6 | ||
|
|
9668913d76 | ||
|
|
ede7f1a069 | ||
|
|
1816c7f9c1 | ||
|
|
66707bc3ea | ||
|
|
000c06d41b | ||
|
|
abe2755f7a | ||
|
|
688430e3ce | ||
|
|
87d5a8507d | ||
|
|
ed977d9702 | ||
|
|
62d750eadb | ||
|
|
a60a8c6966 | ||
|
|
4c51acb26b | ||
|
|
543e3bf7e9 | ||
|
|
1baca49870 | ||
|
|
afc25c5145 | ||
|
|
98eb2b0aba | ||
|
|
adece922f1 | ||
|
|
1da1e644ed | ||
|
|
e1372e5bc1 | ||
|
|
de69e7f16a | ||
|
|
0fb3be7f0e | ||
|
|
d6dc531d4b | ||
|
|
358de23a50 | ||
|
|
018156079b | ||
|
|
1f3558b65c | ||
|
|
b95fd7e13e | ||
|
|
372d24b84b | ||
|
|
2223be2cc4 | ||
|
|
9affb513a8 | ||
|
|
e31f224ad2 | ||
|
|
1c8c56503f | ||
|
|
60314cb688 | ||
|
|
c4c4cf5687 | ||
|
|
050c9485df | ||
|
|
919348665b | ||
|
|
c12bc4aa30 | ||
|
|
367a289b29 | ||
|
|
bfaddbcd0d | ||
|
|
0ce7d66368 | ||
|
|
b2feeddf42 | ||
|
|
eef9406c6b | ||
|
|
aaa8033ee9 | ||
|
|
23a37b4b77 | ||
|
|
61e5cc173e | ||
|
|
84d7496b9d | ||
|
|
525265c1a8 | ||
|
|
d0ca2f6bc3 | ||
|
|
a0f492d9f4 | ||
|
|
206f4c88b1 | ||
|
|
851db77256 | ||
|
|
2be51d0b27 | ||
|
|
850012bf5c | ||
|
|
bb1f52347a | ||
|
|
de26c8acce | ||
|
|
d9c0f86de8 | ||
|
|
37208fef7e | ||
|
|
aa7ec64a54 | ||
|
|
17a6a2bab1 | ||
|
|
685c8c314f | ||
|
|
5cb453c01b | ||
|
|
f2d7931b70 | ||
|
|
b3e5b96111 | ||
|
|
1dac4d13f3 | ||
|
|
b148bef471 | ||
|
|
de70cd3853 | ||
|
|
ef90befef1 | ||
|
|
c3472dd395 | ||
|
|
8b290b87e9 | ||
|
|
3ab8ae5807 | ||
|
|
73e229eb42 | ||
|
|
98ff7d0773 | ||
|
|
8aa1179ce4 | ||
|
|
39c08ce4c1 | ||
|
|
fe25997157 | ||
|
|
95b18eb781 | ||
|
|
d69eede59b | ||
|
|
91839ca01a | ||
|
|
6b5563c54a | ||
|
|
cddff73bbd | ||
|
|
87d670c96b | ||
|
|
2f309b844c | ||
|
|
bc50431e8b | ||
|
|
2a6af15448 | ||
|
|
c9beb0b01f | ||
|
|
5454fdacd4 | ||
|
|
304d836a61 | ||
|
|
cb72c901b3 | ||
|
|
0d740a6a72 | ||
|
|
9a73a1fb83 | ||
|
|
397d666432 | ||
|
|
e03a68c48b | ||
|
|
cab35ff17a | ||
|
|
522c466e24 | ||
|
|
8085c75356 | ||
|
|
195fc715ff | ||
|
|
08b9776970 | ||
|
|
a9f2ea720b | ||
|
|
5f0697243c | ||
|
|
c28aab6714 | ||
|
|
a4e23b81d3 | ||
|
|
3917d27467 | ||
|
|
a2eea2fb2e | ||
|
|
b2ee5be52e | ||
|
|
897e48dde3 | ||
|
|
66ee8f3553 | ||
|
|
c30d74d0f9 | ||
|
|
2d36a0c9ff | ||
|
|
322cb048e7 | ||
|
|
a7eceb57a9 | ||
|
|
ebd88af075 | ||
|
|
5bf7cf788d | ||
|
|
bf8ecf7c93 | ||
|
|
990201dc93 | ||
|
|
c55a017225 | ||
|
|
1bdb0b71b1 | ||
|
|
9ae2e9e76f | ||
|
|
16fc3323b9 | ||
|
|
731d803d19 | ||
|
|
96102c69e7 | ||
|
|
22b92e30ca | ||
|
|
49a0a11f55 | ||
|
|
912515e63a | ||
|
|
f9a4b2753c | ||
|
|
2401812b76 | ||
|
|
3d264ba636 | ||
|
|
662a44d924 | ||
|
|
24a595c3fc | ||
|
|
25c4eb1659 | ||
|
|
b029ad431b | ||
|
|
40f71bcd4c | ||
|
|
327d0a7fdd | ||
|
|
165a3ead52 | ||
|
|
9f664ab330 | ||
|
|
94d99c9c3c | ||
|
|
b8e5e2a93e | ||
|
|
0bc129481d | ||
|
|
03fce8f3d0 | ||
|
|
69f5ee970c | ||
|
|
c9e7fde8b3 | ||
|
|
ad2ff67343 | ||
|
|
cdc0733047 | ||
|
|
0a0baeb3b0 | ||
|
|
851d8f9f7c | ||
|
|
4e7a97dea0 | ||
|
|
e1c2fef593 | ||
|
|
6589326e96 | ||
|
|
17c8aa6587 | ||
|
|
71360a94cb | ||
|
|
c4532101a4 | ||
|
|
efc48c36ff | ||
|
|
340aed3982 | ||
|
|
b907b9fb1a | ||
|
|
c5d74e5869 | ||
|
|
c5332fdc55 | ||
|
|
0f668145e9 | ||
|
|
fbe80e6df2 | ||
|
|
151ef80e28 | ||
|
|
8106d95577 | ||
|
|
1f32170060 | ||
|
|
7bf2972379 | ||
|
|
8ad2a538da | ||
|
|
53dfbbb2ee | ||
|
|
d83676c97a |
@@ -4,7 +4,7 @@ tmp_dir = ".air"
|
||||
[build]
|
||||
pre_cmd = ["killall -9 gitea 2>/dev/null || true"] # kill off potential zombie processes from previous runs
|
||||
cmd = "make --no-print-directory backend"
|
||||
bin = "gitea"
|
||||
entrypoint = ["./gitea"]
|
||||
delay = 2000
|
||||
include_ext = ["go", "tmpl"]
|
||||
include_file = ["main.go"]
|
||||
|
||||
@@ -74,6 +74,9 @@ cpu.out
|
||||
/VERSION
|
||||
/.air
|
||||
/.go-licenses
|
||||
/Dockerfile
|
||||
/Dockerfile.rootless
|
||||
/.venv
|
||||
|
||||
# Files and folders that were previously generated
|
||||
/public/assets/img/webpack
|
||||
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -8,3 +8,4 @@
|
||||
/vendor/** -text -eol linguist-vendored
|
||||
/web_src/js/vendor/** -text -eol linguist-vendored
|
||||
Dockerfile.* linguist-language=Dockerfile
|
||||
Makefile.* linguist-language=Makefile
|
||||
|
||||
1
.github/labeler.yml
vendored
1
.github/labeler.yml
vendored
@@ -51,7 +51,6 @@ modifies/internal:
|
||||
- ".github/**"
|
||||
- ".gitea/**"
|
||||
- ".devcontainer/**"
|
||||
- "build.go"
|
||||
- "build/**"
|
||||
- "contrib/**"
|
||||
|
||||
|
||||
4
.github/workflows/cron-licenses.yml
vendored
4
.github/workflows/cron-licenses.yml
vendored
@@ -9,8 +9,10 @@ jobs:
|
||||
cron-licenses:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'go-gitea/gitea'
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
4
.github/workflows/cron-translations.yml
vendored
4
.github/workflows/cron-translations.yml
vendored
@@ -9,8 +9,10 @@ jobs:
|
||||
crowdin-pull:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'go-gitea/gitea'
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: crowdin/github-action@v1
|
||||
with:
|
||||
upload_sources: true
|
||||
|
||||
4
.github/workflows/files-changed.yml
vendored
4
.github/workflows/files-changed.yml
vendored
@@ -24,6 +24,8 @@ jobs:
|
||||
detect:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 3
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
backend: ${{ steps.changes.outputs.backend }}
|
||||
frontend: ${{ steps.changes.outputs.frontend }}
|
||||
@@ -34,7 +36,7 @@ jobs:
|
||||
swagger: ${{ steps.changes.outputs.swagger }}
|
||||
yaml: ${{ steps.changes.outputs.yaml }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: changes
|
||||
with:
|
||||
|
||||
50
.github/workflows/pull-compliance.yml
vendored
50
.github/workflows/pull-compliance.yml
vendored
@@ -10,13 +10,17 @@ concurrency:
|
||||
jobs:
|
||||
files-changed:
|
||||
uses: ./.github/workflows/files-changed.yml
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
lint-backend:
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
@@ -30,8 +34,10 @@ jobs:
|
||||
if: needs.files-changed.outputs.templates == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: astral-sh/setup-uv@v6
|
||||
- run: uv python install 3.12
|
||||
- uses: pnpm/action-setup@v4
|
||||
@@ -46,8 +52,10 @@ jobs:
|
||||
if: needs.files-changed.outputs.yaml == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: astral-sh/setup-uv@v6
|
||||
- run: uv python install 3.12
|
||||
- run: make deps-py
|
||||
@@ -57,8 +65,10 @@ jobs:
|
||||
if: needs.files-changed.outputs.swagger == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
@@ -70,8 +80,10 @@ jobs:
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.templates == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
@@ -82,8 +94,10 @@ jobs:
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
@@ -99,8 +113,10 @@ jobs:
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
@@ -114,8 +130,10 @@ jobs:
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
@@ -127,8 +145,10 @@ jobs:
|
||||
if: needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
@@ -143,8 +163,10 @@ jobs:
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
@@ -175,8 +197,10 @@ jobs:
|
||||
if: needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
@@ -188,8 +212,10 @@ jobs:
|
||||
if: needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
28
.github/workflows/pull-db-tests.yml
vendored
28
.github/workflows/pull-db-tests.yml
vendored
@@ -10,11 +10,15 @@ concurrency:
|
||||
jobs:
|
||||
files-changed:
|
||||
uses: ./.github/workflows/files-changed.yml
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
test-pgsql:
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
services:
|
||||
pgsql:
|
||||
image: postgres:14
|
||||
@@ -38,7 +42,7 @@ jobs:
|
||||
ports:
|
||||
- "9000:9000"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
@@ -65,20 +69,22 @@ jobs:
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- run: make deps-backend
|
||||
- run: make backend
|
||||
- run: GOEXPERIMENT='' make backend
|
||||
env:
|
||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
||||
- name: run migration tests
|
||||
run: make test-sqlite-migration
|
||||
- name: run tests
|
||||
run: make test-sqlite
|
||||
run: GOEXPERIMENT='' make test-sqlite
|
||||
timeout-minutes: 50
|
||||
env:
|
||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
||||
@@ -90,6 +96,8 @@ jobs:
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
services:
|
||||
elasticsearch:
|
||||
image: elasticsearch:7.5.0
|
||||
@@ -124,7 +132,7 @@ jobs:
|
||||
ports:
|
||||
- 10000:10000
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
@@ -142,7 +150,7 @@ jobs:
|
||||
RACE_ENABLED: true
|
||||
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
||||
- name: unit-tests-gogit
|
||||
run: make unit-test-coverage test-check
|
||||
run: GOEXPERIMENT='' make unit-test-coverage test-check
|
||||
env:
|
||||
TAGS: bindata gogit
|
||||
RACE_ENABLED: true
|
||||
@@ -152,6 +160,8 @@ jobs:
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
services:
|
||||
mysql:
|
||||
# the bitnami mysql image has more options than the official one, it's easier to customize
|
||||
@@ -177,7 +187,7 @@ jobs:
|
||||
- "587:587"
|
||||
- "993:993"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
@@ -203,6 +213,8 @@ jobs:
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
services:
|
||||
mssql:
|
||||
image: mcr.microsoft.com/mssql/server:2019-latest
|
||||
@@ -217,7 +229,7 @@ jobs:
|
||||
ports:
|
||||
- 10000:10000
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
22
.github/workflows/pull-docker-dryrun.yml
vendored
22
.github/workflows/pull-docker-dryrun.yml
vendored
@@ -10,26 +10,28 @@ concurrency:
|
||||
jobs:
|
||||
files-changed:
|
||||
uses: ./.github/workflows/files-changed.yml
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
regular:
|
||||
container:
|
||||
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/build-push-action@v5
|
||||
- name: Build regular container image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
tags: gitea/gitea:linux-amd64
|
||||
|
||||
rootless:
|
||||
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/build-push-action@v5
|
||||
- name: Build rootless container image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
file: Dockerfile.rootless
|
||||
tags: gitea/gitea:linux-amd64
|
||||
|
||||
35
.github/workflows/pull-e2e-tests.yml
vendored
35
.github/workflows/pull-e2e-tests.yml
vendored
@@ -1,35 +0,0 @@
|
||||
name: e2e-tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
uses: ./.github/workflows/files-changed.yml
|
||||
|
||||
test-e2e:
|
||||
# the "test-e2e" won't pass, and it seems that there is no useful test, so skip
|
||||
# if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
if: false
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 24
|
||||
- run: make deps-frontend frontend deps-backend
|
||||
- run: pnpm exec playwright install --with-deps
|
||||
- run: make test-e2e-sqlite
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
USE_REPO_TEST_DIR: 1
|
||||
2
.github/workflows/pull-labeler.yml
vendored
2
.github/workflows/pull-labeler.yml
vendored
@@ -15,6 +15,6 @@ jobs:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/labeler@v5
|
||||
- uses: actions/labeler@v6
|
||||
with:
|
||||
sync-labels: true
|
||||
|
||||
86
.github/workflows/release-nightly.yml
vendored
86
.github/workflows/release-nightly.yml
vendored
@@ -11,8 +11,10 @@ concurrency:
|
||||
jobs:
|
||||
nightly-binary:
|
||||
runs-on: namespace-profile-gitea-release-binary
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
@@ -56,19 +58,17 @@ jobs:
|
||||
- name: upload binaries to s3
|
||||
run: |
|
||||
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
||||
nightly-docker-rootful:
|
||||
|
||||
nightly-container:
|
||||
runs-on: namespace-profile-gitea-release-docker
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write # to publish to ghcr.io
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: Get cleaned branch name
|
||||
@@ -76,6 +76,29 @@ jobs:
|
||||
run: |
|
||||
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
||||
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
||||
- uses: docker/metadata-action@v5
|
||||
id: meta
|
||||
with:
|
||||
images: |-
|
||||
gitea/gitea
|
||||
ghcr.io/go-gitea/gitea
|
||||
tags: |
|
||||
type=raw,value=${{ steps.clean_name.outputs.branch }}
|
||||
annotations: |
|
||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||
- uses: docker/metadata-action@v5
|
||||
id: meta_rootless
|
||||
with:
|
||||
images: |-
|
||||
gitea/gitea
|
||||
ghcr.io/go-gitea/gitea
|
||||
# each tag below will have the suffix of -rootless
|
||||
flavor: |
|
||||
suffix=-rootless
|
||||
tags: |
|
||||
type=raw,value=${{ steps.clean_name.outputs.branch }}
|
||||
annotations: |
|
||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -87,57 +110,20 @@ jobs:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: fetch go modules
|
||||
run: make vendor
|
||||
- name: build rootful docker image
|
||||
- name: build regular docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||
push: true
|
||||
tags: |-
|
||||
gitea/gitea:${{ steps.clean_name.outputs.branch }}
|
||||
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}
|
||||
nightly-docker-rootless:
|
||||
runs-on: namespace-profile-gitea-release-docker
|
||||
permissions:
|
||||
packages: write # to publish to ghcr.io
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: Get cleaned branch name
|
||||
id: clean_name
|
||||
run: |
|
||||
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
||||
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Login to GHCR using PAT
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: fetch go modules
|
||||
run: make vendor
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
annotations: ${{ steps.meta.outputs.annotations }}
|
||||
- name: build rootless docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||
push: true
|
||||
file: Dockerfile.rootless
|
||||
tags: |-
|
||||
gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
|
||||
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
|
||||
tags: ${{ steps.meta_rootless.outputs.tags }}
|
||||
annotations: ${{ steps.meta_rootless.outputs.annotations }}
|
||||
|
||||
60
.github/workflows/release-tag-rc.yml
vendored
60
.github/workflows/release-tag-rc.yml
vendored
@@ -12,8 +12,10 @@ concurrency:
|
||||
jobs:
|
||||
binary:
|
||||
runs-on: namespace-profile-gitea-release-binary
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
@@ -66,12 +68,14 @@ jobs:
|
||||
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --draft --notes-from-tag dist/release/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
docker-rootful:
|
||||
|
||||
container:
|
||||
runs-on: namespace-profile-gitea-release-docker
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write # to publish to ghcr.io
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
@@ -88,38 +92,10 @@ jobs:
|
||||
# 1.2.3-rc0
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Login to GHCR using PAT
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: build rootful docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
docker-rootless:
|
||||
runs-on: namespace-profile-gitea-release-docker
|
||||
permissions:
|
||||
packages: write # to publish to ghcr.io
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
annotations: |
|
||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||
- uses: docker/metadata-action@v5
|
||||
id: meta
|
||||
id: meta_rootless
|
||||
with:
|
||||
images: |-
|
||||
gitea/gitea
|
||||
@@ -131,6 +107,8 @@ jobs:
|
||||
# 1.2.3-rc0
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
annotations: |
|
||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -142,12 +120,20 @@ jobs:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: build rootless docker image
|
||||
- name: build regular container image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
annotations: ${{ steps.meta.outputs.annotations }}
|
||||
- name: build rootless container image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||
push: true
|
||||
file: Dockerfile.rootless
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
tags: ${{ steps.meta_rootless.outputs.tags }}
|
||||
annotations: ${{ steps.meta_rootless.outputs.annotations }}
|
||||
|
||||
57
.github/workflows/release-tag-version.yml
vendored
57
.github/workflows/release-tag-version.yml
vendored
@@ -15,9 +15,10 @@ jobs:
|
||||
binary:
|
||||
runs-on: namespace-profile-gitea-release-binary
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write # to publish to ghcr.io
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
@@ -70,12 +71,14 @@ jobs:
|
||||
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --notes-from-tag dist/release/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
docker-rootful:
|
||||
|
||||
container:
|
||||
runs-on: namespace-profile-gitea-release-docker
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write # to publish to ghcr.io
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
@@ -96,36 +99,10 @@ jobs:
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Login to GHCR using PAT
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: build rootful docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
docker-rootless:
|
||||
runs-on: namespace-profile-gitea-release-docker
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
annotations: |
|
||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||
- uses: docker/metadata-action@v5
|
||||
id: meta
|
||||
id: meta_rootless
|
||||
with:
|
||||
images: |-
|
||||
gitea/gitea
|
||||
@@ -142,6 +119,8 @@ jobs:
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
annotations: |
|
||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -153,12 +132,20 @@ jobs:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: build rootless docker image
|
||||
- name: build regular container image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
annotations: ${{ steps.meta.outputs.annotations }}
|
||||
- name: build rootless container image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||
push: true
|
||||
file: Dockerfile.rootless
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
tags: ${{ steps.meta_rootless.outputs.tags }}
|
||||
annotations: ${{ steps.meta_rootless.outputs.annotations }}
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -25,6 +25,9 @@ __debug_bin*
|
||||
# Visual Studio
|
||||
/.vs/
|
||||
|
||||
# mise version managment tool
|
||||
mise.toml
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
@@ -122,3 +125,8 @@ prime/
|
||||
/CLAUDE.md
|
||||
/llms.txt
|
||||
|
||||
# Ignore worktrees when working on multiple branches
|
||||
.worktrees/
|
||||
|
||||
# A Makefile for custom make targets
|
||||
Makefile.local
|
||||
|
||||
@@ -14,6 +14,7 @@ linters:
|
||||
- govet
|
||||
- ineffassign
|
||||
- mirror
|
||||
- modernize
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- perfsprint
|
||||
@@ -55,6 +56,7 @@ linters:
|
||||
disabled-checks:
|
||||
- ifElseChain
|
||||
- singleCaseSwitch # Every time this occurred in the code, there was no other way.
|
||||
- deprecatedComment # conflicts with go-swagger comments
|
||||
revive:
|
||||
severity: error
|
||||
rules:
|
||||
@@ -107,6 +109,15 @@ linters:
|
||||
- require-error
|
||||
usetesting:
|
||||
os-temp-dir: true
|
||||
modernize:
|
||||
disable:
|
||||
- stringsbuilder
|
||||
perfsprint:
|
||||
concat-loop: false
|
||||
govet:
|
||||
enable:
|
||||
- nilness
|
||||
- unusedwrite
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
@@ -153,6 +164,7 @@ linters:
|
||||
text: '(?i)exitAfterDefer:'
|
||||
paths:
|
||||
- node_modules
|
||||
- .venv
|
||||
- public
|
||||
- web_src
|
||||
- third_party$
|
||||
@@ -172,6 +184,7 @@ formatters:
|
||||
generated: lax
|
||||
paths:
|
||||
- node_modules
|
||||
- .venv
|
||||
- public
|
||||
- web_src
|
||||
- third_party$
|
||||
|
||||
403
CHANGELOG.md
403
CHANGELOG.md
@@ -4,394 +4,7 @@ This changelog goes through the changes that have been made in each release
|
||||
without substantial changes to our git log; to see the highlights of what has
|
||||
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
|
||||
## [1.25.2](https://github.com/go-gitea/gitea/releases/tag/1.25.2) - 2025-11-23
|
||||
|
||||
* SECURITY
|
||||
* Upgrade golang.org/x/crypto to 0.45.0 (#35985) (#35988)
|
||||
* Fix various permission & login related bugs (#36002) (#36004)
|
||||
* ENHANCEMENTS
|
||||
* Display source code downloads last for release attachments (#35897) (#35903)
|
||||
* Change project default column icon to 'star' (#35967) (#35979)
|
||||
* BUGFIXES
|
||||
* Allow empty commit when merging pull request with squash style (#35989) (#36003)
|
||||
* Fix container push tag overwriting (#35936) (#35954)
|
||||
* Fix corrupted external render content (#35946) and upgrade golang.org/x packages (#35950)
|
||||
* Limit reading bytes instead of ReadAll (#35928) (#35934)
|
||||
* Use correct form field for allowed force push users in branch protection API (#35894) (#35908)
|
||||
* Fix team member access check (#35899) (#35905)
|
||||
* Fix conda null depend issue (#35900) (#35902)
|
||||
* Set the dates to now when not specified by the caller (#35861) (#35874)
|
||||
* Fix gogit ListEntriesRecursiveWithSize (#35862)
|
||||
* Misc CSS fixes (#35888) (#35981)
|
||||
* Don't show unnecessary error message to end users for DeleteBranchAfterMerge (#35937) (#35941)
|
||||
* Load jQuery as early as possible to support custom scripts (#35926) (#35929)
|
||||
* Allow to display embed images/pdfs when SERVE_DIRECT was enabled on MinIO storage (#35882) (#35917)
|
||||
* Make OAuth2 issuer configurable (#35915) (#35916)
|
||||
* Fix #35763: Add proper page title for project pages (#35773) (#35909)
|
||||
* Fix avatar upload error handling (#35887) (#35890)
|
||||
* Contribution heatmap improvements (#35876) (#35880)
|
||||
* Remove padding override on `.ui .sha.label` (#35864) (#35873)
|
||||
* Fix pull description code label background (#35865) (#35870)
|
||||
|
||||
## [1.25.1](https://github.com/go-gitea/gitea/releases/tag/v1.25.1) - 2025-11-03
|
||||
|
||||
* BUGFIXES
|
||||
* Make ACME email optional (#35849) #35857
|
||||
* Add a doctor command to fix inconsistent run status (#35840) (#35845)
|
||||
* Remove wrong code (#35846)
|
||||
* Fix viewed files number is not right if not all files loaded (#35821) (#35844)
|
||||
* Fix incorrect pull request counter (#35819) (#35841)
|
||||
* Upgrade go mail to 0.7.2 and fix the bug (#35833) (#35837)
|
||||
* Revert gomail to v0.7.0 to fix sending mail failed (#35816) (#35824)
|
||||
* Fix clone mixed bug (#35810) (#35822)
|
||||
* Fix cli "Before" handling (#35797) (#35808)
|
||||
* Improve and fix markup code preview rendering (#35777) (#35787)
|
||||
* Fix actions rerun bug (#35783) (#35784)
|
||||
* Fix actions schedule update issue (#35767) (#35774)
|
||||
* Fix circular spin animation direction (#35785) (#35823)
|
||||
* Fix file extension on gogs.png (#35793) (#35799)
|
||||
* Add pnpm to Snapcraft (#35778)
|
||||
|
||||
## [1.25.0](https://github.com/go-gitea/gitea/releases/tag/v1.25.0) - 2025-10-30
|
||||
|
||||
* BREAKING
|
||||
* Return 201 Created for CreateVariable API responses (#34517)
|
||||
* Add label 'state' to metric 'gitea_users' (#34326)
|
||||
* SECURITY
|
||||
* Upgrade security public key (#34956)
|
||||
* Also include all security fixes in 1.24.x after 1.25.0-rc0
|
||||
* FEATURES
|
||||
* Stream repo zip/tar.gz/bundle achives by default (#35487)
|
||||
* Use configurable remote name for git commands (#35172)
|
||||
* Send email on Workflow Run Success/Failure (#34982)
|
||||
* Refactor OpenIDConnect to support SSH/FullName sync (#34978)
|
||||
* Refactor repo contents API and add "contents-ext" API (#34822)
|
||||
* Add support for 3D/CAD file formats preview (#34794)
|
||||
* Improve instance wide ssh commit signing (#34341)
|
||||
* Edit file workflow for creating a fork and proposing changes (#34240)
|
||||
* Follow file symlinks in the UI to their target (#28835)
|
||||
* Allow renaming/moving binary/LFS files in the UI (#34350)
|
||||
* PERFORMANCE
|
||||
* Improve the performance when detecting the file editable (#34653)
|
||||
* ENHANCEMENTS
|
||||
* Enable more markdown paste features in textarea editor (#35494)
|
||||
* Don't store repo archives on `gitea dump` (#35467)
|
||||
* Always return the relevant status information, even if no status exists. (#35335)
|
||||
* Add start time on perf trace because it seems some steps haven't been recorded. (#35282)
|
||||
* Remove deprecated auth sources (#35272)
|
||||
* When sorting issues by nearest due date, issues without due date should be sorted ascending (#35267)
|
||||
* Disable field count validation of CSV viewer (#35228)
|
||||
* Add `has_code` to repository REST API (#35214)
|
||||
* Display pull request in merged commit view (#35202)
|
||||
* Support Basic Authentication for archive downloads (#35087)
|
||||
* Add hover background to table rows in user and repo admin page (#35072)
|
||||
* Partially refresh notifications list (#35010)
|
||||
* Also display "recently pushed branch" alert on PR view (#35001)
|
||||
* Refactor time tracker UI (#34983)
|
||||
* Improve CLI commands (#34973)
|
||||
* Improve project & label color picker and image scroll (#34971)
|
||||
* Improve NuGet API Parity (#21291) (#34940)
|
||||
* Support getting last commit message using contents-ext API (#34904)
|
||||
* Adds title on branch commit counts (#34869)
|
||||
* Add "Cancel workflow run" button to Actions list page (#34817)
|
||||
* Improve img lazy loading (#34804)
|
||||
* Forks repository list page follow other repositories page (#34784)
|
||||
* Add ff_only parameter to POST /repos/{owner}/{repo}/merge-upstream (#34770)
|
||||
* Rework delete org and rename org UI (#34762)
|
||||
* Improve nuget/rubygems package registries (#34741)
|
||||
* Add repo file tree item link behavior (#34730)
|
||||
* Add issue delete notifier (#34592)
|
||||
* Improve Actions list (#34530)
|
||||
* Add a default tab on repo header when migrating (#34503)
|
||||
* Add post-installation redirect based on admin account status (#34493)
|
||||
* Trigger 'unlabeled' event when label is Deleted from PR (#34316)
|
||||
* Support annotated tags when using create release API (#31840)
|
||||
* Use lfs label for lfs file rather than a long description (#34363)
|
||||
* Add "View workflow file" to Actions list page (#34538)
|
||||
* Move organization's visibility change to danger zone. (#34814)
|
||||
* Don't block site admin's operation if SECRET_KEY is lost (#35721)
|
||||
* Make restricted users can access public repositories (#35693)
|
||||
* The status icon of the Action step is consistent with GitHub (#35618) #35621
|
||||
* BUGFIXES
|
||||
* Update tab title when navigating file tree (#35757) #35772
|
||||
* Fix "ref-issue" handling in markup (#35739) #35771
|
||||
* Fix webhook to prevent tag events from bypassing branch filters targets (#35567) #35577
|
||||
* Fix markup init after issue comment editing (#35536) #35537
|
||||
* Fix creating pull request failure when the target branch name is the same as some tag (#35552) #35582
|
||||
* Fix auto-expand and auto-scroll for actions logs (#35570) (#35583) #35586
|
||||
* Use inputs context when parsing workflows (#35590) #35595
|
||||
* Fix diffpatch API endpoint (#35610) #35613
|
||||
* Creating push comments before invoke pull request checking (#35647) #35668
|
||||
* Fix missing Close when error occurs and abused connection pool (#35658) #35670
|
||||
* Fix build (#35674)
|
||||
* Use LFS object size instead of blob size when viewing a LFS file (#35679)
|
||||
* Fix workflow run event status while rerunning a failed job (#35689)
|
||||
* Avoid emoji mismatch and allow to only enable chosen emojis (#35692)
|
||||
* Refactor legacy code, fix LFS auth bypass, fix symlink bypass (#35708)
|
||||
* Fix various trivial problems (#35714)
|
||||
* Fix attachment file size limit in server backend (#35519)
|
||||
* Honor delete branch on merge repo setting when using merge API (#35488)
|
||||
* Fix external render, make iframe render work (#35727, #35730)
|
||||
* Upgrade go mail to 0.7.2 (#35748)
|
||||
* Revert #18491, fix oauth2 client link account (#35745)
|
||||
* Fix different behavior in status check pattern matching with double stars (#35474)
|
||||
* Fix overflow in notifications list (#35446)
|
||||
* Fix package link setting can only list limited repositories (#35394)
|
||||
* Extend comment treepath length (#35389)
|
||||
* Fix font-size in inline code comment preview (#35209)
|
||||
* Move git config/remote to gitrepo package and add global lock to resolve possible conflict when updating repository git config file (#35151)
|
||||
* Change some columns from text to longtext and fix column wrong type caused by xorm (#35141)
|
||||
* Redirect to a presigned URL of HEAD for HEAD requests (#35088)
|
||||
* Fix git commit committer parsing and add some tests (#35007)
|
||||
* Fix OCI manifest parser (#34797)
|
||||
* Refactor FindOrgOptions to use enum instead of bool, fix membership visibility (#34629)
|
||||
* Fix notification count positioning for variable-width elements (#34597)
|
||||
* Keeping consistent between UI and API about combined commit status state and fix some bugs (#34562)
|
||||
* Fix possible panic (#34508)
|
||||
* Fix autofocus behavior (#34397)
|
||||
* Fix Actions API (#35204)
|
||||
* Fix ListWorkflowRuns OpenAPI response model. (#35026)
|
||||
* Small fix in Pull Requests page (#34612)
|
||||
* Fix http auth header parsing (#34936)
|
||||
* Fix modal + form abuse (#34921)
|
||||
* Fix PR toggle WIP (#34920)
|
||||
* Fix log fmt (#34810)
|
||||
* Replace stopwatch toggle with explicit start/stop actions (#34818)
|
||||
* Fix some package registry problems (#34759)
|
||||
* Fix RPM package download routing & missing package version count (#34909)
|
||||
* Fix repo search input height (#34330)
|
||||
* Fix "The sidebar of the repository file list does not have a fixed height #34298" (#34321)
|
||||
* Fix minor typos in two files #HSFDPMUW (#34944)
|
||||
* Fix actions skipped commit status indicator (#34507)
|
||||
* Fix job status aggregation logic (#35000)
|
||||
* Fix broken OneDev migration caused by various REST API changes in OneDev 7.8.0 and later (#35216)
|
||||
* Fix typo in oauth2_full_name_claim_name string (#35199)
|
||||
* Fix typo in locale_en-US.ini (#35196)
|
||||
* API
|
||||
* Exposing TimeEstimate field in the API (#35475)
|
||||
* UpdateBranch API supports renaming a branch (#35374)
|
||||
* Add `owner` and `parent` fields clarification to docs (#35023)
|
||||
* Improve OAuth2 provider (correct Issuer, respect ENABLED) (#34966)
|
||||
* Add a `login`/`login-name`/`username` disambiguation to affected endpoint parameters and response/request models (#34901)
|
||||
* Do not mutate incoming options to SearchRepositoryByName (#34553)
|
||||
* Do not mutate incoming options to RenderUserSearch and SearchUsers (#34544)
|
||||
* Export repo's manual merge settings (#34502)
|
||||
* Add date range filtering to commit retrieval endpoints (#34497)
|
||||
* Add endpoint deleting workflow run (#34337)
|
||||
* Add workflow_run api + webhook (#33964)
|
||||
* REFACTOR
|
||||
* Move updateref and removeref to gitrepo and remove unnecessary open repository (#35511)
|
||||
* Remove unused param `doer` (#34545)
|
||||
* Split GetLatestCommitStatus as two functions (#34535)
|
||||
* Use gitrepo.SetDefaultBranch when set default branch of wiki repository (#33911)
|
||||
* Refactor editor (#34780)
|
||||
* Refactor packages (#34777)
|
||||
* Refactor container package (#34877)
|
||||
* Refactor "change file" API (#34855)
|
||||
* Rename pull request GetGitRefName to GetGitHeadRefName to prepare introducing GetGitMergeRefName (#35093)
|
||||
* Move git command to git/gitcmd (#35483)
|
||||
* Use db.WithTx/WithTx2 instead of TxContext when possible (#35428)
|
||||
* Support Node.js 22.6 with type stripping (#35427)
|
||||
* Migrate tools and configs to typescript, require node.js >= 22.18.0 (#35421)
|
||||
* Check user and repo for redirects when using git via SSH transport (#35416)
|
||||
* Remove the duplicated function GetTags (#35375)
|
||||
* Refactor to use reflect.TypeFor (#35370)
|
||||
* Deleting branch could delete broken branch which has database record but git branch is missing (#35360)
|
||||
* Exit with success when already up to date (#35312)
|
||||
* Split admin config settings templates to make it maintain easier (#35294)
|
||||
* A small refactor to use context in the service layer (#35179)
|
||||
* Refactor and update mail templates (#35150)
|
||||
* Use db.WithTx/WithTx2 instead of TxContext when possible (#35130)
|
||||
* Align `issue-title-buttons` with `list-header` (#35018)
|
||||
* Add Notifications section in User Settings (#35008)
|
||||
* Tweak placement of diff file menu (#34999)
|
||||
* Refactor mail template and support preview (#34990)
|
||||
* Rerun job only when run is done (#34970)
|
||||
* Merge index.js (#34963)
|
||||
* Refactor "delete-button" to "link-action" (#34962)
|
||||
* Refactor webhook and fix feishu/lark secret (#34961)
|
||||
* Exclude devtest.ts from tailwindcss (#34935)
|
||||
* Refactor head navbar icons (#34922)
|
||||
* Improve html escape (#34911)
|
||||
* Improve tags list page (#34898)
|
||||
* Improve `labels-list` rendering (#34846)
|
||||
* Remove unused variable HUGO_VERSION (#34840)
|
||||
* Correct migration tab name (#34826)
|
||||
* Refactor template helper (#34819)
|
||||
* Use `shallowRef` instead of `ref` in `.vue` files where possible (#34813)
|
||||
* Use standalone function to update repository cols (#34811)
|
||||
* Refactor wiki (#34805)
|
||||
* Remove unnecessary duplicate code (#34733)
|
||||
* Refactor embedded assets and drop unnecessary dependencies (#34692)
|
||||
* Update x/crypto package and make builtin SSH use default parameters (#34667)
|
||||
* Add `--color-logo`, matching the logo's primary color (#34639)
|
||||
* Add openssh-keygen to rootless image (#34625)
|
||||
* Replace update repository function in some places (#34566)
|
||||
* Change "rejected" to "changes requested" in 3rd party PR review notification (#34481)
|
||||
* Remove legacy template helper functions (#34426)
|
||||
* Use run-name and evaluate workflow variables (#34301)
|
||||
* Move HasWiki to repository service package (#33912)
|
||||
* Move some functions from package git to gitrepo (#33910)
|
||||
* TESTING
|
||||
* Add webhook test for push event (#34442)
|
||||
* Add a webhook push test for dev branch (#34421)
|
||||
* Add migrations tests (#34456) (#34498)
|
||||
* STYLE
|
||||
* Enforce explanation for necessary nolints and fix bugs (#34883)
|
||||
* Fix remaining issues after `gopls modernize` formatting (#34771)
|
||||
* Update gofumpt, add go.mod ignore directive (#35434)
|
||||
* Enforce nolint scope (#34851)
|
||||
* Enable gocritic `equalFold` and fix issues (#34952)
|
||||
* Run `gopls modernize` on codebase (#34751)
|
||||
* Upgrade `gopls` to v0.19.0, add `make fix` (#34772)
|
||||
* BUILD
|
||||
* bump archives&rar dep (#35637) #35638
|
||||
* Use github.com/mholt/archives replace github.com/mholt/archiver (#35390)
|
||||
* Update JS and PY dependencies (#35444)
|
||||
* Upgrade devcontainer go version to 1.24.6 (#35298)
|
||||
* Upgrade golang to 1.25.1 and add descriptions for the swagger structs' fields (#35418)
|
||||
* Update JS and PY deps (#35191)
|
||||
* Update JS and PY dependencies (#34391)
|
||||
* Update go tool dependencies (#34845)
|
||||
* Update `uint8-to-base64`, remove type stub (#34844)
|
||||
* Switch to `@resvg/resvg-wasm` for `generate-images` (#35415)
|
||||
* Switch to pnpm (#35274)
|
||||
* Update chroma to v2.20.0 (#35220)
|
||||
* Migrate to urfave v3 (#34510)
|
||||
* Update JS deps, regenerate SVGs (#34640)
|
||||
* Upgrade dependencies (#35384)
|
||||
* Bump `@github/relative-time-element` to v4.4.8 (#34413)
|
||||
* Update JS dependencies (#34951)
|
||||
* Upgrade orgmode to v1.8.0 (#34721)
|
||||
* Raise minimum Node.js version to 20, test on 24 (#34713)
|
||||
* Update JS deps (#34701)
|
||||
* Upgrade htmx to 2.0.6 (#34887)
|
||||
* Update eslint to v9 (#35485)
|
||||
* Update js dependencies (#35429)
|
||||
* Clean up npm dependencies (#35508)
|
||||
* Clean up npm dependencies (#35484)
|
||||
* Bump setup-node to v5 (#35448)
|
||||
* MISC
|
||||
* Add gitignore rules to exclude LLM instruction files (#35076)
|
||||
* Gitignore: Visual Studio settings folder (#34375)
|
||||
* Improve language in en-US locale strings (#35124)
|
||||
* Fixed all grammatical errors in locale_en-US.ini (#35053)
|
||||
* Docs/fix typo and grammar in CONTRIBUTING.md (#35024)
|
||||
* Improve english grammar and readability in locale_en-US.ini (#35017)
|
||||
|
||||
## [1.24.7](https://github.com/go-gitea/gitea/releases/tag/v1.24.7) - 2025-10-24
|
||||
|
||||
* SECURITY
|
||||
* Refactor legacy code (#35708) (#35713)
|
||||
* Fixing issue #35530: Password Leak in Log Messages (#35584) (#35665)
|
||||
* Fix a bug missed return (#35655) (#35671)
|
||||
* BUGFIXES
|
||||
* Fix inputing review comment will remove reviewer (#35591) (#35664)
|
||||
* TESTING
|
||||
* Mock external service in hcaptcha TestCaptcha (#35604) (#35663)
|
||||
* Fix build (#35669)
|
||||
|
||||
## [1.24.6](https://github.com/go-gitea/gitea/releases/tag/v1.24.6) - 2025-09-10
|
||||
|
||||
* SECURITY
|
||||
* Upgrade xz to v0.5.15 (#35385)
|
||||
* BUGFIXES
|
||||
* Fix a compare page 404 bug when the pull request disabled (#35441) (#35453)
|
||||
* Fix bug when issue disabled, pull request number in the commit message cannot be redirected (#35420) (#35442)
|
||||
* Add author.name field to Swift Package Registry API response (#35410) (#35431)
|
||||
* Remove usernames when empty in discord webhook (#35412) (#35417)
|
||||
* Allow foreachref parser to grow its buffer (#35365) (#35376)
|
||||
* Allow deleting comment with content via API like web did (#35346) (#35354)
|
||||
* Fix atom/rss mixed error (#35345) (#35347)
|
||||
* Fix review request webhook bug (#35339)
|
||||
* Remove duplicate html IDs (#35210) (#35325)
|
||||
* Fix LFS range size header response (#35277) (#35293)
|
||||
* Fix GitHub release assets URL validation (#35287) (#35290)
|
||||
* Fix token lifetime, closes #35230 (#35271) (#35281)
|
||||
* Fix push commits comments when changing the pull request target branch (#35386) (#35443)
|
||||
|
||||
## [1.24.5](https://github.com/go-gitea/gitea/releases/tag/v1.24.5) - 2025-08-12
|
||||
|
||||
* BUGFIXES
|
||||
* Fix a bug where lfs gc never worked. (#35198) (#35255)
|
||||
* Reload issue when sending webhook to make num comments is right. (#35243) (#35248)
|
||||
* Fix bug when review pull request commits (#35192) (#35246)
|
||||
* MISC
|
||||
* Vertically center "Show Resolved" (#35211) (#35218)
|
||||
|
||||
## [1.24.4](https://github.com/go-gitea/gitea/releases/tag/v1.24.4) - 2025-08-03
|
||||
|
||||
* BUGFIXES
|
||||
* Fix various bugs (1.24) (#35186)
|
||||
* Fix migrate input box bug (#35166) (#35171)
|
||||
* Only hide dropzone when no files have been uploaded (#35156) (#35167)
|
||||
* Fix review comment/dimiss comment x reference can be refereced back (#35094) (#35099)
|
||||
* Fix submodule nil check (#35096) (#35098)
|
||||
* MISC
|
||||
* Don't use full-file highlight when there is a git diff textconv (#35114) (#35119)
|
||||
* Increase gap on latest commit (#35104) (#35113)
|
||||
|
||||
## [1.24.3](https://github.com/go-gitea/gitea/releases/tag/v1.24.3) - 2025-07-15
|
||||
|
||||
* BUGFIXES
|
||||
* Fix form property assignment edge case (#35073) (#35078)
|
||||
* Improve submodule relative path handling (#35056) (#35075)
|
||||
* Fix incorrect comment diff hunk parsing, fix github asset ID nil panic (#35046) (#35055)
|
||||
* Fix updating user visibility (#35036) (#35044)
|
||||
* Support base64-encoded agit push options (#35037) (#35041)
|
||||
* Make submodule link work with relative path (#35034) (#35038)
|
||||
* Fix bug when displaying git user avatar in commits list (#35006)
|
||||
* Fix API response for swagger spec (#35029)
|
||||
* Start automerge check again after the conflict check and the schedule (#34988) (#35002)
|
||||
* Fix the response format for actions/workflows (#35009) (#35016)
|
||||
* Fix repo settings and protocol log problems (#35012) (#35013)
|
||||
* Fix project images scroll (#34971) (#34972)
|
||||
* Mark old reviews as stale on agit pr updates (#34933) (#34965)
|
||||
* Fix git graph page (#34948) (#34949)
|
||||
* Don't send trigger for a pending review's comment create/update/delete (#34928) (#34939)
|
||||
* Fix some log and UI problems (#34863) (#34868)
|
||||
* Fix archive API (#34853) (#34857)
|
||||
* Ignore force pushes for changed files in a PR review (#34837) (#34843)
|
||||
* Fix SSH LFS timeout (#34838) (#34842)
|
||||
* Fix team permissions (#34827) (#34836)
|
||||
* Fix job status aggregation logic (#34823) (#34835)
|
||||
* Fix issue filter (#34914) (#34915)
|
||||
* Fix typo in pull request merge warning message text (#34899) (#34903)
|
||||
* Support the open-icon of folder (#34168) (#34896)
|
||||
* Optimize flex layout of release attachment area (#34885) (#34886)
|
||||
* Fix the issue of abnormal interface when there is no issue-item on the project page (#34791) (#34880)
|
||||
* Skip updating timestamp when sync branch (#34875)
|
||||
* Fix required contexts and commit status matching bug (#34815) (#34829)
|
||||
|
||||
## [1.24.2](https://github.com/go-gitea/gitea/releases/tag/v1.24.2) - 2025-06-20
|
||||
|
||||
* BUGFIXES
|
||||
* Fix container range bug (#34795) (#34796)
|
||||
* Upgrade chi to v5.2.2 (#34798) (#34799)
|
||||
* BUILD
|
||||
* Bump poetry feature to new url for dev container (#34787) (#34790)
|
||||
|
||||
## [1.24.1](https://github.com/go-gitea/gitea/releases/tag/v1.24.1) - 2025-06-18
|
||||
|
||||
* ENHANCEMENTS
|
||||
* Improve alignment of commit status icon on commit page (#34750) (#34757)
|
||||
* Support title and body query parameters for new PRs (#34537) (#34752)
|
||||
|
||||
* BUGFIXES
|
||||
* When using rules to delete packages, remove unclean bugs (#34632) (#34761)
|
||||
* Fix ghost user in feeds when pushing in an actions, it should be gitea-actions (#34703) (#34756)
|
||||
* Prevent double markdown link brackets when pasting URL (#34745) (#34748)
|
||||
* Prevent duplicate form submissions when creating forks (#34714) (#34735)
|
||||
* Fix markdown wrap (#34697) (#34702)
|
||||
* Fix pull requests API convert panic when head repository is deleted. (#34685) (#34687)
|
||||
* Fix commit message rendering and some UI problems (#34680) (#34683)
|
||||
* Fix container range bug (#34725) (#34732)
|
||||
* Fix incorrect cli default values (#34765) (#34766)
|
||||
* Fix dropdown filter (#34708) (#34711)
|
||||
* Hide href attribute of a tag if there is no target_url (#34556) (#34684)
|
||||
* Fix tag target (#34781) #34783
|
||||
|
||||
## [1.24.0](https://github.com/go-gitea/gitea/releases/tag/v1.24.0) - 2025-05-26
|
||||
## [1.24.0](https://github.com/go-gitea/gitea/releases/tag/1.24.0) - 2025-05-26
|
||||
|
||||
* BREAKING
|
||||
* Make Gitea always use its internal config, ignore `/etc/gitconfig` (#33076)
|
||||
@@ -761,7 +374,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
* Bump x/net (#32896) (#32900)
|
||||
* Only activity tab needs heatmap data loading (#34652)
|
||||
|
||||
## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/v1.23.8) - 2025-05-11
|
||||
## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/1.23.8) - 2025-05-11
|
||||
|
||||
* SECURITY
|
||||
* Fix a bug when uploading file via lfs ssh command (#34408) (#34411)
|
||||
@@ -788,7 +401,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
* Bump go version in go.mod (#34160)
|
||||
* remove hardcoded 'code' string in clone_panel.tmpl (#34153) (#34158)
|
||||
|
||||
## [1.23.7](https://github.com/go-gitea/gitea/releases/tag/v1.23.7) - 2025-04-07
|
||||
## [1.23.7](https://github.com/go-gitea/gitea/releases/tag/1.23.7) - 2025-04-07
|
||||
|
||||
* Enhancements
|
||||
* Add a config option to block "expensive" pages for anonymous users (#34024) (#34071)
|
||||
@@ -886,7 +499,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
* BUGFIXES
|
||||
* Fix a bug caused by status webhook template #33512
|
||||
|
||||
## [1.23.2](https://github.com/go-gitea/gitea/releases/tag/v1.23.2) - 2025-02-04
|
||||
## [1.23.2](https://github.com/go-gitea/gitea/releases/tag/1.23.2) - 2025-02-04
|
||||
|
||||
* BREAKING
|
||||
* Add tests for webhook and fix some webhook bugs (#33396) (#33442)
|
||||
@@ -3416,7 +3029,7 @@ Key highlights of this release encompass significant changes categorized under `
|
||||
* Improve decryption failure message (#24573) (#24575)
|
||||
* Makefile: Use portable !, not GNUish -not, with find(1). (#24565) (#24572)
|
||||
|
||||
## [1.19.3](https://github.com/go-gitea/gitea/releases/tag/v1.19.3) - 2023-05-03
|
||||
## [1.19.3](https://github.com/go-gitea/gitea/releases/tag/1.19.3) - 2023-05-03
|
||||
|
||||
* SECURITY
|
||||
* Use golang 1.20.4 to fix CVE-2023-24539, CVE-2023-24540, and CVE-2023-29400
|
||||
@@ -3429,7 +3042,7 @@ Key highlights of this release encompass significant changes categorized under `
|
||||
* Fix incorrect CurrentUser check for docker rootless (#24435)
|
||||
* Getting the tag list does not require being signed in (#24413) (#24416)
|
||||
|
||||
## [1.19.2](https://github.com/go-gitea/gitea/releases/tag/v1.19.2) - 2023-04-26
|
||||
## [1.19.2](https://github.com/go-gitea/gitea/releases/tag/1.19.2) - 2023-04-26
|
||||
|
||||
* SECURITY
|
||||
* Require repo scope for PATs for private repos and basic authentication (#24362) (#24364)
|
||||
@@ -3928,7 +3541,7 @@ Key highlights of this release encompass significant changes categorized under `
|
||||
* Display attachments of review comment when comment content is blank (#23035) (#23046)
|
||||
* Return empty url for submodule tree entries (#23043) (#23048)
|
||||
|
||||
## [1.18.4](https://github.com/go-gitea/gitea/releases/tag/v1.18.4) - 2023-02-20
|
||||
## [1.18.4](https://github.com/go-gitea/gitea/releases/tag/1.18.4) - 2023-02-20
|
||||
|
||||
* SECURITY
|
||||
* Provide the ability to set password hash algorithm parameters (#22942) (#22943)
|
||||
@@ -4355,7 +3968,7 @@ Key highlights of this release encompass significant changes categorized under `
|
||||
* Fix the mode of custom dir to 0700 in docker-rootless (#20861) (#20867)
|
||||
* Fix UI mis-align for PR commit history (#20845) (#20859)
|
||||
|
||||
## [1.17.1](https://github.com/go-gitea/gitea/releases/tag/v1.17.1) - 2022-08-17
|
||||
## [1.17.1](https://github.com/go-gitea/gitea/releases/tag/1.17.1) - 2022-08-17
|
||||
|
||||
* SECURITY
|
||||
* Correctly escape within tribute.js (#20831) (#20832)
|
||||
|
||||
@@ -166,19 +166,19 @@ Here's how to run the test suite:
|
||||
|
||||
- code lint
|
||||
|
||||
| | |
|
||||
| :-------------------- | :---------------------------------------------------------------- |
|
||||
| | |
|
||||
| :-------------------- | :--------------------------------------------------------------------------- |
|
||||
|``make lint`` | lint everything (not needed if you only change the front- **or** backend) |
|
||||
|``make lint-frontend`` | lint frontend files |
|
||||
|``make lint-backend`` | lint backend files |
|
||||
|``make lint-frontend`` | lint frontend files |
|
||||
|``make lint-backend`` | lint backend files |
|
||||
|
||||
- run tests (we suggest running them on Linux)
|
||||
|
||||
| Command | Action | |
|
||||
| :------------------------------------- | :----------------------------------------------- | ------------ |
|
||||
|``make test[\#SpecificTestName]`` | run unit test(s) | |
|
||||
|``make test-sqlite[\#SpecificTestName]``| run [integration](tests/integration) test(s) for SQLite |[More details](tests/integration/README.md) |
|
||||
|``make test-e2e-sqlite[\#SpecificTestName]``| run [end-to-end](tests/e2e) test(s) for SQLite |[More details](tests/e2e/README.md) |
|
||||
| Command | Action | |
|
||||
| :------------------------------------------ | :------------------------------------------------------- | ------------------------------------------- |
|
||||
|``make test[\#SpecificTestName]`` | run unit test(s) | |
|
||||
|``make test-sqlite[\#SpecificTestName]`` | run [integration](tests/integration) test(s) for SQLite | [More details](tests/integration/README.md) |
|
||||
|``make test-e2e-sqlite[\#SpecificTestName]`` | run [end-to-end](tests/e2e) test(s) for SQLite | [More details](tests/e2e/README.md) |
|
||||
|
||||
## Translation
|
||||
|
||||
|
||||
47
Dockerfile
47
Dockerfile
@@ -1,8 +1,8 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Build stage
|
||||
FROM docker.io/library/golang:1.25-alpine3.22 AS build-env
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY=${GOPROXY:-direct}
|
||||
ARG GOPROXY=direct
|
||||
|
||||
ARG GITEA_VERSION
|
||||
ARG TAGS="sqlite sqlite_unlock_notify"
|
||||
@@ -14,35 +14,32 @@ RUN apk --no-cache add \
|
||||
build-base \
|
||||
git \
|
||||
nodejs \
|
||||
npm \
|
||||
&& npm install -g pnpm@10 \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
pnpm
|
||||
|
||||
# Setup repo
|
||||
COPY . ${GOPATH}/src/code.gitea.io/gitea
|
||||
WORKDIR ${GOPATH}/src/code.gitea.io/gitea
|
||||
# Use COPY but not "mount" because some directories like "node_modules" contain platform-depended contents and these directories need to be ignored.
|
||||
# ".git" directory will be mounted later separately for getting version data.
|
||||
# TODO: in the future, maybe we can pre-build the frontend assets on one platform and share them for different platforms, the benefit is that it won't be affected by webpack plugin compatibility problems, then the working directory can be fully mounted and the COPY is not needed.
|
||||
COPY --exclude=.git/ . .
|
||||
|
||||
# Checkout version if set
|
||||
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
|
||||
&& make clean-all build
|
||||
# Build gitea, .git mount is required for version data
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target="/root/.cache/go-build" \
|
||||
--mount=type=cache,target=/root/.local/share/pnpm/store \
|
||||
--mount=type=bind,source=".git/",target=".git/" \
|
||||
make
|
||||
|
||||
# Begin env-to-ini build
|
||||
RUN go build contrib/environment-to-ini/environment-to-ini.go
|
||||
|
||||
# Copy local files
|
||||
COPY docker/root /tmp/local
|
||||
|
||||
# Set permissions
|
||||
# Set permissions for builds that made under windows which strips the executable bit from file
|
||||
RUN chmod 755 /tmp/local/usr/bin/entrypoint \
|
||||
/tmp/local/usr/local/bin/gitea \
|
||||
/tmp/local/usr/local/bin/* \
|
||||
/tmp/local/etc/s6/gitea/* \
|
||||
/tmp/local/etc/s6/openssh/* \
|
||||
/tmp/local/etc/s6/.s6-svscan/* \
|
||||
/go/src/code.gitea.io/gitea/gitea \
|
||||
/go/src/code.gitea.io/gitea/environment-to-ini
|
||||
/go/src/code.gitea.io/gitea/gitea
|
||||
|
||||
FROM docker.io/library/alpine:3.22
|
||||
LABEL maintainer="maintainers@gitea.io"
|
||||
FROM docker.io/library/alpine:3.22 AS gitea
|
||||
|
||||
EXPOSE 22 3000
|
||||
|
||||
@@ -57,8 +54,7 @@ RUN apk --no-cache add \
|
||||
s6 \
|
||||
sqlite \
|
||||
su-exec \
|
||||
gnupg \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
gnupg
|
||||
|
||||
RUN addgroup \
|
||||
-S -g 1000 \
|
||||
@@ -72,6 +68,9 @@ RUN addgroup \
|
||||
git && \
|
||||
echo "git:*" | chpasswd -e
|
||||
|
||||
COPY --from=build-env /tmp/local /
|
||||
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
|
||||
|
||||
ENV USER=git
|
||||
ENV GITEA_CUSTOM=/data/gitea
|
||||
|
||||
@@ -79,7 +78,3 @@ VOLUME ["/data"]
|
||||
|
||||
ENTRYPOINT ["/usr/bin/entrypoint"]
|
||||
CMD ["/usr/bin/s6-svscan", "/etc/s6"]
|
||||
|
||||
COPY --from=build-env /tmp/local /
|
||||
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
|
||||
COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
|
||||
|
||||
@@ -1,46 +1,39 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Build stage
|
||||
FROM docker.io/library/golang:1.25-alpine3.22 AS build-env
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY=${GOPROXY:-direct}
|
||||
ARG GOPROXY=direct
|
||||
|
||||
ARG GITEA_VERSION
|
||||
ARG TAGS="sqlite sqlite_unlock_notify"
|
||||
ENV TAGS="bindata timetzdata $TAGS"
|
||||
ARG CGO_EXTRA_CFLAGS
|
||||
|
||||
#Build deps
|
||||
# Build deps
|
||||
RUN apk --no-cache add \
|
||||
build-base \
|
||||
git \
|
||||
nodejs \
|
||||
npm \
|
||||
&& npm install -g pnpm@10 \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
pnpm
|
||||
|
||||
# Setup repo
|
||||
COPY . ${GOPATH}/src/code.gitea.io/gitea
|
||||
WORKDIR ${GOPATH}/src/code.gitea.io/gitea
|
||||
# See the comments in Dockerfile
|
||||
COPY --exclude=.git/ . .
|
||||
|
||||
# Checkout version if set
|
||||
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
|
||||
&& make clean-all build
|
||||
# Build gitea, .git mount is required for version data
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target="/root/.cache/go-build" \
|
||||
--mount=type=cache,target=/root/.local/share/pnpm/store \
|
||||
--mount=type=bind,source=".git/",target=".git/" \
|
||||
make
|
||||
|
||||
# Begin env-to-ini build
|
||||
RUN go build contrib/environment-to-ini/environment-to-ini.go
|
||||
|
||||
# Copy local files
|
||||
COPY docker/rootless /tmp/local
|
||||
|
||||
# Set permissions
|
||||
RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
|
||||
/tmp/local/usr/local/bin/docker-setup.sh \
|
||||
/tmp/local/usr/local/bin/gitea \
|
||||
/go/src/code.gitea.io/gitea/gitea \
|
||||
/go/src/code.gitea.io/gitea/environment-to-ini
|
||||
# Set permissions for builds that made under windows which strips the executable bit from file
|
||||
RUN chmod 755 /tmp/local/usr/local/bin/* \
|
||||
/go/src/code.gitea.io/gitea/gitea
|
||||
|
||||
FROM docker.io/library/alpine:3.22
|
||||
LABEL maintainer="maintainers@gitea.io"
|
||||
FROM docker.io/library/alpine:3.22 AS gitea-rootless
|
||||
|
||||
EXPOSE 2222 3000
|
||||
|
||||
@@ -52,8 +45,7 @@ RUN apk --no-cache add \
|
||||
git \
|
||||
curl \
|
||||
gnupg \
|
||||
openssh-keygen \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
openssh-keygen
|
||||
|
||||
RUN addgroup \
|
||||
-S -g 1000 \
|
||||
@@ -71,7 +63,6 @@ RUN chown git:git /var/lib/gitea /etc/gitea
|
||||
|
||||
COPY --from=build-env /tmp/local /
|
||||
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/environment-to-ini /usr/local/bin/environment-to-ini
|
||||
|
||||
# git:git
|
||||
USER 1000:1000
|
||||
|
||||
63
Makefile
63
Makefile
@@ -18,6 +18,10 @@ DIST := dist
|
||||
DIST_DIRS := $(DIST)/binaries $(DIST)/release
|
||||
IMPORT := code.gitea.io/gitea
|
||||
|
||||
# By default use go's 1.25 experimental json v2 library when building
|
||||
# TODO: remove when no longer experimental
|
||||
export GOEXPERIMENT ?= jsonv2
|
||||
|
||||
GO ?= go
|
||||
SHASUM ?= shasum -a 256
|
||||
HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes)
|
||||
@@ -27,17 +31,15 @@ XGO_VERSION := go-1.25.x
|
||||
|
||||
AIR_PACKAGE ?= github.com/air-verse/air@v1
|
||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3
|
||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.1
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.4.0
|
||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.2
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.7.0
|
||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15
|
||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.7.0
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@717e3cb29becaaf00e56953556c6d80f8a01b286
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1
|
||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
|
||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
|
||||
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.8
|
||||
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.20.0
|
||||
GOPLS_MODERNIZE_PACKAGE ?= golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@v0.20.0
|
||||
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.9
|
||||
|
||||
DOCKER_IMAGE ?= gitea/gitea
|
||||
DOCKER_TAG ?= latest
|
||||
@@ -159,12 +161,12 @@ TEST_TAGS ?= $(TAGS_SPLIT) sqlite sqlite_unlock_notify
|
||||
|
||||
TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR) $(GO_LICENSE_TMP_DIR)
|
||||
|
||||
GO_DIRS := build cmd models modules routers services tests
|
||||
GO_DIRS := build cmd models modules routers services tests tools
|
||||
WEB_DIRS := web_src/js web_src/css
|
||||
|
||||
ESLINT_FILES := web_src/js tools *.ts tests/e2e
|
||||
STYLELINT_FILES := web_src/css web_src/js/components/*.vue
|
||||
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.ini .github $(filter-out CHANGELOG.md, $(wildcard *.go *.md *.yml *.yaml *.toml)) $(filter-out tools/misspellings.csv, $(wildcard tools/*))
|
||||
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.ini .github $(filter-out CHANGELOG.md, $(wildcard *.go *.md *.yml *.yaml *.toml))
|
||||
EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.ini
|
||||
|
||||
GO_SOURCES := $(wildcard *.go)
|
||||
@@ -195,6 +197,10 @@ TEST_MSSQL_DBNAME ?= gitea
|
||||
TEST_MSSQL_USERNAME ?= sa
|
||||
TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1
|
||||
|
||||
# Include local Makefile
|
||||
# Makefile.local is listed in .gitignore
|
||||
sinclude Makefile.local
|
||||
|
||||
.PHONY: all
|
||||
all: build
|
||||
|
||||
@@ -254,7 +260,7 @@ clean: ## delete backend and integration files
|
||||
|
||||
.PHONY: fmt
|
||||
fmt: ## format the Go and template code
|
||||
@GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}'
|
||||
@GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run tools/code-batch-process.go gitea-fmt -w '{file-list}'
|
||||
$(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl'))
|
||||
@# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only
|
||||
@# whitespace before it
|
||||
@@ -272,19 +278,6 @@ fmt-check: fmt
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: fix
|
||||
fix: ## apply automated fixes to Go code
|
||||
$(GO) run $(GOPLS_MODERNIZE_PACKAGE) -fix ./...
|
||||
|
||||
.PHONY: fix-check
|
||||
fix-check: fix
|
||||
@diff=$$(git diff --color=always $(GO_SOURCES)); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo "Please run 'make fix' and commit the result:"; \
|
||||
printf "%s" "$${diff}"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: $(TAGS_EVIDENCE)
|
||||
$(TAGS_EVIDENCE):
|
||||
@mkdir -p $(MAKE_EVIDENCE_DIR)
|
||||
@@ -324,7 +317,7 @@ checks: checks-frontend checks-backend ## run various consistency checks
|
||||
checks-frontend: lockfile-check svg-check ## check frontend files
|
||||
|
||||
.PHONY: checks-backend
|
||||
checks-backend: tidy-check swagger-check fmt-check fix-check swagger-validate security-check ## check backend files
|
||||
checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check ## check backend files
|
||||
|
||||
.PHONY: lint
|
||||
lint: lint-frontend lint-backend lint-spell ## lint everything
|
||||
@@ -339,7 +332,7 @@ lint-frontend: lint-js lint-css ## lint frontend files
|
||||
lint-frontend-fix: lint-js-fix lint-css-fix ## lint frontend files and fix issues
|
||||
|
||||
.PHONY: lint-backend
|
||||
lint-backend: lint-go lint-go-gitea-vet lint-go-gopls lint-editorconfig ## lint backend files
|
||||
lint-backend: lint-go lint-go-gitea-vet lint-editorconfig ## lint backend files
|
||||
|
||||
.PHONY: lint-backend-fix
|
||||
lint-backend-fix: lint-go-fix lint-go-gitea-vet lint-editorconfig ## lint backend files and fix issues
|
||||
@@ -370,13 +363,17 @@ lint-swagger: node_modules ## lint swagger files
|
||||
lint-md: node_modules ## lint markdown files
|
||||
$(NODE_VARS) pnpm exec markdownlint *.md
|
||||
|
||||
.PHONY: lint-md-fix
|
||||
lint-md-fix: node_modules ## lint markdown files and fix issues
|
||||
$(NODE_VARS) pnpm exec markdownlint --fix *.md
|
||||
|
||||
.PHONY: lint-spell
|
||||
lint-spell: ## lint spelling
|
||||
@go run $(MISSPELL_PACKAGE) -dict tools/misspellings.csv -error $(SPELLCHECK_FILES)
|
||||
@go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -error $(SPELLCHECK_FILES)
|
||||
|
||||
.PHONY: lint-spell-fix
|
||||
lint-spell-fix: ## lint spelling and fix issues
|
||||
@go run $(MISSPELL_PACKAGE) -dict tools/misspellings.csv -w $(SPELLCHECK_FILES)
|
||||
@go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -w $(SPELLCHECK_FILES)
|
||||
|
||||
.PHONY: lint-go
|
||||
lint-go: ## lint go files
|
||||
@@ -396,13 +393,7 @@ lint-go-windows:
|
||||
.PHONY: lint-go-gitea-vet
|
||||
lint-go-gitea-vet: ## lint go files with gitea-vet
|
||||
@echo "Running gitea-vet..."
|
||||
@GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet
|
||||
@$(GO) vet -vettool=gitea-vet ./...
|
||||
|
||||
.PHONY: lint-go-gopls
|
||||
lint-go-gopls: ## lint go files with gopls
|
||||
@echo "Running gopls check..."
|
||||
@GO=$(GO) GOPLS_PACKAGE=$(GOPLS_PACKAGE) tools/lint-go-gopls.sh $(GO_SOURCES)
|
||||
@$(GO) vet -vettool="$(shell GOOS= GOARCH= go tool -n gitea-vet)" ./...
|
||||
|
||||
.PHONY: lint-editorconfig
|
||||
lint-editorconfig:
|
||||
@@ -468,7 +459,7 @@ test\#%:
|
||||
coverage:
|
||||
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' coverage.out > coverage-bodged.out
|
||||
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' integration.coverage.out > integration.coverage-bodged.out
|
||||
$(GO) run build/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all
|
||||
$(GO) run tools/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all
|
||||
|
||||
.PHONY: unit-test-coverage
|
||||
unit-test-coverage:
|
||||
@@ -766,7 +757,7 @@ generate-go: $(TAGS_PREREQ)
|
||||
|
||||
.PHONY: security-check
|
||||
security-check:
|
||||
go run $(GOVULNCHECK_PACKAGE) -show color ./...
|
||||
GOEXPERIMENT= go run $(GOVULNCHECK_PACKAGE) -show color ./...
|
||||
|
||||
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
|
||||
ifneq ($(and $(STATIC),$(findstring pam,$(TAGS))),)
|
||||
@@ -847,8 +838,6 @@ deps-tools: ## install tool dependencies
|
||||
$(GO) install $(GO_LICENSES_PACKAGE) & \
|
||||
$(GO) install $(GOVULNCHECK_PACKAGE) & \
|
||||
$(GO) install $(ACTIONLINT_PACKAGE) & \
|
||||
$(GO) install $(GOPLS_PACKAGE) & \
|
||||
$(GO) install $(GOPLS_MODERNIZE_PACKAGE) & \
|
||||
wait
|
||||
|
||||
node_modules: pnpm-lock.yaml
|
||||
|
||||
14
build.go
14
build.go
@@ -1,14 +0,0 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build vendor
|
||||
|
||||
package main
|
||||
|
||||
// Libraries that are included to vendor utilities used during Makefile build.
|
||||
// These libraries will not be included in a normal compilation.
|
||||
|
||||
import (
|
||||
// for vet
|
||||
_ "code.gitea.io/gitea-vet"
|
||||
)
|
||||
@@ -121,7 +121,7 @@ func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
|
||||
}
|
||||
log.Trace("Processing next %d repos of %d", len(repos), count)
|
||||
for _, repo := range repos {
|
||||
log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RepoPath())
|
||||
log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RelativePath())
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
|
||||
if err != nil {
|
||||
log.Warn("OpenRepository: %v", err)
|
||||
@@ -147,7 +147,7 @@ func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Trace(" repo %s releases synchronized to tags: from %d to %d",
|
||||
log.Trace("repo %s releases synchronized to tags: from %d to %d",
|
||||
repo.FullName(), oldnum, count)
|
||||
gitRepo.Close()
|
||||
}
|
||||
|
||||
@@ -151,6 +151,7 @@ func runCreateUser(ctx context.Context, c *cli.Command) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// codeql[disable-next-line=go/clear-text-logging]
|
||||
fmt.Printf("generated random password is '%s'\n", password)
|
||||
} else if userType == user_model.UserTypeIndividual {
|
||||
return errors.New("must set either password or random-password flag")
|
||||
|
||||
@@ -58,6 +58,7 @@ func runMustChangePassword(ctx context.Context, c *cli.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// codeql[disable-next-line=go/clear-text-logging]
|
||||
fmt.Printf("Updated %d users setting MustChangePassword to %t\n", n, mustChangePassword)
|
||||
return nil
|
||||
}
|
||||
|
||||
156
cmd/config.go
Normal file
156
cmd/config.go
Normal file
@@ -0,0 +1,156 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func cmdConfig() *cli.Command {
|
||||
subcmdConfigEditIni := &cli.Command{
|
||||
Name: "edit-ini",
|
||||
Usage: "Load an existing INI file, apply environment variables, keep specified keys, and output to a new INI file.",
|
||||
Description: `
|
||||
Help users to edit the Gitea configuration INI file.
|
||||
|
||||
# Keep Specified Keys
|
||||
|
||||
If you need to re-create the configuration file with only a subset of keys,
|
||||
you can provide an INI template file for the kept keys and use the "--config-keep-keys" flag.
|
||||
For example, if a helm chart needs to reset the settings and only keep SECRET_KEY,
|
||||
it can use a template file (only keys take effect, values are ignored):
|
||||
|
||||
[security]
|
||||
SECRET_KEY=
|
||||
|
||||
$ ./gitea config edit-ini --config app-old.ini --config-keep-keys app-keys.ini --out app-new.ini
|
||||
|
||||
# Map Environment Variables to INI Configuration
|
||||
|
||||
Environment variables of the form "GITEA__section_name__KEY_NAME"
|
||||
will be mapped to the ini section "[section_name]" and the key
|
||||
"KEY_NAME" with the value as provided.
|
||||
|
||||
Environment variables of the form "GITEA__section_name__KEY_NAME__FILE"
|
||||
will be mapped to the ini section "[section_name]" and the key
|
||||
"KEY_NAME" with the value loaded from the specified file.
|
||||
|
||||
Environment variable keys can only contain characters "0-9A-Z_",
|
||||
if a section or key name contains dot ".", it needs to be escaped as _0x2E_.
|
||||
For example, to apply this config:
|
||||
|
||||
[git.config]
|
||||
foo.bar=val
|
||||
|
||||
$ export GITEA__git_0x2E_config__foo_0x2E_bar=val
|
||||
|
||||
# Put All Together
|
||||
|
||||
$ ./gitea config edit-ini --config app.ini --config-keep-keys app-keys.ini --apply-env {--in-place|--out app-new.ini}
|
||||
`,
|
||||
Flags: []cli.Flag{
|
||||
// "--config" flag is provided by global flags, and this flag is also used by "environment-to-ini" script wrapper
|
||||
// "--in-place" is also used by "environment-to-ini" script wrapper for its old behavior: always overwrite the existing config file
|
||||
&cli.BoolFlag{
|
||||
Name: "in-place",
|
||||
Usage: "Output to the same config file as input. This flag will be ignored if --out is set.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "config-keep-keys",
|
||||
Usage: "An INI template file containing keys for keeping. Only the keys defined in the INI template will be kept from old config. If not set, all keys will be kept.",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "apply-env",
|
||||
Usage: "Apply all GITEA__* variables from the environment to the config.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "out",
|
||||
Usage: "Destination config file to write to.",
|
||||
},
|
||||
},
|
||||
Action: runConfigEditIni,
|
||||
}
|
||||
|
||||
return &cli.Command{
|
||||
Name: "config",
|
||||
Usage: "Manage Gitea configuration",
|
||||
Commands: []*cli.Command{
|
||||
subcmdConfigEditIni,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func runConfigEditIni(_ context.Context, c *cli.Command) error {
|
||||
// the config system may change the environment variables, so get a copy first, to be used later
|
||||
env := append([]string{}, os.Environ()...)
|
||||
|
||||
// don't use the guessed setting.CustomConf, instead, require the user to provide --config explicitly
|
||||
if !c.IsSet("config") {
|
||||
return errors.New("flag is required but not set: --config")
|
||||
}
|
||||
configFileIn := c.String("config")
|
||||
|
||||
cfgIn, err := setting.NewConfigProviderFromFile(configFileIn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load config file %q: %v", configFileIn, err)
|
||||
}
|
||||
|
||||
// determine output config file: use "--out" flag or use "--in-place" flag to overwrite input file
|
||||
inPlace := c.Bool("in-place")
|
||||
configFileOut := c.String("out")
|
||||
if configFileOut == "" {
|
||||
if !inPlace {
|
||||
return errors.New("either --in-place or --out must be specified")
|
||||
}
|
||||
configFileOut = configFileIn // in-place edit
|
||||
}
|
||||
|
||||
needWriteOut := configFileOut != configFileIn
|
||||
|
||||
cfgOut := cfgIn
|
||||
configKeepKeys := c.String("config-keep-keys")
|
||||
if configKeepKeys != "" {
|
||||
needWriteOut = true
|
||||
cfgOut, err = setting.NewConfigProviderFromFile(configKeepKeys)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load config-keep-keys template file %q: %v", configKeepKeys, err)
|
||||
}
|
||||
|
||||
for _, secOut := range cfgOut.Sections() {
|
||||
for _, keyOut := range secOut.Keys() {
|
||||
secIn := cfgIn.Section(secOut.Name())
|
||||
keyIn := setting.ConfigSectionKey(secIn, keyOut.Name())
|
||||
if keyIn != nil {
|
||||
keyOut.SetValue(keyIn.String())
|
||||
} else {
|
||||
secOut.DeleteKey(keyOut.Name())
|
||||
}
|
||||
}
|
||||
if len(secOut.Keys()) == 0 {
|
||||
cfgOut.DeleteSection(secOut.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.Bool("apply-env") {
|
||||
if setting.EnvironmentToConfig(cfgOut, env) {
|
||||
needWriteOut = true
|
||||
}
|
||||
}
|
||||
|
||||
if needWriteOut {
|
||||
err = cfgOut.SaveTo(configFileOut)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
85
cmd/config_test.go
Normal file
85
cmd/config_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConfigEdit(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configOld := tmpDir + "/app-old.ini"
|
||||
configTemplate := tmpDir + "/app-template.ini"
|
||||
_ = os.WriteFile(configOld, []byte(`
|
||||
[sec]
|
||||
k1=v1
|
||||
k2=v2
|
||||
`), os.ModePerm)
|
||||
|
||||
_ = os.WriteFile(configTemplate, []byte(`
|
||||
[sec]
|
||||
k1=in-template
|
||||
|
||||
[sec2]
|
||||
k3=v3
|
||||
`), os.ModePerm)
|
||||
|
||||
t.Setenv("GITEA__EnV__KeY", "val")
|
||||
|
||||
t.Run("OutputToNewWithEnv", func(t *testing.T) {
|
||||
configNew := tmpDir + "/app-new.ini"
|
||||
err := NewMainApp(AppVersion{}).Run(t.Context(), []string{
|
||||
"./gitea", "--config", configOld,
|
||||
"config", "edit-ini",
|
||||
"--apply-env",
|
||||
"--config-keep-keys", configTemplate,
|
||||
"--out", configNew,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// "k1" old value is kept because its key is in the template
|
||||
// "k2" is removed because it isn't in the template
|
||||
// "k3" isn't in new config because it isn't in the old config
|
||||
// [env] is applied from environment variable
|
||||
data, _ := os.ReadFile(configNew)
|
||||
require.Equal(t, `[sec]
|
||||
k1 = v1
|
||||
|
||||
[env]
|
||||
KeY = val
|
||||
`, string(data))
|
||||
})
|
||||
|
||||
t.Run("OutputToExisting(environment-to-ini)", func(t *testing.T) {
|
||||
// the legacy "environment-to-ini" (now a wrapper script) behavior:
|
||||
// if no "--out", then "--in-place" must be used to overwrite the existing "--config" file
|
||||
err := NewMainApp(AppVersion{}).Run(t.Context(), []string{
|
||||
"./gitea", "config", "edit-ini",
|
||||
"--apply-env",
|
||||
"--config", configOld,
|
||||
})
|
||||
require.ErrorContains(t, err, "either --in-place or --out must be specified")
|
||||
|
||||
// simulate the "environment-to-ini" behavior with "--in-place"
|
||||
err = NewMainApp(AppVersion{}).Run(t.Context(), []string{
|
||||
"./gitea", "config", "edit-ini",
|
||||
"--in-place",
|
||||
"--apply-env",
|
||||
"--config", configOld,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
data, _ := os.ReadFile(configOld)
|
||||
require.Equal(t, `[sec]
|
||||
k1 = v1
|
||||
k2 = v2
|
||||
|
||||
[env]
|
||||
KeY = val
|
||||
`, string(data))
|
||||
})
|
||||
}
|
||||
@@ -91,6 +91,7 @@ func runGenerateSecretKey(_ context.Context, c *cli.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// codeql[disable-next-line=go/clear-text-logging]
|
||||
fmt.Printf("%s", secretKey)
|
||||
|
||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
|
||||
@@ -186,7 +186,7 @@ Gitea or set your environment appropriately.`, "")
|
||||
userID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
|
||||
prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64)
|
||||
deployKeyID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvDeployKeyID), 10, 64)
|
||||
actionPerm, _ := strconv.ParseInt(os.Getenv(repo_module.EnvActionPerm), 10, 64)
|
||||
actionPerm, _ := strconv.Atoi(os.Getenv(repo_module.EnvActionPerm))
|
||||
|
||||
hookOptions := private.HookOptions{
|
||||
UserID: userID,
|
||||
@@ -196,7 +196,7 @@ Gitea or set your environment appropriately.`, "")
|
||||
GitPushOptions: pushOptions(),
|
||||
PullRequestID: prID,
|
||||
DeployKeyID: deployKeyID,
|
||||
ActionPerm: int(actionPerm),
|
||||
ActionPerm: actionPerm,
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
@@ -313,7 +313,7 @@ func runHookPostReceive(ctx context.Context, c *cli.Command) error {
|
||||
setup(ctx, c.Bool("debug"))
|
||||
|
||||
// First of all run update-server-info no matter what
|
||||
if _, _, err := gitcmd.NewCommand("update-server-info").RunStdString(ctx, nil); err != nil {
|
||||
if _, _, err := gitcmd.NewCommand("update-server-info").RunStdString(ctx); err != nil {
|
||||
return fmt.Errorf("failed to call 'git update-server-info': %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -132,6 +132,7 @@ func NewMainApp(appVer AppVersion) *cli.Command {
|
||||
|
||||
// these sub-commands do not need the config file, and they do not depend on any path or environment variable.
|
||||
subCmdStandalone := []*cli.Command{
|
||||
cmdConfig(),
|
||||
cmdCert(),
|
||||
CmdGenerate,
|
||||
CmdDocs,
|
||||
|
||||
@@ -156,7 +156,6 @@ func serveInstall(cmd *cli.Command) error {
|
||||
case <-graceful.GetManager().IsShutdown():
|
||||
<-graceful.GetManager().Done()
|
||||
log.Info("PID: %d Gitea Web Finished", os.Getpid())
|
||||
log.GetManager().Close()
|
||||
return err
|
||||
default:
|
||||
}
|
||||
@@ -231,7 +230,6 @@ func serveInstalled(c *cli.Command) error {
|
||||
err := listen(webRoutes, true)
|
||||
<-graceful.GetManager().Done()
|
||||
log.Info("PID: %d Gitea Web Finished", os.Getpid())
|
||||
log.GetManager().Close()
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
Environment To Ini
|
||||
==================
|
||||
|
||||
Multiple docker users have requested that the Gitea docker is changed
|
||||
to permit arbitrary configuration via environment variables.
|
||||
|
||||
Gitea needs to use an ini file for configuration because the running
|
||||
environment that starts the docker may not be the same as that used
|
||||
by the hooks. An ini file also gives a good default and means that
|
||||
users do not have to completely provide a full environment.
|
||||
|
||||
With those caveats above, this command provides a generic way of
|
||||
converting suitably structured environment variables into any ini
|
||||
value.
|
||||
|
||||
To use the command is very simple just run it and the default gitea
|
||||
app.ini will be rewritten to take account of the variables provided,
|
||||
however there are various options to give slightly different
|
||||
behavior and these can be interrogated with the `-h` option.
|
||||
|
||||
The environment variables should be of the form:
|
||||
|
||||
GITEA__SECTION_NAME__KEY_NAME
|
||||
|
||||
Note, SECTION_NAME in the notation above is case-insensitive.
|
||||
|
||||
Environment variables are usually restricted to a reduced character
|
||||
set "0-9A-Z_" - in order to allow the setting of sections with
|
||||
characters outside of that set, they should be escaped as following:
|
||||
"_0X2E_" for "." and "_0X2D_" for "-". The entire section and key names
|
||||
can be escaped as a UTF8 byte string if necessary. E.g. to configure:
|
||||
|
||||
"""
|
||||
...
|
||||
[log.console]
|
||||
COLORIZE=false
|
||||
STDERR=true
|
||||
...
|
||||
"""
|
||||
|
||||
You would set the environment variables: "GITEA__LOG_0x2E_CONSOLE__COLORIZE=false"
|
||||
and "GITEA__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found
|
||||
on the configuration cheat sheet.
|
||||
|
||||
To build locally, run:
|
||||
|
||||
go build contrib/environment-to-ini/environment-to-ini.go
|
||||
@@ -1,112 +0,0 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.Command{}
|
||||
app.Name = "environment-to-ini"
|
||||
app.Usage = "Use provided environment to update configuration ini"
|
||||
app.Description = `As a helper to allow docker users to update the gitea configuration
|
||||
through the environment, this command allows environment variables to
|
||||
be mapped to values in the ini.
|
||||
|
||||
Environment variables of the form "GITEA__SECTION_NAME__KEY_NAME"
|
||||
will be mapped to the ini section "[section_name]" and the key
|
||||
"KEY_NAME" with the value as provided.
|
||||
|
||||
Environment variables of the form "GITEA__SECTION_NAME__KEY_NAME__FILE"
|
||||
will be mapped to the ini section "[section_name]" and the key
|
||||
"KEY_NAME" with the value loaded from the specified file.
|
||||
|
||||
Environment variables are usually restricted to a reduced character
|
||||
set "0-9A-Z_" - in order to allow the setting of sections with
|
||||
characters outside of that set, they should be escaped as following:
|
||||
"_0X2E_" for ".". The entire section and key names can be escaped as
|
||||
a UTF8 byte string if necessary. E.g. to configure:
|
||||
|
||||
"""
|
||||
...
|
||||
[log.console]
|
||||
COLORIZE=false
|
||||
STDERR=true
|
||||
...
|
||||
"""
|
||||
|
||||
You would set the environment variables: "GITEA__LOG_0x2E_CONSOLE__COLORIZE=false"
|
||||
and "GITEA__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found
|
||||
on the configuration cheat sheet.`
|
||||
app.Flags = []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "custom-path",
|
||||
Aliases: []string{"C"},
|
||||
Value: setting.CustomPath,
|
||||
Usage: "Custom path file path",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "config",
|
||||
Aliases: []string{"c"},
|
||||
Value: setting.CustomConf,
|
||||
Usage: "Custom configuration file path",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "work-path",
|
||||
Aliases: []string{"w"},
|
||||
Value: setting.AppWorkPath,
|
||||
Usage: "Set the gitea working path",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "out",
|
||||
Aliases: []string{"o"},
|
||||
Value: "",
|
||||
Usage: "Destination file to write to",
|
||||
},
|
||||
}
|
||||
app.Action = runEnvironmentToIni
|
||||
err := app.Run(context.Background(), os.Args)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to run app with %s: %v", os.Args, err)
|
||||
}
|
||||
}
|
||||
|
||||
func runEnvironmentToIni(_ context.Context, c *cli.Command) error {
|
||||
// the config system may change the environment variables, so get a copy first, to be used later
|
||||
env := append([]string{}, os.Environ()...)
|
||||
setting.InitWorkPathAndCfgProvider(os.Getenv, setting.ArgWorkPathAndCustomConf{
|
||||
WorkPath: c.String("work-path"),
|
||||
CustomPath: c.String("custom-path"),
|
||||
CustomConf: c.String("config"),
|
||||
})
|
||||
|
||||
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load custom conf '%s': %v", setting.CustomConf, err)
|
||||
}
|
||||
|
||||
changed := setting.EnvironmentToConfig(cfg, env)
|
||||
|
||||
// try to save the config file
|
||||
destination := c.String("out")
|
||||
if len(destination) == 0 {
|
||||
destination = setting.CustomConf
|
||||
}
|
||||
if destination != setting.CustomConf || changed {
|
||||
log.Info("Settings saved to: %q", destination)
|
||||
err = cfg.SaveTo(destination)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -2334,7 +2334,7 @@ LEVEL = Info
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Resynchronize pre-receive, update and post-receive hooks of all repositories.
|
||||
;; Resynchronize git hooks of all repositories (pre-receive, update, post-receive, proc-receive, ...)
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;[cron.resync_all_hooks]
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
2
docker/root/usr/local/bin/environment-to-ini
Normal file
2
docker/root/usr/local/bin/environment-to-ini
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
exec /app/gitea/gitea config edit-ini --in-place --apply-env "$@"
|
||||
2
docker/rootless/usr/local/bin/environment-to-ini
Normal file
2
docker/rootless/usr/local/bin/environment-to-ini
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
exec /app/gitea/gitea config edit-ini --in-place --apply-env "$@"
|
||||
@@ -3,7 +3,6 @@ import comments from '@eslint-community/eslint-plugin-eslint-comments';
|
||||
import github from 'eslint-plugin-github';
|
||||
import globals from 'globals';
|
||||
import importPlugin from 'eslint-plugin-import-x';
|
||||
import noUseExtendNative from 'eslint-plugin-no-use-extend-native';
|
||||
import playwright from 'eslint-plugin-playwright';
|
||||
import regexp from 'eslint-plugin-regexp';
|
||||
import sonarjs from 'eslint-plugin-sonarjs';
|
||||
@@ -49,24 +48,19 @@ export default defineConfig([
|
||||
},
|
||||
linterOptions: {
|
||||
reportUnusedDisableDirectives: 2,
|
||||
reportUnusedInlineConfigs: 2,
|
||||
},
|
||||
plugins: {
|
||||
'@eslint-community/eslint-comments': comments,
|
||||
// @ts-expect-error
|
||||
'@stylistic': stylistic,
|
||||
'@typescript-eslint': typescriptPlugin.plugin,
|
||||
'array-func': arrayFunc,
|
||||
// @ts-expect-error -- https://github.com/un-ts/eslint-plugin-import-x/issues/203
|
||||
'import-x': importPlugin,
|
||||
'no-use-extend-native': noUseExtendNative,
|
||||
// @ts-expect-error
|
||||
regexp,
|
||||
// @ts-expect-error
|
||||
sonarjs,
|
||||
// @ts-expect-error
|
||||
unicorn,
|
||||
github,
|
||||
// @ts-expect-error
|
||||
wc,
|
||||
},
|
||||
settings: {
|
||||
@@ -159,7 +153,7 @@ export default defineConfig([
|
||||
'@typescript-eslint/ban-tslint-comment': [0],
|
||||
'@typescript-eslint/class-literal-property-style': [0],
|
||||
'@typescript-eslint/class-methods-use-this': [0],
|
||||
'@typescript-eslint/consistent-generic-constructors': [0],
|
||||
'@typescript-eslint/consistent-generic-constructors': [2, 'constructor'],
|
||||
'@typescript-eslint/consistent-indexed-object-style': [0],
|
||||
'@typescript-eslint/consistent-return': [0],
|
||||
'@typescript-eslint/consistent-type-assertions': [2, {assertionStyle: 'as', objectLiteralTypeAssertions: 'allow'}],
|
||||
@@ -235,6 +229,7 @@ export default defineConfig([
|
||||
'@typescript-eslint/no-unsafe-return': [0],
|
||||
'@typescript-eslint/no-unsafe-unary-minus': [2],
|
||||
'@typescript-eslint/no-unused-expressions': [0],
|
||||
'@typescript-eslint/no-unused-private-class-members': [2],
|
||||
'@typescript-eslint/no-unused-vars': [2, {vars: 'all', args: 'all', caughtErrors: 'all', ignoreRestSiblings: false, argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_'}],
|
||||
'@typescript-eslint/no-use-before-define': [2, {functions: false, classes: true, variables: true, allowNamedExports: true, typedefs: false, enums: false, ignoreTypeReferences: true}],
|
||||
'@typescript-eslint/no-useless-constructor': [0],
|
||||
@@ -591,10 +586,10 @@ export default defineConfig([
|
||||
'no-unsafe-negation': [2],
|
||||
'no-unused-expressions': [2],
|
||||
'no-unused-labels': [2],
|
||||
'no-unused-private-class-members': [2],
|
||||
'no-unused-private-class-members': [0], // handled by @typescript-eslint/no-unused-private-class-members
|
||||
'no-unused-vars': [0], // handled by @typescript-eslint/no-unused-vars
|
||||
'no-use-before-define': [0], // handled by @typescript-eslint/no-use-before-define
|
||||
'no-use-extend-native/no-use-extend-native': [2],
|
||||
'no-useless-assignment': [2],
|
||||
'no-useless-backreference': [2],
|
||||
'no-useless-call': [2],
|
||||
'no-useless-catch': [2],
|
||||
@@ -777,6 +772,7 @@ export default defineConfig([
|
||||
'unicorn/no-empty-file': [2],
|
||||
'unicorn/no-for-loop': [0],
|
||||
'unicorn/no-hex-escape': [0],
|
||||
'unicorn/no-immediate-mutation': [0],
|
||||
'unicorn/no-instanceof-array': [0],
|
||||
'unicorn/no-invalid-fetch-options': [2],
|
||||
'unicorn/no-invalid-remove-event-listener': [2],
|
||||
@@ -802,6 +798,7 @@ export default defineConfig([
|
||||
'unicorn/no-unreadable-array-destructuring': [0],
|
||||
'unicorn/no-unreadable-iife': [2],
|
||||
'unicorn/no-unused-properties': [2],
|
||||
'unicorn/no-useless-collection-argument': [2],
|
||||
'unicorn/no-useless-fallback-in-spread': [2],
|
||||
'unicorn/no-useless-length-check': [2],
|
||||
'unicorn/no-useless-promise-resolve-reject': [2],
|
||||
@@ -813,8 +810,8 @@ export default defineConfig([
|
||||
'unicorn/numeric-separators-style': [0],
|
||||
'unicorn/prefer-add-event-listener': [2],
|
||||
'unicorn/prefer-array-find': [2],
|
||||
'unicorn/prefer-array-flat-map': [2],
|
||||
'unicorn/prefer-array-flat': [2],
|
||||
'unicorn/prefer-array-flat-map': [2],
|
||||
'unicorn/prefer-array-index-of': [2],
|
||||
'unicorn/prefer-array-some': [2],
|
||||
'unicorn/prefer-at': [0],
|
||||
@@ -849,6 +846,7 @@ export default defineConfig([
|
||||
'unicorn/prefer-query-selector': [2],
|
||||
'unicorn/prefer-reflect-apply': [0],
|
||||
'unicorn/prefer-regexp-test': [2],
|
||||
'unicorn/prefer-response-static-json': [2],
|
||||
'unicorn/prefer-set-has': [0],
|
||||
'unicorn/prefer-set-size': [2],
|
||||
'unicorn/prefer-spread': [0],
|
||||
@@ -900,7 +898,6 @@ export default defineConfig([
|
||||
'yoda': [2, 'never'],
|
||||
},
|
||||
},
|
||||
// @ts-expect-error
|
||||
{
|
||||
...playwright.configs['flat/recommended'],
|
||||
files: ['tests/e2e/**'],
|
||||
@@ -916,7 +913,6 @@ export default defineConfig([
|
||||
},
|
||||
},
|
||||
extends: [
|
||||
// @ts-expect-error
|
||||
vue.configs['flat/recommended'],
|
||||
// @ts-expect-error
|
||||
vueScopedCss.configs['flat/recommended'],
|
||||
@@ -926,6 +922,7 @@ export default defineConfig([
|
||||
'vue/html-closing-bracket-spacing': [2, {startTag: 'never', endTag: 'never', selfClosingTag: 'never'}],
|
||||
'vue/max-attributes-per-line': [0],
|
||||
'vue/singleline-html-element-content-newline': [0],
|
||||
'vue/require-typed-ref': [2],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -936,7 +933,6 @@ export default defineConfig([
|
||||
},
|
||||
{
|
||||
files: ['**/*.test.ts', 'web_src/js/test/setup.ts'],
|
||||
// @ts-expect-error - https://github.com/vitest-dev/eslint-plugin-vitest/issues/737
|
||||
plugins: {vitest},
|
||||
languageOptions: {globals: globals.vitest},
|
||||
rules: {
|
||||
|
||||
40
flake.lock
generated
40
flake.lock
generated
@@ -1,30 +1,12 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1755186698,
|
||||
"narHash": "sha256-wNO3+Ks2jZJ4nTHMuks+cxAiVBGNuEBXsT29Bz6HASo=",
|
||||
"lastModified": 1760038930,
|
||||
"narHash": "sha256-Oncbh0UmHjSlxO7ErQDM3KM0A5/Znfofj2BSzlHLeVw=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "fbcf476f790d8a217c3eab4e12033dc4a0f6d23c",
|
||||
"rev": "0b4defa2584313f3b781240b29d61f6f9f7e0df3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -36,24 +18,8 @@
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
||||
139
flake.nix
139
flake.nix
@@ -1,73 +1,94 @@
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
outputs =
|
||||
{ nixpkgs, flake-utils, ... }:
|
||||
flake-utils.lib.eachDefaultSystem (
|
||||
system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in
|
||||
{
|
||||
devShells.default =
|
||||
with pkgs;
|
||||
{ nixpkgs, ... }:
|
||||
let
|
||||
supportedSystems = [
|
||||
"aarch64-darwin"
|
||||
"aarch64-linux"
|
||||
"x86_64-darwin"
|
||||
"x86_64-linux"
|
||||
];
|
||||
|
||||
forEachSupportedSystem =
|
||||
f:
|
||||
nixpkgs.lib.genAttrs supportedSystems (
|
||||
system:
|
||||
let
|
||||
# only bump toolchain versions here
|
||||
go = go_1_25;
|
||||
nodejs = nodejs_24;
|
||||
python3 = python312;
|
||||
pnpm = pnpm_10;
|
||||
|
||||
# Platform-specific dependencies
|
||||
linuxOnlyInputs = lib.optionals pkgs.stdenv.isLinux [
|
||||
glibc.static
|
||||
];
|
||||
|
||||
linuxOnlyEnv = lib.optionalAttrs pkgs.stdenv.isLinux {
|
||||
CFLAGS = "-I${glibc.static.dev}/include";
|
||||
LDFLAGS = "-L ${glibc.static}/lib";
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
};
|
||||
in
|
||||
pkgs.mkShell (
|
||||
{
|
||||
buildInputs = [
|
||||
# generic
|
||||
git
|
||||
git-lfs
|
||||
gnumake
|
||||
gnused
|
||||
gnutar
|
||||
gzip
|
||||
zip
|
||||
f { inherit pkgs; }
|
||||
);
|
||||
in
|
||||
{
|
||||
devShells = forEachSupportedSystem (
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
default =
|
||||
let
|
||||
inherit (pkgs) lib;
|
||||
|
||||
# frontend
|
||||
nodejs
|
||||
pnpm
|
||||
cairo
|
||||
pixman
|
||||
pkg-config
|
||||
# only bump toolchain versions here
|
||||
go = pkgs.go_1_25;
|
||||
nodejs = pkgs.nodejs_24;
|
||||
python3 = pkgs.python312;
|
||||
pnpm = pkgs.pnpm_10;
|
||||
|
||||
# linting
|
||||
python3
|
||||
uv
|
||||
# Platform-specific dependencies
|
||||
linuxOnlyInputs = lib.optionals pkgs.stdenv.isLinux [
|
||||
pkgs.glibc.static
|
||||
];
|
||||
|
||||
# backend
|
||||
go
|
||||
gofumpt
|
||||
sqlite
|
||||
]
|
||||
++ linuxOnlyInputs;
|
||||
linuxOnlyEnv = lib.optionalAttrs pkgs.stdenv.isLinux {
|
||||
CFLAGS = "-I${pkgs.glibc.static.dev}/include";
|
||||
LDFLAGS = "-L ${pkgs.glibc.static}/lib";
|
||||
};
|
||||
in
|
||||
pkgs.mkShell {
|
||||
packages =
|
||||
with pkgs;
|
||||
[
|
||||
# generic
|
||||
git
|
||||
git-lfs
|
||||
gnumake
|
||||
gnused
|
||||
gnutar
|
||||
gzip
|
||||
zip
|
||||
|
||||
GO = "${go}/bin/go";
|
||||
GOROOT = "${go}/share/go";
|
||||
# frontend
|
||||
nodejs
|
||||
pnpm
|
||||
cairo
|
||||
pixman
|
||||
pkg-config
|
||||
|
||||
TAGS = "sqlite sqlite_unlock_notify";
|
||||
STATIC = "true";
|
||||
}
|
||||
// linuxOnlyEnv
|
||||
);
|
||||
}
|
||||
);
|
||||
# linting
|
||||
python3
|
||||
uv
|
||||
|
||||
# backend
|
||||
go
|
||||
gofumpt
|
||||
sqlite
|
||||
]
|
||||
++ linuxOnlyInputs;
|
||||
|
||||
env = {
|
||||
GO = "${go}/bin/go";
|
||||
GOROOT = "${go}/share/go";
|
||||
|
||||
TAGS = "sqlite sqlite_unlock_notify";
|
||||
STATIC = "true";
|
||||
}
|
||||
// linuxOnlyEnv;
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
14
go.mod
14
go.mod
@@ -11,14 +11,13 @@ godebug x509negativeserial=1
|
||||
|
||||
require (
|
||||
code.gitea.io/actions-proto-go v0.4.1
|
||||
code.gitea.io/gitea-vet v0.2.3
|
||||
code.gitea.io/sdk/gitea v0.22.0
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
||||
connectrpc.com/connect v1.18.1
|
||||
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed
|
||||
gitea.com/go-chi/cache v0.2.1
|
||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
|
||||
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96
|
||||
gitea.com/go-chi/session v0.0.0-20251124165456-68e0254e989e
|
||||
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
|
||||
github.com/42wim/httpsig v1.2.3
|
||||
@@ -63,6 +62,7 @@ require (
|
||||
github.com/go-redsync/redsync/v4 v4.13.0
|
||||
github.com/go-sql-driver/mysql v1.9.3
|
||||
github.com/go-webauthn/webauthn v0.13.4
|
||||
github.com/goccy/go-json v0.10.5
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
@@ -77,7 +77,6 @@ require (
|
||||
github.com/huandu/xstrings v1.5.0
|
||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056
|
||||
github.com/jhillyerd/enmime v1.3.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/klauspost/compress v1.18.0
|
||||
github.com/klauspost/cpuid/v2 v2.3.0
|
||||
@@ -86,7 +85,7 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mattn/go-sqlite3 v1.14.32
|
||||
github.com/meilisearch/meilisearch-go v0.33.2
|
||||
github.com/mholt/archives v0.1.5-0.20251009205813-e30ac6010726
|
||||
github.com/mholt/archives v0.0.0-20251009205813-e30ac6010726
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/microsoft/go-mssqldb v1.9.3
|
||||
github.com/minio/minio-go/v7 v7.0.95
|
||||
@@ -137,6 +136,7 @@ require (
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.8.0 // indirect
|
||||
code.gitea.io/gitea-vet v0.2.3 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
|
||||
@@ -202,7 +202,6 @@ require (
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-webauthn/x v0.1.24 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
@@ -222,6 +221,7 @@ require (
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/libdns/libdns v1.1.1 // indirect
|
||||
@@ -280,7 +280,7 @@ require (
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
@@ -309,3 +309,5 @@ exclude github.com/gofrs/uuid v4.0.0+incompatible
|
||||
exclude github.com/goccy/go-json v0.4.11
|
||||
|
||||
exclude github.com/satori/go.uuid v1.2.0
|
||||
|
||||
tool code.gitea.io/gitea-vet
|
||||
|
||||
12
go.sum
12
go.sum
@@ -41,8 +41,8 @@ gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g=
|
||||
gitea.com/go-chi/cache v0.2.1/go.mod h1:Qic0HZ8hOHW62ETGbonpwz8WYypj9NieU9659wFUJ8Q=
|
||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR98IP2KZQKRzJJiV7aTeMAFwaWo=
|
||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098/go.mod h1:LjzIOHlRemuUyO7WR12fmm18VZIlCAaOt9L3yKw40pk=
|
||||
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96 h1:IFDiMBObsP6CZIRaDLd54SR6zPYAffPXiXck5Xslu0Q=
|
||||
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96/go.mod h1:0iEpFKnwO5dG0aF98O4eq6FMsAiXkNBaDIlUOlq4BtM=
|
||||
gitea.com/go-chi/session v0.0.0-20251124165456-68e0254e989e h1:4bugwPyGMLvblEm3pZ8fZProSPVxE4l0UXF2Kv6IJoY=
|
||||
gitea.com/go-chi/session v0.0.0-20251124165456-68e0254e989e/go.mod h1:KDvcfMUoXfATPHs2mbMoXFTXT45/FAFAS39waz9tPk0=
|
||||
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:+wWBi6Qfruqu7xJgjOIrKVQGiLUZdpKYCZewJ4clqhw=
|
||||
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:VyMQP6ue6MKHM8UsOXfNfuMKD0oSAWZdXVcpHIN2yaY=
|
||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHqdhS7keYWioqfmxdnfblFDTGoOwcZ+o=
|
||||
@@ -572,8 +572,8 @@ github.com/meilisearch/meilisearch-go v0.33.2 h1:YgsQSLYhAkRN2ias6I1KNRTjdYCN5w2
|
||||
github.com/meilisearch/meilisearch-go v0.33.2/go.mod h1:6eOPcQ+OAuwXvnONlfSgfgvr7TIAWM/6OdhcVHg8cF0=
|
||||
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
|
||||
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/mholt/archives v0.1.5-0.20251009205813-e30ac6010726 h1:WVjGWXBLI1Ggm2kHzNraCGgxFhLoK6gdpPSizCdxnx0=
|
||||
github.com/mholt/archives v0.1.5-0.20251009205813-e30ac6010726/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
|
||||
github.com/mholt/archives v0.0.0-20251009205813-e30ac6010726 h1:narluFTg20M5KBwKxedpFiSMkdjQRRNUlpY4uAsKMwk=
|
||||
github.com/mholt/archives v0.0.0-20251009205813-e30ac6010726/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/microsoft/go-mssqldb v1.9.3 h1:hy4p+LDC8LIGvI3JATnLVmBOLMJbmn5X400mr5j0lPs=
|
||||
@@ -850,8 +850,8 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4=
|
||||
|
||||
1
main.go
1
main.go
@@ -44,6 +44,7 @@ func main() {
|
||||
}
|
||||
app := cmd.NewMainApp(cmd.AppVersion{Version: Version, Extra: formatBuiltWith()})
|
||||
_ = cmd.RunMainApp(app, os.Args...) // all errors should have been handled by the RunMainApp
|
||||
// flush the queued logs before exiting, it is a MUST, otherwise there will be log loss
|
||||
log.GetManager().Close()
|
||||
}
|
||||
|
||||
|
||||
@@ -16,13 +16,13 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
|
||||
"github.com/nektos/act/pkg/jobparser"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
type ActionRun struct {
|
||||
ID int64
|
||||
Title string
|
||||
RepoID int64 `xorm:"index unique(repo_index)"`
|
||||
RepoID int64 `xorm:"unique(repo_index) index(repo_concurrency)"`
|
||||
Repo *repo_model.Repository `xorm:"-"`
|
||||
OwnerID int64 `xorm:"index"`
|
||||
WorkflowID string `xorm:"index"` // the name of workflow file
|
||||
@@ -49,6 +49,9 @@ type ActionRun struct {
|
||||
TriggerEvent string // the trigger event defined in the `on` configuration of the triggered workflow
|
||||
Status Status `xorm:"index"`
|
||||
Version int `xorm:"version default 0"` // Status could be updated concomitantly, so an optimistic lock is needed
|
||||
RawConcurrency string // raw concurrency
|
||||
ConcurrencyGroup string `xorm:"index(repo_concurrency) NOT NULL DEFAULT ''"`
|
||||
ConcurrencyCancel bool `xorm:"NOT NULL DEFAULT FALSE"`
|
||||
// Started and Stopped is used for recording last run time, if rerun happened, they will be reset to 0
|
||||
Started timeutil.TimeStamp
|
||||
Stopped timeutil.TimeStamp
|
||||
@@ -102,6 +105,15 @@ func (run *ActionRun) PrettyRef() string {
|
||||
return refName.ShortName()
|
||||
}
|
||||
|
||||
// RefTooltip return a tooltop of run's ref. For pull request, it's the title of the PR, otherwise it's the ShortName.
|
||||
func (run *ActionRun) RefTooltip() string {
|
||||
payload, err := run.GetPullRequestEventPayload()
|
||||
if err == nil && payload != nil && payload.PullRequest != nil {
|
||||
return payload.PullRequest.Title
|
||||
}
|
||||
return git.RefName(run.Ref).ShortName()
|
||||
}
|
||||
|
||||
// LoadAttributes load Repo TriggerUser if not loaded
|
||||
func (run *ActionRun) LoadAttributes(ctx context.Context) error {
|
||||
if run == nil {
|
||||
@@ -181,7 +193,8 @@ func (run *ActionRun) IsSchedule() bool {
|
||||
return run.ScheduleID > 0
|
||||
}
|
||||
|
||||
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
|
||||
// UpdateRepoRunsNumbers updates the number of runs and closed runs of a repository.
|
||||
func UpdateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
|
||||
_, err := db.GetEngine(ctx).ID(repo.ID).
|
||||
NoAutoTime().
|
||||
Cols("num_action_runs", "num_closed_action_runs").
|
||||
@@ -239,116 +252,62 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
|
||||
return cancelledJobs, err
|
||||
}
|
||||
|
||||
// Iterate over each job and attempt to cancel it.
|
||||
for _, job := range jobs {
|
||||
// Skip jobs that are already in a terminal state (completed, cancelled, etc.).
|
||||
status := job.Status
|
||||
if status.IsDone() {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the job has no associated task (probably an error), set its status to 'Cancelled' and stop it.
|
||||
if job.TaskID == 0 {
|
||||
job.Status = StatusCancelled
|
||||
job.Stopped = timeutil.TimeStampNow()
|
||||
|
||||
// Update the job's status and stopped time in the database.
|
||||
n, err := UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}, "status", "stopped")
|
||||
if err != nil {
|
||||
return cancelledJobs, err
|
||||
}
|
||||
|
||||
// If the update affected 0 rows, it means the job has changed in the meantime, so we need to try again.
|
||||
if n == 0 {
|
||||
return cancelledJobs, errors.New("job has changed, try again")
|
||||
}
|
||||
|
||||
cancelledJobs = append(cancelledJobs, job)
|
||||
// Continue with the next job.
|
||||
continue
|
||||
}
|
||||
|
||||
// If the job has an associated task, try to stop the task, effectively cancelling the job.
|
||||
if err := StopTask(ctx, job.TaskID, StatusCancelled); err != nil {
|
||||
return cancelledJobs, err
|
||||
}
|
||||
cancelledJobs = append(cancelledJobs, job)
|
||||
cjs, err := CancelJobs(ctx, jobs)
|
||||
if err != nil {
|
||||
return cancelledJobs, err
|
||||
}
|
||||
cancelledJobs = append(cancelledJobs, cjs...)
|
||||
}
|
||||
|
||||
// Return nil to indicate successful cancellation of all running and waiting jobs.
|
||||
return cancelledJobs, nil
|
||||
}
|
||||
|
||||
// InsertRun inserts a run
|
||||
// The title will be cut off at 255 characters if it's longer than 255 characters.
|
||||
func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
index, err := db.GetNextResourceIndex(ctx, "action_run_index", run.RepoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
run.Index = index
|
||||
run.Title = util.EllipsisDisplayString(run.Title, 255)
|
||||
|
||||
if err := db.Insert(ctx, run); err != nil {
|
||||
return err
|
||||
func CancelJobs(ctx context.Context, jobs []*ActionRunJob) ([]*ActionRunJob, error) {
|
||||
cancelledJobs := make([]*ActionRunJob, 0, len(jobs))
|
||||
// Iterate over each job and attempt to cancel it.
|
||||
for _, job := range jobs {
|
||||
// Skip jobs that are already in a terminal state (completed, cancelled, etc.).
|
||||
status := job.Status
|
||||
if status.IsDone() {
|
||||
continue
|
||||
}
|
||||
|
||||
if run.Repo == nil {
|
||||
repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID)
|
||||
// If the job has no associated task (probably an error), set its status to 'Cancelled' and stop it.
|
||||
if job.TaskID == 0 {
|
||||
job.Status = StatusCancelled
|
||||
job.Stopped = timeutil.TimeStampNow()
|
||||
|
||||
// Update the job's status and stopped time in the database.
|
||||
n, err := UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}, "status", "stopped")
|
||||
if err != nil {
|
||||
return err
|
||||
return cancelledJobs, err
|
||||
}
|
||||
run.Repo = repo
|
||||
|
||||
// If the update affected 0 rows, it means the job has changed in the meantime
|
||||
if n == 0 {
|
||||
log.Error("Failed to cancel job %d because it has changed", job.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
cancelledJobs = append(cancelledJobs, job)
|
||||
// Continue with the next job.
|
||||
continue
|
||||
}
|
||||
|
||||
if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil {
|
||||
return err
|
||||
// If the job has an associated task, try to stop the task, effectively cancelling the job.
|
||||
if err := StopTask(ctx, job.TaskID, StatusCancelled); err != nil {
|
||||
return cancelledJobs, err
|
||||
}
|
||||
updatedJob, err := GetRunJobByID(ctx, job.ID)
|
||||
if err != nil {
|
||||
return cancelledJobs, fmt.Errorf("get job: %w", err)
|
||||
}
|
||||
cancelledJobs = append(cancelledJobs, updatedJob)
|
||||
}
|
||||
|
||||
runJobs := make([]*ActionRunJob, 0, len(jobs))
|
||||
var hasWaiting bool
|
||||
for _, v := range jobs {
|
||||
id, job := v.Job()
|
||||
needs := job.Needs()
|
||||
if err := v.SetJob(id, job.EraseNeeds()); err != nil {
|
||||
return err
|
||||
}
|
||||
payload, _ := v.Marshal()
|
||||
status := StatusWaiting
|
||||
if len(needs) > 0 || run.NeedApproval {
|
||||
status = StatusBlocked
|
||||
} else {
|
||||
hasWaiting = true
|
||||
}
|
||||
job.Name = util.EllipsisDisplayString(job.Name, 255)
|
||||
runJobs = append(runJobs, &ActionRunJob{
|
||||
RunID: run.ID,
|
||||
RepoID: run.RepoID,
|
||||
OwnerID: run.OwnerID,
|
||||
CommitSHA: run.CommitSHA,
|
||||
IsForkPullRequest: run.IsForkPullRequest,
|
||||
Name: job.Name,
|
||||
WorkflowPayload: payload,
|
||||
JobID: id,
|
||||
Needs: needs,
|
||||
RunsOn: job.RunsOn(),
|
||||
Status: status,
|
||||
})
|
||||
}
|
||||
if err := db.Insert(ctx, runJobs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if there is a job in the waiting status, increase tasks version.
|
||||
if hasWaiting {
|
||||
if err := IncreaseTaskVersion(ctx, run.OwnerID, run.RepoID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
// Return nil to indicate successful cancellation of all running and waiting jobs.
|
||||
return cancelledJobs, nil
|
||||
}
|
||||
|
||||
func GetRunByRepoAndID(ctx context.Context, repoID, runID int64) (*ActionRun, error) {
|
||||
@@ -433,7 +392,7 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
|
||||
if err = run.LoadRepo(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil {
|
||||
if err := UpdateRepoRunsNumbers(ctx, run.Repo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -442,3 +401,59 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
|
||||
}
|
||||
|
||||
type ActionRunIndex db.ResourceIndex
|
||||
|
||||
func GetConcurrentRunsAndJobs(ctx context.Context, repoID int64, concurrencyGroup string, status []Status) ([]*ActionRun, []*ActionRunJob, error) {
|
||||
runs, err := db.Find[ActionRun](ctx, &FindRunOptions{
|
||||
RepoID: repoID,
|
||||
ConcurrencyGroup: concurrencyGroup,
|
||||
Status: status,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("find runs: %w", err)
|
||||
}
|
||||
|
||||
jobs, err := db.Find[ActionRunJob](ctx, &FindRunJobOptions{
|
||||
RepoID: repoID,
|
||||
ConcurrencyGroup: concurrencyGroup,
|
||||
Statuses: status,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("find jobs: %w", err)
|
||||
}
|
||||
|
||||
return runs, jobs, nil
|
||||
}
|
||||
|
||||
func CancelPreviousJobsByRunConcurrency(ctx context.Context, actionRun *ActionRun) ([]*ActionRunJob, error) {
|
||||
if actionRun.ConcurrencyGroup == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var jobsToCancel []*ActionRunJob
|
||||
|
||||
statusFindOption := []Status{StatusWaiting, StatusBlocked}
|
||||
if actionRun.ConcurrencyCancel {
|
||||
statusFindOption = append(statusFindOption, StatusRunning)
|
||||
}
|
||||
runs, jobs, err := GetConcurrentRunsAndJobs(ctx, actionRun.RepoID, actionRun.ConcurrencyGroup, statusFindOption)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("find concurrent runs and jobs: %w", err)
|
||||
}
|
||||
jobsToCancel = append(jobsToCancel, jobs...)
|
||||
|
||||
// cancel runs in the same concurrency group
|
||||
for _, run := range runs {
|
||||
if run.ID == actionRun.ID {
|
||||
continue
|
||||
}
|
||||
jobs, err := db.Find[ActionRunJob](ctx, FindRunJobOptions{
|
||||
RunID: run.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("find run %d jobs: %w", run.ID, err)
|
||||
}
|
||||
jobsToCancel = append(jobsToCancel, jobs...)
|
||||
}
|
||||
|
||||
return CancelJobs(ctx, jobsToCancel)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/nektos/act/pkg/jobparser"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
@@ -22,23 +23,38 @@ type ActionRunJob struct {
|
||||
ID int64
|
||||
RunID int64 `xorm:"index"`
|
||||
Run *ActionRun `xorm:"-"`
|
||||
RepoID int64 `xorm:"index"`
|
||||
RepoID int64 `xorm:"index(repo_concurrency)"`
|
||||
Repo *repo_model.Repository `xorm:"-"`
|
||||
OwnerID int64 `xorm:"index"`
|
||||
CommitSHA string `xorm:"index"`
|
||||
IsForkPullRequest bool
|
||||
Name string `xorm:"VARCHAR(255)"`
|
||||
Attempt int64
|
||||
WorkflowPayload []byte
|
||||
JobID string `xorm:"VARCHAR(255)"` // job id in workflow, not job's id
|
||||
Needs []string `xorm:"JSON TEXT"`
|
||||
RunsOn []string `xorm:"JSON TEXT"`
|
||||
TaskID int64 // the latest task of the job
|
||||
Status Status `xorm:"index"`
|
||||
Started timeutil.TimeStamp
|
||||
Stopped timeutil.TimeStamp
|
||||
Created timeutil.TimeStamp `xorm:"created"`
|
||||
Updated timeutil.TimeStamp `xorm:"updated index"`
|
||||
|
||||
// WorkflowPayload is act/jobparser.SingleWorkflow for act/jobparser.Parse
|
||||
// it should contain exactly one job with global workflow fields for this model
|
||||
WorkflowPayload []byte
|
||||
|
||||
JobID string `xorm:"VARCHAR(255)"` // job id in workflow, not job's id
|
||||
Needs []string `xorm:"JSON TEXT"`
|
||||
RunsOn []string `xorm:"JSON TEXT"`
|
||||
TaskID int64 // the latest task of the job
|
||||
Status Status `xorm:"index"`
|
||||
|
||||
RawConcurrency string // raw concurrency from job YAML's "concurrency" section
|
||||
|
||||
// IsConcurrencyEvaluated is only valid/needed when this job's RawConcurrency is not empty.
|
||||
// If RawConcurrency can't be evaluated (e.g. depend on other job's outputs or have errors), this field will be false.
|
||||
// If RawConcurrency has been successfully evaluated, this field will be true, ConcurrencyGroup and ConcurrencyCancel are also set.
|
||||
IsConcurrencyEvaluated bool
|
||||
|
||||
ConcurrencyGroup string `xorm:"index(repo_concurrency) NOT NULL DEFAULT ''"` // evaluated concurrency.group
|
||||
ConcurrencyCancel bool `xorm:"NOT NULL DEFAULT FALSE"` // evaluated concurrency.cancel-in-progress
|
||||
|
||||
Started timeutil.TimeStamp
|
||||
Stopped timeutil.TimeStamp
|
||||
Created timeutil.TimeStamp `xorm:"created"`
|
||||
Updated timeutil.TimeStamp `xorm:"updated index"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -84,6 +100,24 @@ func (job *ActionRunJob) LoadAttributes(ctx context.Context) error {
|
||||
return job.Run.LoadAttributes(ctx)
|
||||
}
|
||||
|
||||
// ParseJob parses the job structure from the ActionRunJob.WorkflowPayload
|
||||
func (job *ActionRunJob) ParseJob() (*jobparser.Job, error) {
|
||||
// job.WorkflowPayload is a SingleWorkflow created from an ActionRun's workflow, which exactly contains this job's YAML definition.
|
||||
// Ideally it shouldn't be called "Workflow", it is just a job with global workflow fields + trigger
|
||||
parsedWorkflows, err := jobparser.Parse(job.WorkflowPayload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("job %d single workflow: unable to parse: %w", job.ID, err)
|
||||
} else if len(parsedWorkflows) != 1 {
|
||||
return nil, fmt.Errorf("job %d single workflow: not single workflow", job.ID)
|
||||
}
|
||||
_, workflowJob := parsedWorkflows[0].Job()
|
||||
if workflowJob == nil {
|
||||
// it shouldn't happen, and since the callers don't check nil, so return an error instead of nil
|
||||
return nil, util.ErrorWrap(util.ErrNotExist, "job %d single workflow: payload doesn't contain a job", job.ID)
|
||||
}
|
||||
return workflowJob, nil
|
||||
}
|
||||
|
||||
func GetRunJobByID(ctx context.Context, id int64) (*ActionRunJob, error) {
|
||||
var job ActionRunJob
|
||||
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&job)
|
||||
@@ -125,7 +159,7 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
|
||||
return affected, nil
|
||||
}
|
||||
|
||||
if affected != 0 && slices.Contains(cols, "status") && job.Status.IsWaiting() {
|
||||
if slices.Contains(cols, "status") && job.Status.IsWaiting() {
|
||||
// if the status of job changes to waiting again, increase tasks version.
|
||||
if err := IncreaseTaskVersion(ctx, job.OwnerID, job.RepoID); err != nil {
|
||||
return 0, err
|
||||
@@ -197,3 +231,39 @@ func AggregateJobStatus(jobs []*ActionRunJob) Status {
|
||||
return StatusUnknown // it shouldn't happen
|
||||
}
|
||||
}
|
||||
|
||||
func CancelPreviousJobsByJobConcurrency(ctx context.Context, job *ActionRunJob) (jobsToCancel []*ActionRunJob, _ error) {
|
||||
if job.RawConcurrency == "" {
|
||||
return nil, nil
|
||||
}
|
||||
if !job.IsConcurrencyEvaluated {
|
||||
return nil, nil
|
||||
}
|
||||
if job.ConcurrencyGroup == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
statusFindOption := []Status{StatusWaiting, StatusBlocked}
|
||||
if job.ConcurrencyCancel {
|
||||
statusFindOption = append(statusFindOption, StatusRunning)
|
||||
}
|
||||
runs, jobs, err := GetConcurrentRunsAndJobs(ctx, job.RepoID, job.ConcurrencyGroup, statusFindOption)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("find concurrent runs and jobs: %w", err)
|
||||
}
|
||||
jobs = slices.DeleteFunc(jobs, func(j *ActionRunJob) bool { return j.ID == job.ID })
|
||||
jobsToCancel = append(jobsToCancel, jobs...)
|
||||
|
||||
// cancel runs in the same concurrency group
|
||||
for _, run := range runs {
|
||||
jobs, err := db.Find[ActionRunJob](ctx, FindRunJobOptions{
|
||||
RunID: run.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("find run %d jobs: %w", run.ID, err)
|
||||
}
|
||||
jobsToCancel = append(jobsToCancel, jobs...)
|
||||
}
|
||||
|
||||
return CancelJobs(ctx, jobsToCancel)
|
||||
}
|
||||
|
||||
@@ -69,12 +69,13 @@ func (jobs ActionJobList) LoadAttributes(ctx context.Context, withRepo bool) err
|
||||
|
||||
type FindRunJobOptions struct {
|
||||
db.ListOptions
|
||||
RunID int64
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
CommitSHA string
|
||||
Statuses []Status
|
||||
UpdatedBefore timeutil.TimeStamp
|
||||
RunID int64
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
CommitSHA string
|
||||
Statuses []Status
|
||||
UpdatedBefore timeutil.TimeStamp
|
||||
ConcurrencyGroup string
|
||||
}
|
||||
|
||||
func (opts FindRunJobOptions) ToConds() builder.Cond {
|
||||
@@ -94,6 +95,12 @@ func (opts FindRunJobOptions) ToConds() builder.Cond {
|
||||
if opts.UpdatedBefore > 0 {
|
||||
cond = cond.And(builder.Lt{"`action_run_job`.updated": opts.UpdatedBefore})
|
||||
}
|
||||
if opts.ConcurrencyGroup != "" {
|
||||
if opts.RepoID == 0 {
|
||||
panic("Invalid FindRunJobOptions: repo_id is required")
|
||||
}
|
||||
cond = cond.And(builder.Eq{"`action_run_job`.concurrency_group": opts.ConcurrencyGroup})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
|
||||
@@ -64,15 +64,16 @@ func (runs RunList) LoadRepos(ctx context.Context) error {
|
||||
|
||||
type FindRunOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
WorkflowID string
|
||||
Ref string // the commit/tag/… that caused this workflow
|
||||
TriggerUserID int64
|
||||
TriggerEvent webhook_module.HookEventType
|
||||
Approved bool // not util.OptionalBool, it works only when it's true
|
||||
Status []Status
|
||||
CommitSHA string
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
WorkflowID string
|
||||
Ref string // the commit/tag/… that caused this workflow
|
||||
TriggerUserID int64
|
||||
TriggerEvent webhook_module.HookEventType
|
||||
Approved bool // not util.OptionalBool, it works only when it's true
|
||||
Status []Status
|
||||
ConcurrencyGroup string
|
||||
CommitSHA string
|
||||
}
|
||||
|
||||
func (opts FindRunOptions) ToConds() builder.Cond {
|
||||
@@ -101,6 +102,12 @@ func (opts FindRunOptions) ToConds() builder.Cond {
|
||||
if opts.CommitSHA != "" {
|
||||
cond = cond.And(builder.Eq{"`action_run`.commit_sha": opts.CommitSHA})
|
||||
}
|
||||
if len(opts.ConcurrencyGroup) > 0 {
|
||||
if opts.RepoID == 0 {
|
||||
panic("Invalid FindRunOptions: repo_id is required")
|
||||
}
|
||||
cond = cond.And(builder.Eq{"`action_run`.concurrency_group": opts.ConcurrencyGroup})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestUpdateRepoRunsNumbers(t *testing.T) {
|
||||
assert.Equal(t, 2, repo.NumClosedActionRuns)
|
||||
|
||||
// now update will correct them, only num_actionr_runs and num_closed_action_runs should be updated
|
||||
err = updateRepoRunsNumbers(t.Context(), repo)
|
||||
err = UpdateRepoRunsNumbers(t.Context(), repo)
|
||||
assert.NoError(t, err)
|
||||
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||
assert.Equal(t, 5, repo.NumActionRuns)
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/shared/types"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
@@ -173,6 +174,13 @@ func (r *ActionRunner) GenerateToken() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// CanMatchLabels checks whether the runner's labels can match a job's "runs-on"
|
||||
// See https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idruns-on
|
||||
func (r *ActionRunner) CanMatchLabels(jobRunsOn []string) bool {
|
||||
runnerLabelSet := container.SetOf(r.AgentLabels...)
|
||||
return runnerLabelSet.Contains(jobRunsOn...) // match all labels
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(&ActionRunner{})
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
@@ -21,7 +20,6 @@ import (
|
||||
|
||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
"github.com/nektos/act/pkg/jobparser"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
@@ -246,7 +244,7 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
|
||||
var job *ActionRunJob
|
||||
log.Trace("runner labels: %v", runner.AgentLabels)
|
||||
for _, v := range jobs {
|
||||
if isSubset(runner.AgentLabels, v.RunsOn) {
|
||||
if runner.CanMatchLabels(v.RunsOn) {
|
||||
job = v
|
||||
break
|
||||
}
|
||||
@@ -278,13 +276,10 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
parsedWorkflows, err := jobparser.Parse(job.WorkflowPayload)
|
||||
workflowJob, err := job.ParseJob()
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("parse workflow of job %d: %w", job.ID, err)
|
||||
} else if len(parsedWorkflows) != 1 {
|
||||
return nil, false, fmt.Errorf("workflow of job %d: not single workflow", job.ID)
|
||||
return nil, false, fmt.Errorf("load job %d: %w", job.ID, err)
|
||||
}
|
||||
_, workflowJob := parsedWorkflows[0].Job()
|
||||
|
||||
if _, err := e.Insert(task); err != nil {
|
||||
return nil, false, err
|
||||
@@ -479,20 +474,6 @@ func FindOldTasksToExpire(ctx context.Context, olderThan timeutil.TimeStamp, lim
|
||||
Find(&tasks)
|
||||
}
|
||||
|
||||
func isSubset(set, subset []string) bool {
|
||||
m := make(container.Set[string], len(set))
|
||||
for _, v := range set {
|
||||
m.Add(v)
|
||||
}
|
||||
|
||||
for _, v := range subset {
|
||||
if !m.Contains(v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func convertTimestamp(timestamp *timestamppb.Timestamp) timeutil.TimeStamp {
|
||||
if timestamp.GetSeconds() == 0 && timestamp.GetNanos() == 0 {
|
||||
return timeutil.TimeStamp(0)
|
||||
|
||||
@@ -139,9 +139,28 @@
|
||||
updated: 1683636626
|
||||
need_approval: 0
|
||||
approved_by: 0
|
||||
|
||||
-
|
||||
id: 796
|
||||
id: 804
|
||||
title: "use a private action"
|
||||
repo_id: 60
|
||||
owner_id: 40
|
||||
workflow_id: "run.yaml"
|
||||
index: 189
|
||||
trigger_user_id: 40
|
||||
ref: "refs/heads/master"
|
||||
commit_sha: "6e64b26de7ba966d01d90ecfaf5c7f14ef203e86"
|
||||
event: "push"
|
||||
trigger_event: "push"
|
||||
is_fork_pull_request: 0
|
||||
status: 1
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
created: 1683636108
|
||||
updated: 1683636626
|
||||
need_approval: 0
|
||||
approved_by: 0
|
||||
-
|
||||
id: 805
|
||||
title: "update actions"
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
|
||||
@@ -129,10 +129,23 @@
|
||||
status: 5
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
|
||||
-
|
||||
id: 205
|
||||
run_id: 796
|
||||
run_id: 804
|
||||
repo_id: 6
|
||||
owner_id: 10
|
||||
commit_sha: 6e64b26de7ba966d01d90ecfaf5c7f14ef203e86
|
||||
is_fork_pull_request: 0
|
||||
name: job_2
|
||||
attempt: 1
|
||||
job_id: job_2
|
||||
task_id: 48
|
||||
status: 1
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
-
|
||||
id: 206
|
||||
run_id: 805
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
@@ -140,7 +153,7 @@
|
||||
name: job_2
|
||||
attempt: 1
|
||||
job_id: job_2
|
||||
task_id: 55
|
||||
task_id: 56
|
||||
status: 3
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
|
||||
@@ -177,12 +177,30 @@
|
||||
log_length: 0
|
||||
log_size: 0
|
||||
log_expired: 0
|
||||
|
||||
-
|
||||
id: 55
|
||||
job_id: 205
|
||||
attempt: 1
|
||||
runner_id: 1
|
||||
status: 6 # 6 is the status code for "running"
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
repo_id: 6
|
||||
owner_id: 10
|
||||
commit_sha: 6e64b26de7ba966d01d90ecfaf5c7f14ef203e86
|
||||
is_fork_pull_request: 0
|
||||
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc478422b
|
||||
token_salt: ERxJGHvg3I
|
||||
token_last_eight: 182199eb
|
||||
log_filename: collaborative-owner-test/1a/49.log
|
||||
log_in_storage: 1
|
||||
log_length: 707
|
||||
log_size: 90179
|
||||
log_expired: 0
|
||||
-
|
||||
id: 56
|
||||
attempt: 1
|
||||
runner_id: 1
|
||||
status: 3 # 3 is the status code for "cancelled"
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
|
||||
@@ -225,3 +225,27 @@
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 27
|
||||
repo_id: 1
|
||||
name: 'DefaultBranch'
|
||||
commit_id: '90c1019714259b24fb81711d4416ac0f18667dfa'
|
||||
commit_message: 'add license'
|
||||
commit_time: 1709345946
|
||||
pusher_id: 1
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 28
|
||||
repo_id: 1
|
||||
name: 'sub-home-md-img-check'
|
||||
commit_id: '4649299398e4d39a5c09eb4f534df6f1e1eb87cc'
|
||||
commit_message: "Test how READMEs render images when found in a subfolder"
|
||||
commit_time: 1678403550
|
||||
pusher_id: 1
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
@@ -736,6 +736,13 @@
|
||||
|
||||
-
|
||||
id: 111
|
||||
repo_id: 3
|
||||
type: 10
|
||||
config: "{}"
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 112
|
||||
repo_id: 4
|
||||
type: 10
|
||||
config: "{}"
|
||||
|
||||
@@ -30,17 +30,21 @@ import (
|
||||
|
||||
// CommitStatus holds a single Status of a single Commit
|
||||
type CommitStatus struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
|
||||
RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
|
||||
Repo *repo_model.Repository `xorm:"-"`
|
||||
State commitstatus.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
|
||||
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
|
||||
TargetURL string `xorm:"TEXT"`
|
||||
Description string `xorm:"TEXT"`
|
||||
ContextHash string `xorm:"VARCHAR(64) index"`
|
||||
Context string `xorm:"TEXT"`
|
||||
Creator *user_model.User `xorm:"-"`
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
|
||||
RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
|
||||
Repo *repo_model.Repository `xorm:"-"`
|
||||
State commitstatus.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
|
||||
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
|
||||
|
||||
// TargetURL points to the commit status page reported by a CI system
|
||||
// If Gitea Actions is used, it is a relative link like "{RepoLink}/actions/runs/{RunID}/jobs{JobID}"
|
||||
TargetURL string `xorm:"TEXT"`
|
||||
|
||||
Description string `xorm:"TEXT"`
|
||||
ContextHash string `xorm:"VARCHAR(64) index"`
|
||||
Context string `xorm:"TEXT"`
|
||||
Creator *user_model.User `xorm:"-"`
|
||||
CreatorID int64
|
||||
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
@@ -211,21 +215,45 @@ func (status *CommitStatus) LocaleString(lang translation.Locale) string {
|
||||
|
||||
// HideActionsURL set `TargetURL` to an empty string if the status comes from Gitea Actions
|
||||
func (status *CommitStatus) HideActionsURL(ctx context.Context) {
|
||||
if _, ok := status.cutTargetURLGiteaActionsPrefix(ctx); ok {
|
||||
status.TargetURL = ""
|
||||
}
|
||||
}
|
||||
|
||||
func (status *CommitStatus) cutTargetURLGiteaActionsPrefix(ctx context.Context) (string, bool) {
|
||||
if status.RepoID == 0 {
|
||||
return
|
||||
return "", false
|
||||
}
|
||||
|
||||
if status.Repo == nil {
|
||||
if err := status.loadRepository(ctx); err != nil {
|
||||
log.Error("loadRepository: %v", err)
|
||||
return
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
prefix := status.Repo.Link() + "/actions"
|
||||
if strings.HasPrefix(status.TargetURL, prefix) {
|
||||
status.TargetURL = ""
|
||||
return strings.CutPrefix(status.TargetURL, prefix)
|
||||
}
|
||||
|
||||
// ParseGiteaActionsTargetURL parses the commit status target URL as Gitea Actions link
|
||||
func (status *CommitStatus) ParseGiteaActionsTargetURL(ctx context.Context) (runID, jobID int64, ok bool) {
|
||||
s, ok := status.cutTargetURLGiteaActionsPrefix(ctx)
|
||||
if !ok {
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
parts := strings.Split(s, "/") // expect: /runs/{runID}/jobs/{jobID}
|
||||
if len(parts) < 5 || parts[1] != "runs" || parts[3] != "jobs" {
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
runID, err1 := strconv.ParseInt(parts[2], 10, 64)
|
||||
jobID, err2 := strconv.ParseInt(parts[4], 10, 64)
|
||||
if err1 != nil || err2 != nil {
|
||||
return 0, 0, false
|
||||
}
|
||||
return runID, jobID, true
|
||||
}
|
||||
|
||||
// CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
@@ -42,30 +41,6 @@ func (err ErrLFSLockNotExist) Unwrap() error {
|
||||
return util.ErrNotExist
|
||||
}
|
||||
|
||||
// ErrLFSUnauthorizedAction represents a "LFSUnauthorizedAction" kind of error.
|
||||
type ErrLFSUnauthorizedAction struct {
|
||||
RepoID int64
|
||||
UserName string
|
||||
Mode perm.AccessMode
|
||||
}
|
||||
|
||||
// IsErrLFSUnauthorizedAction checks if an error is a ErrLFSUnauthorizedAction.
|
||||
func IsErrLFSUnauthorizedAction(err error) bool {
|
||||
_, ok := err.(ErrLFSUnauthorizedAction)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrLFSUnauthorizedAction) Error() string {
|
||||
if err.Mode == perm.AccessModeWrite {
|
||||
return fmt.Sprintf("User %s doesn't have write access for lfs lock [rid: %d]", err.UserName, err.RepoID)
|
||||
}
|
||||
return fmt.Sprintf("User %s doesn't have read access for lfs lock [rid: %d]", err.UserName, err.RepoID)
|
||||
}
|
||||
|
||||
func (err ErrLFSUnauthorizedAction) Unwrap() error {
|
||||
return util.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// ErrLFSLockAlreadyExist represents a "LFSLockAlreadyExist" kind of error.
|
||||
type ErrLFSLockAlreadyExist struct {
|
||||
RepoID int64
|
||||
@@ -93,12 +68,6 @@ type ErrLFSFileLocked struct {
|
||||
UserName string
|
||||
}
|
||||
|
||||
// IsErrLFSFileLocked checks if an error is a ErrLFSFileLocked.
|
||||
func IsErrLFSFileLocked(err error) bool {
|
||||
_, ok := err.(ErrLFSFileLocked)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrLFSFileLocked) Error() string {
|
||||
return fmt.Sprintf("File is lfs locked [repo: %d, locked by: %s, path: %s]", err.RepoID, err.UserName, err.Path)
|
||||
}
|
||||
|
||||
@@ -11,10 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@@ -71,10 +68,6 @@ func (l *LFSLock) LoadOwner(ctx context.Context) error {
|
||||
// CreateLFSLock creates a new lock.
|
||||
func CreateLFSLock(ctx context.Context, repo *repo_model.Repository, lock *LFSLock) (*LFSLock, error) {
|
||||
return db.WithTx2(ctx, func(ctx context.Context) (*LFSLock, error) {
|
||||
if err := CheckLFSAccessForRepo(ctx, lock.OwnerID, repo, perm.AccessModeWrite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lock.Path = util.PathJoinRel(lock.Path)
|
||||
lock.RepoID = repo.ID
|
||||
|
||||
@@ -165,10 +158,6 @@ func DeleteLFSLockByID(ctx context.Context, id int64, repo *repo_model.Repositor
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := CheckLFSAccessForRepo(ctx, u.ID, repo, perm.AccessModeWrite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !force && u.ID != lock.OwnerID {
|
||||
return nil, errors.New("user doesn't own lock and force flag is not set")
|
||||
}
|
||||
@@ -180,22 +169,3 @@ func DeleteLFSLockByID(ctx context.Context, id int64, repo *repo_model.Repositor
|
||||
return lock, nil
|
||||
})
|
||||
}
|
||||
|
||||
// CheckLFSAccessForRepo check needed access mode base on action
|
||||
func CheckLFSAccessForRepo(ctx context.Context, ownerID int64, repo *repo_model.Repository, mode perm.AccessMode) error {
|
||||
if ownerID == 0 {
|
||||
return ErrLFSUnauthorizedAction{repo.ID, "undefined", mode}
|
||||
}
|
||||
u, err := user_model.GetUserByID(ctx, ownerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, repo, u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !perm.CanAccess(mode, unit.TypeCode) {
|
||||
return ErrLFSUnauthorizedAction{repo.ID, u.DisplayName(), mode}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -417,10 +417,6 @@ func (pr *PullRequest) GetGitHeadRefName() string {
|
||||
return fmt.Sprintf("%s%d/head", git.PullPrefix, pr.Index)
|
||||
}
|
||||
|
||||
func (pr *PullRequest) GetGitHeadBranchRefName() string {
|
||||
return fmt.Sprintf("%s%s", git.BranchPrefix, pr.HeadBranch)
|
||||
}
|
||||
|
||||
// GetReviewCommentsCount returns the number of review comments made on the diff of a PR review (not including comments on commits or issues in a PR)
|
||||
func (pr *PullRequest) GetReviewCommentsCount(ctx context.Context) int {
|
||||
opts := FindCommentsOptions{
|
||||
@@ -646,9 +642,8 @@ func (pr *PullRequest) UpdateCols(ctx context.Context, cols ...string) error {
|
||||
}
|
||||
|
||||
// UpdateColsIfNotMerged updates specific fields of a pull request if it has not been merged
|
||||
func (pr *PullRequest) UpdateColsIfNotMerged(ctx context.Context, cols ...string) error {
|
||||
_, err := db.GetEngine(ctx).Where("id = ? AND has_merged = ?", pr.ID, false).Cols(cols...).Update(pr)
|
||||
return err
|
||||
func (pr *PullRequest) UpdateColsIfNotMerged(ctx context.Context, cols ...string) (int64, error) {
|
||||
return db.GetEngine(ctx).Where("id = ? AND has_merged = ?", pr.ID, false).Cols(cols...).Update(pr)
|
||||
}
|
||||
|
||||
// IsWorkInProgress determine if the Pull Request is a Work In Progress by its title
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
// FIXME: this file shouldn't be in a normal package, it should only be compiled for tests
|
||||
@@ -88,6 +89,16 @@ func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, fu
|
||||
return x, deferFn
|
||||
}
|
||||
|
||||
func LoadTableSchemasMap(t *testing.T, x *xorm.Engine) map[string]*schemas.Table {
|
||||
tables, err := x.DBMetas()
|
||||
require.NoError(t, err)
|
||||
tableMap := make(map[string]*schemas.Table)
|
||||
for _, table := range tables {
|
||||
tableMap[table.Name] = table
|
||||
}
|
||||
return tableMap
|
||||
}
|
||||
|
||||
func MainTest(m *testing.M) {
|
||||
testlogger.Init()
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"code.gitea.io/gitea/models/migrations/v1_23"
|
||||
"code.gitea.io/gitea/models/migrations/v1_24"
|
||||
"code.gitea.io/gitea/models/migrations/v1_25"
|
||||
"code.gitea.io/gitea/models/migrations/v1_26"
|
||||
"code.gitea.io/gitea/models/migrations/v1_6"
|
||||
"code.gitea.io/gitea/models/migrations/v1_7"
|
||||
"code.gitea.io/gitea/models/migrations/v1_8"
|
||||
@@ -379,8 +380,8 @@ func prepareMigrationTasks() []*migration {
|
||||
newMigration(309, "Improve Notification table indices", v1_23.ImproveNotificationTableIndices),
|
||||
newMigration(310, "Add Priority to ProtectedBranch", v1_23.AddPriorityToProtectedBranch),
|
||||
newMigration(311, "Add TimeEstimate to Issue table", v1_23.AddTimeEstimateColumnToIssueTable),
|
||||
|
||||
// Gitea 1.23.0-rc0 ends at migration ID number 311 (database version 312)
|
||||
|
||||
newMigration(312, "Add DeleteBranchAfterMerge to AutoMerge", v1_24.AddDeleteBranchAfterMergeForAutoMerge),
|
||||
newMigration(313, "Move PinOrder from issue table to a new table issue_pin", v1_24.MovePinOrderToTableIssuePin),
|
||||
newMigration(314, "Update OwnerID as zero for repository level action tables", v1_24.UpdateOwnerIDOfRepoLevelActionsTables),
|
||||
@@ -390,10 +391,13 @@ func prepareMigrationTasks() []*migration {
|
||||
newMigration(318, "Add anonymous_access_mode for repo_unit", v1_24.AddRepoUnitAnonymousAccessMode),
|
||||
newMigration(319, "Add ExclusiveOrder to Label table", v1_24.AddExclusiveOrderColumnToLabelTable),
|
||||
newMigration(320, "Migrate two_factor_policy to login_source table", v1_24.MigrateSkipTwoFactor),
|
||||
// Gitea 1.24.0 ends at migration ID number 320 (database version 321)
|
||||
|
||||
// Gitea 1.24.0 ends at database version 321
|
||||
newMigration(321, "Use LONGTEXT for some columns and fix review_state.updated_files column", v1_25.UseLongTextInSomeColumnsAndFixBugs),
|
||||
newMigration(322, "Extend comment tree_path length limit", v1_25.ExtendCommentTreePathLength),
|
||||
// Gitea 1.25.0 ends at migration ID number 322 (database version 323)
|
||||
|
||||
newMigration(323, "Add support for actions concurrency", v1_26.AddActionsConcurrency),
|
||||
}
|
||||
return preparedMigrations
|
||||
}
|
||||
|
||||
@@ -84,17 +84,17 @@ func FixMergeBase(ctx context.Context, x *xorm.Engine) error {
|
||||
|
||||
if !pr.HasMerged {
|
||||
var err error
|
||||
pr.MergeBase, _, err = gitcmd.NewCommand("merge-base").AddDashesAndList(pr.BaseBranch, gitRefName).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
pr.MergeBase, _, err = gitcmd.NewCommand("merge-base").AddDashesAndList(pr.BaseBranch, gitRefName).WithDir(repoPath).RunStdString(ctx)
|
||||
if err != nil {
|
||||
var err2 error
|
||||
pr.MergeBase, _, err2 = gitcmd.NewCommand("rev-parse").AddDynamicArguments(git.BranchPrefix+pr.BaseBranch).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
pr.MergeBase, _, err2 = gitcmd.NewCommand("rev-parse").AddDynamicArguments(git.BranchPrefix + pr.BaseBranch).WithDir(repoPath).RunStdString(ctx)
|
||||
if err2 != nil {
|
||||
log.Error("Unable to get merge base for PR ID %d, Index %d in %s/%s. Error: %v & %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err, err2)
|
||||
continue
|
||||
}
|
||||
}
|
||||
} else {
|
||||
parentsString, _, err := gitcmd.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
parentsString, _, err := gitcmd.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).WithDir(repoPath).RunStdString(ctx)
|
||||
if err != nil {
|
||||
log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
|
||||
continue
|
||||
@@ -108,7 +108,7 @@ func FixMergeBase(ctx context.Context, x *xorm.Engine) error {
|
||||
refs = append(refs, gitRefName)
|
||||
cmd := gitcmd.NewCommand("merge-base").AddDashesAndList(refs...)
|
||||
|
||||
pr.MergeBase, _, err = cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
pr.MergeBase, _, err = cmd.WithDir(repoPath).RunStdString(ctx)
|
||||
if err != nil {
|
||||
log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
|
||||
continue
|
||||
|
||||
@@ -80,7 +80,7 @@ func RefixMergeBase(ctx context.Context, x *xorm.Engine) error {
|
||||
|
||||
gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index)
|
||||
|
||||
parentsString, _, err := gitcmd.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
parentsString, _, err := gitcmd.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).WithDir(repoPath).RunStdString(ctx)
|
||||
if err != nil {
|
||||
log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
|
||||
continue
|
||||
@@ -95,7 +95,7 @@ func RefixMergeBase(ctx context.Context, x *xorm.Engine) error {
|
||||
refs = append(refs, gitRefName)
|
||||
cmd := gitcmd.NewCommand("merge-base").AddDashesAndList(refs...)
|
||||
|
||||
pr.MergeBase, _, err = cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
pr.MergeBase, _, err = cmd.WithDir(repoPath).RunStdString(ctx)
|
||||
if err != nil {
|
||||
log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
|
||||
continue
|
||||
|
||||
@@ -6,11 +6,10 @@ package v1_12
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@@ -85,12 +84,9 @@ func AddCommitDivergenceToPulls(x *xorm.Engine) error {
|
||||
log.Error("Missing base repo with id %d for PR ID %d", pr.BaseRepoID, pr.ID)
|
||||
continue
|
||||
}
|
||||
userPath := filepath.Join(setting.RepoRootPath, strings.ToLower(baseRepo.OwnerName))
|
||||
repoPath := filepath.Join(userPath, strings.ToLower(baseRepo.Name)+".git")
|
||||
|
||||
repoStore := repo_model.StorageRepo(repo_model.RelativePath(baseRepo.OwnerName, baseRepo.Name))
|
||||
gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index)
|
||||
|
||||
divergence, err := git.GetDivergingCommits(graceful.GetManager().HammerContext(), repoPath, pr.BaseBranch, gitRefName)
|
||||
divergence, err := gitrepo.GetDivergingCommits(graceful.GetManager().HammerContext(), repoStore, pr.BaseBranch, gitRefName)
|
||||
if err != nil {
|
||||
log.Warn("Could not recalculate Divergence for pull: %d", pr.ID)
|
||||
pr.CommitsAhead = 0
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_UseLongTextInSomeColumnsAndFixBugs(t *testing.T) {
|
||||
@@ -38,33 +39,26 @@ func Test_UseLongTextInSomeColumnsAndFixBugs(t *testing.T) {
|
||||
type Notice struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Type int
|
||||
Description string `xorm:"LONGTEXT"`
|
||||
Description string `xorm:"TEXT"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
}
|
||||
|
||||
// Prepare and load the testing database
|
||||
x, deferable := base.PrepareTestEnv(t, 0, new(ReviewState), new(PackageProperty), new(Notice))
|
||||
defer deferable()
|
||||
x, deferrable := base.PrepareTestEnv(t, 0, new(ReviewState), new(PackageProperty), new(Notice))
|
||||
defer deferrable()
|
||||
|
||||
assert.NoError(t, UseLongTextInSomeColumnsAndFixBugs(x))
|
||||
require.NoError(t, UseLongTextInSomeColumnsAndFixBugs(x))
|
||||
|
||||
tables, err := x.DBMetas()
|
||||
assert.NoError(t, err)
|
||||
tables := base.LoadTableSchemasMap(t, x)
|
||||
table := tables["review_state"]
|
||||
column := table.GetColumn("updated_files")
|
||||
assert.Equal(t, "LONGTEXT", column.SQLType.Name)
|
||||
|
||||
for _, table := range tables {
|
||||
switch table.Name {
|
||||
case "review_state":
|
||||
column := table.GetColumn("updated_files")
|
||||
assert.NotNil(t, column)
|
||||
assert.Equal(t, "LONGTEXT", column.SQLType.Name)
|
||||
case "package_property":
|
||||
column := table.GetColumn("value")
|
||||
assert.NotNil(t, column)
|
||||
assert.Equal(t, "LONGTEXT", column.SQLType.Name)
|
||||
case "notice":
|
||||
column := table.GetColumn("description")
|
||||
assert.NotNil(t, column)
|
||||
assert.Equal(t, "LONGTEXT", column.SQLType.Name)
|
||||
}
|
||||
}
|
||||
table = tables["package_property"]
|
||||
column = table.GetColumn("value")
|
||||
assert.Equal(t, "LONGTEXT", column.SQLType.Name)
|
||||
|
||||
table = tables["notice"]
|
||||
column = table.GetColumn("description")
|
||||
assert.Equal(t, "LONGTEXT", column.SQLType.Name)
|
||||
}
|
||||
|
||||
34
models/migrations/v1_25/v322_test.go
Normal file
34
models/migrations/v1_25/v322_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_25
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/migrations/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_ExtendCommentTreePathLength(t *testing.T) {
|
||||
if setting.Database.Type.IsSQLite3() {
|
||||
t.Skip("For SQLITE, varchar or char will always be represented as TEXT")
|
||||
}
|
||||
|
||||
type Comment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
TreePath string `xorm:"VARCHAR(255)"`
|
||||
}
|
||||
|
||||
x, deferrable := base.PrepareTestEnv(t, 0, new(Comment))
|
||||
defer deferrable()
|
||||
|
||||
require.NoError(t, ExtendCommentTreePathLength(x))
|
||||
table := base.LoadTableSchemasMap(t, x)["comment"]
|
||||
column := table.GetColumn("tree_path")
|
||||
assert.Contains(t, []string{"NVARCHAR", "VARCHAR"}, column.SQLType.Name)
|
||||
assert.EqualValues(t, 4000, column.Length)
|
||||
}
|
||||
14
models/migrations/v1_26/main_test.go
Normal file
14
models/migrations/v1_26/main_test.go
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_26
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/migrations/base"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
base.MainTest(m)
|
||||
}
|
||||
43
models/migrations/v1_26/v323.go
Normal file
43
models/migrations/v1_26/v323.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_26
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func AddActionsConcurrency(x *xorm.Engine) error {
|
||||
type ActionRun struct {
|
||||
RepoID int64 `xorm:"index(repo_concurrency)"`
|
||||
RawConcurrency string
|
||||
ConcurrencyGroup string `xorm:"index(repo_concurrency) NOT NULL DEFAULT ''"`
|
||||
ConcurrencyCancel bool `xorm:"NOT NULL DEFAULT FALSE"`
|
||||
}
|
||||
|
||||
if _, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||
IgnoreDropIndices: true,
|
||||
}, new(ActionRun)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := x.Sync(new(ActionRun)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type ActionRunJob struct {
|
||||
RepoID int64 `xorm:"index(repo_concurrency)"`
|
||||
RawConcurrency string
|
||||
IsConcurrencyEvaluated bool
|
||||
ConcurrencyGroup string `xorm:"index(repo_concurrency) NOT NULL DEFAULT ''"`
|
||||
ConcurrencyCancel bool `xorm:"NOT NULL DEFAULT FALSE"`
|
||||
}
|
||||
|
||||
if _, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||
IgnoreDropIndices: true,
|
||||
}, new(ActionRunJob)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -5,9 +5,11 @@ package access
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
perm_model "code.gitea.io/gitea/models/perm"
|
||||
@@ -254,6 +256,50 @@ func finalProcessRepoUnitPermission(user *user_model.User, perm *Permission) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetActionsUserRepoPermission returns the actions user permissions to the repository
|
||||
func GetActionsUserRepoPermission(ctx context.Context, repo *repo_model.Repository, actionsUser *user_model.User, taskID int64) (perm Permission, err error) {
|
||||
if actionsUser.ID != user_model.ActionsUserID {
|
||||
return perm, errors.New("api GetActionsUserRepoPermission can only be called by the actions user")
|
||||
}
|
||||
task, err := actions_model.GetTaskByID(ctx, taskID)
|
||||
if err != nil {
|
||||
return perm, err
|
||||
}
|
||||
|
||||
var accessMode perm_model.AccessMode
|
||||
if task.RepoID != repo.ID {
|
||||
taskRepo, exist, err := db.GetByID[repo_model.Repository](ctx, task.RepoID)
|
||||
if err != nil || !exist {
|
||||
return perm, err
|
||||
}
|
||||
actionsCfg := repo.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
|
||||
if !actionsCfg.IsCollaborativeOwner(taskRepo.OwnerID) || !taskRepo.IsPrivate {
|
||||
// The task repo can access the current repo only if the task repo is private and
|
||||
// the owner of the task repo is a collaborative owner of the current repo.
|
||||
// FIXME should owner's visibility also be considered here?
|
||||
|
||||
// check permission like simple user but limit to read-only
|
||||
perm, err = GetUserRepoPermission(ctx, repo, user_model.NewActionsUser())
|
||||
if err != nil {
|
||||
return perm, err
|
||||
}
|
||||
perm.AccessMode = min(perm.AccessMode, perm_model.AccessModeRead)
|
||||
return perm, nil
|
||||
}
|
||||
accessMode = perm_model.AccessModeRead
|
||||
} else if task.IsForkPullRequest {
|
||||
accessMode = perm_model.AccessModeRead
|
||||
} else {
|
||||
accessMode = perm_model.AccessModeWrite
|
||||
}
|
||||
|
||||
if err := repo.LoadUnits(ctx); err != nil {
|
||||
return perm, err
|
||||
}
|
||||
perm.SetUnitsWithDefaultAccessMode(repo.Units, accessMode)
|
||||
return perm, nil
|
||||
}
|
||||
|
||||
// GetUserRepoPermission returns the user permissions to the repository
|
||||
func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) {
|
||||
defer func() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git
|
||||
package repo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
@@ -27,11 +26,46 @@ const (
|
||||
ArchiverReady // it's ready
|
||||
)
|
||||
|
||||
// ArchiveType archive types
|
||||
type ArchiveType int
|
||||
|
||||
const (
|
||||
ArchiveUnknown ArchiveType = iota
|
||||
ArchiveZip // 1
|
||||
ArchiveTarGz // 2
|
||||
ArchiveBundle // 3
|
||||
)
|
||||
|
||||
// String converts an ArchiveType to string: the extension of the archive file without prefix dot
|
||||
func (a ArchiveType) String() string {
|
||||
switch a {
|
||||
case ArchiveZip:
|
||||
return "zip"
|
||||
case ArchiveTarGz:
|
||||
return "tar.gz"
|
||||
case ArchiveBundle:
|
||||
return "bundle"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func SplitArchiveNameType(s string) (string, ArchiveType) {
|
||||
switch {
|
||||
case strings.HasSuffix(s, ".zip"):
|
||||
return strings.TrimSuffix(s, ".zip"), ArchiveZip
|
||||
case strings.HasSuffix(s, ".tar.gz"):
|
||||
return strings.TrimSuffix(s, ".tar.gz"), ArchiveTarGz
|
||||
case strings.HasSuffix(s, ".bundle"):
|
||||
return strings.TrimSuffix(s, ".bundle"), ArchiveBundle
|
||||
}
|
||||
return s, ArchiveUnknown
|
||||
}
|
||||
|
||||
// RepoArchiver represents all archivers
|
||||
type RepoArchiver struct { //revive:disable-line:exported
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"index unique(s)"`
|
||||
Type git.ArchiveType `xorm:"unique(s)"`
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"index unique(s)"`
|
||||
Type ArchiveType `xorm:"unique(s)"`
|
||||
Status ArchiverStatus
|
||||
CommitID string `xorm:"VARCHAR(64) unique(s)"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL created"`
|
||||
@@ -56,15 +90,15 @@ func repoArchiverForRelativePath(relativePath string) (*RepoArchiver, error) {
|
||||
if err != nil {
|
||||
return nil, util.NewInvalidArgumentErrorf("invalid storage path: invalid repo id")
|
||||
}
|
||||
commitID, archiveType := git.SplitArchiveNameType(parts[2])
|
||||
if archiveType == git.ArchiveUnknown {
|
||||
commitID, archiveType := SplitArchiveNameType(parts[2])
|
||||
if archiveType == ArchiveUnknown {
|
||||
return nil, util.NewInvalidArgumentErrorf("invalid storage path: invalid archive type")
|
||||
}
|
||||
return &RepoArchiver{RepoID: repoID, CommitID: commitID, Type: archiveType}, nil
|
||||
}
|
||||
|
||||
// GetRepoArchiver get an archiver
|
||||
func GetRepoArchiver(ctx context.Context, repoID int64, tp git.ArchiveType, commitID string) (*RepoArchiver, error) {
|
||||
func GetRepoArchiver(ctx context.Context, repoID int64, tp ArchiveType, commitID string) (*RepoArchiver, error) {
|
||||
var archiver RepoArchiver
|
||||
has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).And("`type`=?", tp).And("commit_id=?", commitID).Get(&archiver)
|
||||
if err != nil {
|
||||
|
||||
@@ -229,10 +229,6 @@ func RelativePath(ownerName, repoName string) string {
|
||||
return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".git"
|
||||
}
|
||||
|
||||
func RelativeWikiPath(ownerName, repoName string) string {
|
||||
return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".wiki.git"
|
||||
}
|
||||
|
||||
// RelativePath should be an unix style path like username/reponame.git
|
||||
func (repo *Repository) RelativePath() string {
|
||||
return RelativePath(repo.OwnerName, repo.Name)
|
||||
@@ -245,12 +241,6 @@ func (sr StorageRepo) RelativePath() string {
|
||||
return string(sr)
|
||||
}
|
||||
|
||||
// WikiStorageRepo returns the storage repo for the wiki
|
||||
// The wiki repository should have the same object format as the code repository
|
||||
func (repo *Repository) WikiStorageRepo() StorageRepo {
|
||||
return StorageRepo(RelativeWikiPath(repo.OwnerName, repo.Name))
|
||||
}
|
||||
|
||||
// SanitizedOriginalURL returns a sanitized OriginalURL
|
||||
func (repo *Repository) SanitizedOriginalURL() string {
|
||||
if repo.OriginalURL == "" {
|
||||
@@ -605,7 +595,7 @@ func (repo *Repository) IsGenerated() bool {
|
||||
|
||||
// RepoPath returns repository path by given user and repository name.
|
||||
func RepoPath(userName, repoName string) string { //revive:disable-line:exported
|
||||
return filepath.Join(user_model.UserPath(userName), strings.ToLower(repoName)+".git")
|
||||
return filepath.Join(setting.RepoRootPath, filepath.Clean(strings.ToLower(userName)), filepath.Clean(strings.ToLower(repoName)+".git"))
|
||||
}
|
||||
|
||||
// RepoPath returns the repository path
|
||||
@@ -879,16 +869,6 @@ func GetRepositoriesMapByIDs(ctx context.Context, ids []int64) (map[int64]*Repos
|
||||
return repos, db.GetEngine(ctx).In("id", ids).Find(&repos)
|
||||
}
|
||||
|
||||
// IsRepositoryModelOrDirExist returns true if the repository with given name under user has already existed.
|
||||
func IsRepositoryModelOrDirExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) {
|
||||
has, err := IsRepositoryModelExist(ctx, u, repoName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
isDir, err := util.IsDir(RepoPath(u.Name, repoName))
|
||||
return has || isDir, err
|
||||
}
|
||||
|
||||
func IsRepositoryModelExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) {
|
||||
return db.GetEngine(ctx).Get(&Repository{
|
||||
OwnerID: u.ID,
|
||||
|
||||
@@ -170,6 +170,9 @@ func (cfg *PullRequestsConfig) GetDefaultMergeStyle() MergeStyle {
|
||||
|
||||
type ActionsConfig struct {
|
||||
DisabledWorkflows []string
|
||||
// CollaborativeOwnerIDs is a list of owner IDs used to share actions from private repos.
|
||||
// Only workflows from the private repos whose owners are in CollaborativeOwnerIDs can access the current repo's actions.
|
||||
CollaborativeOwnerIDs []int64
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) EnableWorkflow(file string) {
|
||||
@@ -192,6 +195,20 @@ func (cfg *ActionsConfig) DisableWorkflow(file string) {
|
||||
cfg.DisabledWorkflows = append(cfg.DisabledWorkflows, file)
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) AddCollaborativeOwner(ownerID int64) {
|
||||
if !slices.Contains(cfg.CollaborativeOwnerIDs, ownerID) {
|
||||
cfg.CollaborativeOwnerIDs = append(cfg.CollaborativeOwnerIDs, ownerID)
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) RemoveCollaborativeOwner(ownerID int64) {
|
||||
cfg.CollaborativeOwnerIDs = util.SliceRemoveAll(cfg.CollaborativeOwnerIDs, ownerID)
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) IsCollaborativeOwner(ownerID int64) bool {
|
||||
return slices.Contains(cfg.CollaborativeOwnerIDs, ownerID)
|
||||
}
|
||||
|
||||
// FromDB fills up a ActionsConfig from serialized format.
|
||||
func (cfg *ActionsConfig) FromDB(bs []byte) error {
|
||||
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
|
||||
|
||||
@@ -9,8 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
@@ -106,35 +104,6 @@ func (err ErrRepoFilesAlreadyExist) Unwrap() error {
|
||||
return util.ErrAlreadyExist
|
||||
}
|
||||
|
||||
// CheckCreateRepository check if doer could create a repository in new owner
|
||||
func CheckCreateRepository(ctx context.Context, doer, owner *user_model.User, name string, overwriteOrAdopt bool) error {
|
||||
if !doer.CanCreateRepoIn(owner) {
|
||||
return ErrReachLimitOfRepo{owner.MaxRepoCreation}
|
||||
}
|
||||
|
||||
if err := IsUsableRepoName(name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
has, err := IsRepositoryModelOrDirExist(ctx, owner, name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("IsRepositoryExist: %w", err)
|
||||
} else if has {
|
||||
return ErrRepoAlreadyExist{owner.Name, name}
|
||||
}
|
||||
|
||||
repoPath := RepoPath(owner.Name, name)
|
||||
isExist, err := util.IsExist(repoPath)
|
||||
if err != nil {
|
||||
log.Error("Unable to check if %s exists. Error: %v", repoPath, err)
|
||||
return err
|
||||
}
|
||||
if !overwriteOrAdopt && isExist {
|
||||
return ErrRepoFilesAlreadyExist{owner.Name, name}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateRepoSize updates the repository size, calculating it using getDirectorySize
|
||||
func UpdateRepoSize(ctx context.Context, repoID, gitSize, lfsSize int64) error {
|
||||
_, err := db.GetEngine(ctx).ID(repoID).Cols("size", "git_size", "lfs_size").NoAutoTime().Update(&Repository{
|
||||
|
||||
@@ -7,7 +7,6 @@ package repo
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
@@ -76,12 +75,12 @@ func (repo *Repository) WikiCloneLink(ctx context.Context, doer *user_model.User
|
||||
return repo.cloneLink(ctx, doer, repo.Name+".wiki")
|
||||
}
|
||||
|
||||
// WikiPath returns wiki data path by given user and repository name.
|
||||
func WikiPath(userName, repoName string) string {
|
||||
return filepath.Join(user_model.UserPath(userName), strings.ToLower(repoName)+".wiki.git")
|
||||
func RelativeWikiPath(ownerName, repoName string) string {
|
||||
return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".wiki.git"
|
||||
}
|
||||
|
||||
// WikiPath returns wiki data path for given repository.
|
||||
func (repo *Repository) WikiPath() string {
|
||||
return WikiPath(repo.OwnerName, repo.Name)
|
||||
// WikiStorageRepo returns the storage repo for the wiki
|
||||
// The wiki repository should have the same object format as the code repository
|
||||
func (repo *Repository) WikiStorageRepo() StorageRepo {
|
||||
return StorageRepo(RelativeWikiPath(repo.OwnerName, repo.Name))
|
||||
}
|
||||
|
||||
@@ -4,12 +4,10 @@
|
||||
package repo_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -23,15 +21,10 @@ func TestRepository_WikiCloneLink(t *testing.T) {
|
||||
assert.Equal(t, "https://try.gitea.io/user2/repo1.wiki.git", cloneLink.HTTPS)
|
||||
}
|
||||
|
||||
func TestWikiPath(t *testing.T) {
|
||||
func TestRepository_RelativeWikiPath(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
expected := filepath.Join(setting.RepoRootPath, "user2/repo1.wiki.git")
|
||||
assert.Equal(t, expected, repo_model.WikiPath("user2", "repo1"))
|
||||
}
|
||||
|
||||
func TestRepository_WikiPath(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
expected := filepath.Join(setting.RepoRootPath, "user2/repo1.wiki.git")
|
||||
assert.Equal(t, expected, repo.WikiPath())
|
||||
assert.Equal(t, "user2/repo1.wiki.git", repo_model.RelativeWikiPath(repo.OwnerName, repo.Name))
|
||||
assert.Equal(t, "user2/repo1.wiki.git", repo.WikiStorageRepo().RelativePath())
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package user
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
@@ -17,12 +18,29 @@ import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// AdminUserOrderByMap represents all possible admin user search orders
|
||||
// This should only be used for admin API endpoints as we should not expose "updated" ordering which could expose recent user activity including logins.
|
||||
var AdminUserOrderByMap = map[string]map[string]db.SearchOrderBy{
|
||||
"asc": {
|
||||
"name": db.SearchOrderByAlphabetically,
|
||||
"created": db.SearchOrderByOldest,
|
||||
"updated": db.SearchOrderByLeastUpdated,
|
||||
"id": db.SearchOrderByID,
|
||||
},
|
||||
"desc": {
|
||||
"name": db.SearchOrderByAlphabeticallyReverse,
|
||||
"created": db.SearchOrderByNewest,
|
||||
"updated": db.SearchOrderByRecentUpdated,
|
||||
"id": db.SearchOrderByIDReverse,
|
||||
},
|
||||
}
|
||||
|
||||
// SearchUserOptions contains the options for searching
|
||||
type SearchUserOptions struct {
|
||||
db.ListOptions
|
||||
|
||||
Keyword string
|
||||
Type UserType
|
||||
Types []UserType
|
||||
UID int64
|
||||
LoginName string // this option should be used only for admin user
|
||||
SourceID int64 // this option should be used only for admin user
|
||||
@@ -43,16 +61,16 @@ type SearchUserOptions struct {
|
||||
|
||||
func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Session {
|
||||
var cond builder.Cond
|
||||
cond = builder.Eq{"type": opts.Type}
|
||||
cond = builder.In("type", opts.Types)
|
||||
if opts.IncludeReserved {
|
||||
switch opts.Type {
|
||||
case UserTypeIndividual:
|
||||
switch {
|
||||
case slices.Contains(opts.Types, UserTypeIndividual):
|
||||
cond = cond.Or(builder.Eq{"type": UserTypeUserReserved}).Or(
|
||||
builder.Eq{"type": UserTypeBot},
|
||||
).Or(
|
||||
builder.Eq{"type": UserTypeRemoteUser},
|
||||
)
|
||||
case UserTypeOrganization:
|
||||
case slices.Contains(opts.Types, UserTypeOrganization):
|
||||
cond = cond.Or(builder.Eq{"type": UserTypeOrganizationReserved})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,8 +249,13 @@ func (u *User) MaxCreationLimit() int {
|
||||
}
|
||||
|
||||
// CanCreateRepoIn checks whether the doer(u) can create a repository in the owner
|
||||
// NOTE: functions calling this assume a failure due to repository count limit; it ONLY checks the repo number LIMIT, if new checks are added, those functions should be revised
|
||||
// NOTE: functions calling this assume a failure due to repository count limit, or the owner is not a real user.
|
||||
// It ONLY checks the repo number LIMIT or whether owner user is real. If new checks are added, those functions should be revised.
|
||||
// TODO: the callers can only return ErrReachLimitOfRepo, need to fine tune to support other error types in the future.
|
||||
func (u *User) CanCreateRepoIn(owner *User) bool {
|
||||
if u.ID <= 0 || owner.ID <= 0 {
|
||||
return false // fake user like Ghost or Actions user
|
||||
}
|
||||
if u.IsAdmin {
|
||||
return true
|
||||
}
|
||||
@@ -980,7 +985,7 @@ func GetInactiveUsers(ctx context.Context, olderThan time.Duration) ([]*User, er
|
||||
|
||||
// UserPath returns the path absolute path of user repositories.
|
||||
func UserPath(userName string) string { //revive:disable-line:exported
|
||||
return filepath.Join(setting.RepoRootPath, strings.ToLower(userName))
|
||||
return filepath.Join(setting.RepoRootPath, filepath.Clean(strings.ToLower(userName)))
|
||||
}
|
||||
|
||||
// GetUserByID returns the user object by given ID if exists.
|
||||
@@ -1257,8 +1262,8 @@ func GetUserByEmail(ctx context.Context, email string) (*User, error) {
|
||||
}
|
||||
|
||||
// Finally, if email address is the protected email address:
|
||||
if strings.HasSuffix(email, "@"+setting.Service.NoReplyAddress) {
|
||||
username := strings.TrimSuffix(email, "@"+setting.Service.NoReplyAddress)
|
||||
if before, ok := strings.CutSuffix(email, "@"+setting.Service.NoReplyAddress); ok {
|
||||
username := before
|
||||
user := &User{}
|
||||
has, err := db.GetEngine(ctx).Where("lower_name=?", username).Get(user)
|
||||
if err != nil {
|
||||
@@ -1444,3 +1449,15 @@ func DisabledFeaturesWithLoginType(user *User) *container.Set[string] {
|
||||
}
|
||||
return &setting.Admin.UserDisabledFeatures
|
||||
}
|
||||
|
||||
// GetUserOrOrgIDByName returns the id for a user or an org by name
|
||||
func GetUserOrOrgIDByName(ctx context.Context, name string) (int64, error) {
|
||||
var id int64
|
||||
has, err := db.GetEngine(ctx).Table("user").Where("name = ?", name).Cols("id").Get(&id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if !has {
|
||||
return 0, fmt.Errorf("user or org with name %s: %w", name, util.ErrNotExist)
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
@@ -48,17 +48,16 @@ func IsGiteaActionsUserName(name string) bool {
|
||||
// NewActionsUser creates and returns a fake user for running the actions.
|
||||
func NewActionsUser() *User {
|
||||
return &User{
|
||||
ID: ActionsUserID,
|
||||
Name: ActionsUserName,
|
||||
LowerName: ActionsUserName,
|
||||
IsActive: true,
|
||||
FullName: "Gitea Actions",
|
||||
Email: ActionsUserEmail,
|
||||
KeepEmailPrivate: true,
|
||||
LoginName: ActionsUserName,
|
||||
Type: UserTypeBot,
|
||||
AllowCreateOrganization: true,
|
||||
Visibility: structs.VisibleTypePublic,
|
||||
ID: ActionsUserID,
|
||||
Name: ActionsUserName,
|
||||
LowerName: ActionsUserName,
|
||||
IsActive: true,
|
||||
FullName: "Gitea Actions",
|
||||
Email: ActionsUserEmail,
|
||||
KeepEmailPrivate: true,
|
||||
LoginName: ActionsUserName,
|
||||
Type: UserTypeBot,
|
||||
Visibility: structs.VisibleTypePublic,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ func TestSearchUsers(t *testing.T) {
|
||||
|
||||
// test orgs
|
||||
testOrgSuccess := func(opts user_model.SearchUserOptions, expectedOrgIDs []int64) {
|
||||
opts.Type = user_model.UserTypeOrganization
|
||||
opts.Types = []user_model.UserType{user_model.UserTypeOrganization}
|
||||
testSuccess(opts, expectedOrgIDs)
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ func TestSearchUsers(t *testing.T) {
|
||||
|
||||
// test users
|
||||
testUserSuccess := func(opts user_model.SearchUserOptions, expectedUserIDs []int64) {
|
||||
opts.Type = user_model.UserTypeIndividual
|
||||
opts.Types = []user_model.UserType{user_model.UserTypeIndividual}
|
||||
testSuccess(opts, expectedUserIDs)
|
||||
}
|
||||
|
||||
@@ -648,33 +648,36 @@ func TestGetInactiveUsers(t *testing.T) {
|
||||
func TestCanCreateRepo(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Repository.MaxCreationLimit)()
|
||||
const noLimit = -1
|
||||
doerNormal := &user_model.User{}
|
||||
doerAdmin := &user_model.User{IsAdmin: true}
|
||||
doerActions := user_model.NewActionsUser()
|
||||
doerNormal := &user_model.User{ID: 2}
|
||||
doerAdmin := &user_model.User{ID: 1, IsAdmin: true}
|
||||
t.Run("NoGlobalLimit", func(t *testing.T) {
|
||||
setting.Repository.MaxCreationLimit = noLimit
|
||||
|
||||
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: noLimit}))
|
||||
assert.False(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 0}))
|
||||
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 100}))
|
||||
assert.False(t, doerNormal.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: 0}))
|
||||
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: 100}))
|
||||
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: noLimit}))
|
||||
|
||||
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: noLimit}))
|
||||
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 0}))
|
||||
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 100}))
|
||||
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: 0}))
|
||||
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: 100}))
|
||||
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: noLimit}))
|
||||
assert.False(t, doerActions.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: noLimit}))
|
||||
assert.False(t, doerAdmin.CanCreateRepoIn(doerActions))
|
||||
})
|
||||
|
||||
t.Run("GlobalLimit50", func(t *testing.T) {
|
||||
setting.Repository.MaxCreationLimit = 50
|
||||
|
||||
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: noLimit}))
|
||||
assert.False(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 60, MaxRepoCreation: noLimit})) // limited by global limit
|
||||
assert.False(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 0}))
|
||||
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 100}))
|
||||
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 60, MaxRepoCreation: 100}))
|
||||
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: noLimit}))
|
||||
assert.False(t, doerNormal.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 60, MaxRepoCreation: noLimit})) // limited by global limit
|
||||
assert.False(t, doerNormal.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: 0}))
|
||||
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: 100}))
|
||||
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 60, MaxRepoCreation: 100}))
|
||||
|
||||
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: noLimit}))
|
||||
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 60, MaxRepoCreation: noLimit}))
|
||||
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 0}))
|
||||
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 100}))
|
||||
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 60, MaxRepoCreation: 100}))
|
||||
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: noLimit}))
|
||||
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 60, MaxRepoCreation: noLimit}))
|
||||
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: 0}))
|
||||
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: 100}))
|
||||
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 60, MaxRepoCreation: 100}))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -365,11 +365,11 @@ func GenerateEmbedBindata(fsRootPath, outputFile string) error {
|
||||
if err = embedFiles(meta.Root, fsRootPath, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
jsonBuf, err := json.Marshal(meta) // can't use json.NewEncoder here because it writes extra EOL
|
||||
jsonBuf, err := json.Marshal(meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = output.Write([]byte{'\n'})
|
||||
_, err = output.Write(jsonBuf)
|
||||
_, err = output.Write(bytes.TrimSpace(jsonBuf))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -61,17 +61,11 @@ func NewArgon2Hasher(config string) *Argon2Hasher {
|
||||
return nil
|
||||
}
|
||||
|
||||
parsed, err := parseUIntParam(vals[0], "time", "argon2", config, nil)
|
||||
hasher.time = uint32(parsed)
|
||||
|
||||
parsed, err = parseUIntParam(vals[1], "memory", "argon2", config, err)
|
||||
hasher.memory = uint32(parsed)
|
||||
|
||||
parsed, err = parseUIntParam(vals[2], "threads", "argon2", config, err)
|
||||
hasher.threads = uint8(parsed)
|
||||
|
||||
parsed, err = parseUIntParam(vals[3], "keyLen", "argon2", config, err)
|
||||
hasher.keyLen = uint32(parsed)
|
||||
var err error
|
||||
hasher.time, err = parseUintParam[uint32](vals[0], "time", "argon2", config, nil)
|
||||
hasher.memory, err = parseUintParam[uint32](vals[1], "memory", "argon2", config, err)
|
||||
hasher.threads, err = parseUintParam[uint8](vals[2], "threads", "argon2", config, err)
|
||||
hasher.keyLen, err = parseUintParam[uint32](vals[3], "keyLen", "argon2", config, err)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
func parseIntParam(value, param, algorithmName, config string, previousErr error) (int, error) {
|
||||
@@ -18,11 +19,12 @@ func parseIntParam(value, param, algorithmName, config string, previousErr error
|
||||
return parsed, previousErr // <- Keep the previous error as this function should still return an error once everything has been checked if any call failed
|
||||
}
|
||||
|
||||
func parseUIntParam(value, param, algorithmName, config string, previousErr error) (uint64, error) { //nolint:unparam // algorithmName is always argon2
|
||||
parsed, err := strconv.ParseUint(value, 10, 64)
|
||||
func parseUintParam[T uint32 | uint8](value, param, algorithmName, config string, previousErr error) (ret T, _ error) {
|
||||
_, isUint32 := any(ret).(uint32)
|
||||
parsed, err := strconv.ParseUint(value, 10, util.Iif(isUint32, 32, 8))
|
||||
if err != nil {
|
||||
log.Error("invalid integer for %s representation in %s hash spec %s", param, algorithmName, config)
|
||||
return 0, err
|
||||
}
|
||||
return parsed, previousErr // <- Keep the previous error as this function should still return an error once everything has been checked if any call failed
|
||||
return T(parsed), previousErr // <- Keep the previous error as this function should still return an error once everything has been checked if any call failed
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ func newRequest(ctx context.Context, method, url string, body io.ReadCloser) (*h
|
||||
// Adding padding will make requests more secure, however is also slower
|
||||
// because artificial responses will be added to the response
|
||||
// For more information, see https://www.troyhunt.com/enhancing-pwned-passwords-privacy-with-padding/
|
||||
func (c *Client) CheckPassword(pw string, padding bool) (int, error) {
|
||||
func (c *Client) CheckPassword(pw string, padding bool) (int64, error) {
|
||||
if pw == "" {
|
||||
return -1, ErrEmptyPassword
|
||||
}
|
||||
@@ -111,7 +111,7 @@ func (c *Client) CheckPassword(pw string, padding bool) (int, error) {
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return int(count), nil
|
||||
return count, nil
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
|
||||
@@ -37,25 +37,25 @@ func TestPassword(t *testing.T) {
|
||||
|
||||
count, err := client.CheckPassword("", false)
|
||||
assert.ErrorIs(t, err, ErrEmptyPassword, "blank input should return ErrEmptyPassword")
|
||||
assert.Equal(t, -1, count)
|
||||
assert.EqualValues(t, -1, count)
|
||||
|
||||
count, err = client.CheckPassword("pwned", false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, count)
|
||||
assert.EqualValues(t, 1, count)
|
||||
|
||||
count, err = client.CheckPassword("notpwned", false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, count)
|
||||
assert.EqualValues(t, 0, count)
|
||||
|
||||
count, err = client.CheckPassword("paddedpwned", true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, count)
|
||||
assert.EqualValues(t, 1, count)
|
||||
|
||||
count, err = client.CheckPassword("paddednotpwned", true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, count)
|
||||
assert.EqualValues(t, 0, count)
|
||||
|
||||
count, err = client.CheckPassword("paddednotpwnedzero", true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, count)
|
||||
assert.EqualValues(t, 0, count)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ var WebAuthn *webauthn.WebAuthn
|
||||
|
||||
// Init initializes the WebAuthn instance from the config.
|
||||
func Init() {
|
||||
gob.Register(&webauthn.SessionData{})
|
||||
gob.Register(&webauthn.SessionData{}) // TODO: CHI-SESSION-GOB-REGISTER.
|
||||
|
||||
appURL, _ := protocol.FullyQualifiedOrigin(setting.AppURL)
|
||||
|
||||
|
||||
@@ -41,8 +41,8 @@ func naturalSortAdvance(str string, pos int) (end int, isNumber bool) {
|
||||
return end, isNumber
|
||||
}
|
||||
|
||||
// NaturalSortLess compares two strings so that they could be sorted in natural order
|
||||
func NaturalSortLess(s1, s2 string) bool {
|
||||
// NaturalSortCompare compares two strings so that they could be sorted in natural order
|
||||
func NaturalSortCompare(s1, s2 string) int {
|
||||
// There is a bug in Golang's collate package: https://github.com/golang/go/issues/67997
|
||||
// text/collate: CompareString(collate.Numeric) returns wrong result for "0.0" vs "1.0" #67997
|
||||
// So we need to handle the number parts by ourselves
|
||||
@@ -55,16 +55,16 @@ func NaturalSortLess(s1, s2 string) bool {
|
||||
if isNum1 && isNum2 {
|
||||
if part1 != part2 {
|
||||
if len(part1) != len(part2) {
|
||||
return len(part1) < len(part2)
|
||||
return len(part1) - len(part2)
|
||||
}
|
||||
return part1 < part2
|
||||
return c.CompareString(part1, part2)
|
||||
}
|
||||
} else {
|
||||
if cmp := c.CompareString(part1, part2); cmp != 0 {
|
||||
return cmp < 0
|
||||
return cmp
|
||||
}
|
||||
}
|
||||
pos1, pos2 = end1, end2
|
||||
}
|
||||
return len(s1) < len(s2)
|
||||
return len(s1) - len(s2)
|
||||
}
|
||||
|
||||
@@ -11,12 +11,10 @@ import (
|
||||
|
||||
func TestNaturalSortLess(t *testing.T) {
|
||||
testLess := func(s1, s2 string) {
|
||||
assert.True(t, NaturalSortLess(s1, s2), "s1<s2 should be true: s1=%q, s2=%q", s1, s2)
|
||||
assert.False(t, NaturalSortLess(s2, s1), "s2<s1 should be false: s1=%q, s2=%q", s1, s2)
|
||||
assert.Negative(t, NaturalSortCompare(s1, s2), "s1<s2 should be true: s1=%q, s2=%q", s1, s2)
|
||||
}
|
||||
testEqual := func(s1, s2 string) {
|
||||
assert.False(t, NaturalSortLess(s1, s2), "s1<s2 should be false: s1=%q, s2=%q", s1, s2)
|
||||
assert.False(t, NaturalSortLess(s2, s1), "s2<s1 should be false: s1=%q, s2=%q", s1, s2)
|
||||
assert.Zero(t, NaturalSortCompare(s1, s2), "s1<s2 should be false: s1=%q, s2=%q", s1, s2)
|
||||
}
|
||||
|
||||
testEqual("", "")
|
||||
|
||||
@@ -5,12 +5,10 @@ package charset
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
@@ -23,60 +21,39 @@ import (
|
||||
var UTF8BOM = []byte{'\xef', '\xbb', '\xbf'}
|
||||
|
||||
type ConvertOpts struct {
|
||||
KeepBOM bool
|
||||
KeepBOM bool
|
||||
ErrorReplacement []byte
|
||||
ErrorReturnOrigin bool
|
||||
}
|
||||
|
||||
var ToUTF8WithFallbackReaderPrefetchSize = 16 * 1024
|
||||
|
||||
// ToUTF8WithFallbackReader detects the encoding of content and converts to UTF-8 reader if possible
|
||||
func ToUTF8WithFallbackReader(rd io.Reader, opts ConvertOpts) io.Reader {
|
||||
buf := make([]byte, 2048)
|
||||
buf := make([]byte, ToUTF8WithFallbackReaderPrefetchSize)
|
||||
n, err := util.ReadAtMost(rd, buf)
|
||||
if err != nil {
|
||||
return io.MultiReader(bytes.NewReader(MaybeRemoveBOM(buf[:n], opts)), rd)
|
||||
}
|
||||
|
||||
charsetLabel, err := DetectEncoding(buf[:n])
|
||||
if err != nil || charsetLabel == "UTF-8" {
|
||||
return io.MultiReader(bytes.NewReader(MaybeRemoveBOM(buf[:n], opts)), rd)
|
||||
}
|
||||
|
||||
encoding, _ := charset.Lookup(charsetLabel)
|
||||
if encoding == nil {
|
||||
// read error occurs, don't do any processing
|
||||
return io.MultiReader(bytes.NewReader(buf[:n]), rd)
|
||||
}
|
||||
|
||||
return transform.NewReader(
|
||||
io.MultiReader(
|
||||
bytes.NewReader(MaybeRemoveBOM(buf[:n], opts)),
|
||||
rd,
|
||||
),
|
||||
encoding.NewDecoder(),
|
||||
)
|
||||
}
|
||||
|
||||
// ToUTF8 converts content to UTF8 encoding
|
||||
func ToUTF8(content []byte, opts ConvertOpts) (string, error) {
|
||||
charsetLabel, err := DetectEncoding(content)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if charsetLabel == "UTF-8" {
|
||||
return string(MaybeRemoveBOM(content, opts)), nil
|
||||
charsetLabel, _ := DetectEncoding(buf[:n])
|
||||
if charsetLabel == "UTF-8" {
|
||||
// is utf-8, try to remove BOM and read it as-is
|
||||
return io.MultiReader(bytes.NewReader(maybeRemoveBOM(buf[:n], opts)), rd)
|
||||
}
|
||||
|
||||
encoding, _ := charset.Lookup(charsetLabel)
|
||||
if encoding == nil {
|
||||
return string(content), fmt.Errorf("Unknown encoding: %s", charsetLabel)
|
||||
// unknown charset, don't do any processing
|
||||
return io.MultiReader(bytes.NewReader(buf[:n]), rd)
|
||||
}
|
||||
|
||||
// If there is an error, we concatenate the nicely decoded part and the
|
||||
// original left over. This way we won't lose much data.
|
||||
result, n, err := transform.Bytes(encoding.NewDecoder(), content)
|
||||
if err != nil {
|
||||
result = append(result, content[n:]...)
|
||||
}
|
||||
|
||||
result = MaybeRemoveBOM(result, opts)
|
||||
|
||||
return string(result), err
|
||||
// convert from charset to utf-8
|
||||
return transform.NewReader(
|
||||
io.MultiReader(bytes.NewReader(buf[:n]), rd),
|
||||
encoding.NewDecoder(),
|
||||
)
|
||||
}
|
||||
|
||||
// ToUTF8WithFallback detects the encoding of content and converts to UTF-8 if possible
|
||||
@@ -85,73 +62,84 @@ func ToUTF8WithFallback(content []byte, opts ConvertOpts) []byte {
|
||||
return bs
|
||||
}
|
||||
|
||||
// ToUTF8DropErrors makes sure the return string is valid utf-8; attempts conversion if possible
|
||||
func ToUTF8DropErrors(content []byte, opts ConvertOpts) []byte {
|
||||
charsetLabel, err := DetectEncoding(content)
|
||||
if err != nil || charsetLabel == "UTF-8" {
|
||||
return MaybeRemoveBOM(content, opts)
|
||||
func ToUTF8DropErrors(content []byte) []byte {
|
||||
return ToUTF8(content, ConvertOpts{ErrorReplacement: []byte{' '}})
|
||||
}
|
||||
|
||||
func ToUTF8(content []byte, opts ConvertOpts) []byte {
|
||||
charsetLabel, _ := DetectEncoding(content)
|
||||
if charsetLabel == "UTF-8" {
|
||||
return maybeRemoveBOM(content, opts)
|
||||
}
|
||||
|
||||
encoding, _ := charset.Lookup(charsetLabel)
|
||||
if encoding == nil {
|
||||
setting.PanicInDevOrTesting("unsupported detected charset %q, it shouldn't happen", charsetLabel)
|
||||
return content
|
||||
}
|
||||
|
||||
// We ignore any non-decodable parts from the file.
|
||||
// Some parts might be lost
|
||||
var decoded []byte
|
||||
decoder := encoding.NewDecoder()
|
||||
idx := 0
|
||||
for {
|
||||
for idx < len(content) {
|
||||
result, n, err := transform.Bytes(decoder, content[idx:])
|
||||
decoded = append(decoded, result...)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
decoded = append(decoded, ' ')
|
||||
idx = idx + n + 1
|
||||
if idx >= len(content) {
|
||||
break
|
||||
if opts.ErrorReturnOrigin {
|
||||
return content
|
||||
}
|
||||
if opts.ErrorReplacement == nil {
|
||||
decoded = append(decoded, content[idx+n])
|
||||
} else {
|
||||
decoded = append(decoded, opts.ErrorReplacement...)
|
||||
}
|
||||
idx += n + 1
|
||||
}
|
||||
|
||||
return MaybeRemoveBOM(decoded, opts)
|
||||
return maybeRemoveBOM(decoded, opts)
|
||||
}
|
||||
|
||||
// MaybeRemoveBOM removes a UTF-8 BOM from a []byte when opts.KeepBOM is false
|
||||
func MaybeRemoveBOM(content []byte, opts ConvertOpts) []byte {
|
||||
// maybeRemoveBOM removes a UTF-8 BOM from a []byte when opts.KeepBOM is false
|
||||
func maybeRemoveBOM(content []byte, opts ConvertOpts) []byte {
|
||||
if opts.KeepBOM {
|
||||
return content
|
||||
}
|
||||
if len(content) > 2 && bytes.Equal(content[0:3], UTF8BOM) {
|
||||
return content[3:]
|
||||
}
|
||||
return content
|
||||
return bytes.TrimPrefix(content, UTF8BOM)
|
||||
}
|
||||
|
||||
// DetectEncoding detect the encoding of content
|
||||
func DetectEncoding(content []byte) (string, error) {
|
||||
// it always returns a detected or guessed "encoding" string, no matter error happens or not
|
||||
func DetectEncoding(content []byte) (encoding string, _ error) {
|
||||
// First we check if the content represents valid utf8 content excepting a truncated character at the end.
|
||||
|
||||
// Now we could decode all the runes in turn but this is not necessarily the cheapest thing to do
|
||||
// instead we walk backwards from the end to trim off a the incomplete character
|
||||
// instead we walk backwards from the end to trim off the incomplete character
|
||||
toValidate := content
|
||||
end := len(toValidate) - 1
|
||||
|
||||
if end < 0 {
|
||||
// no-op
|
||||
} else if toValidate[end]>>5 == 0b110 {
|
||||
// Incomplete 1 byte extension e.g. © <c2><a9> which has been truncated to <c2>
|
||||
toValidate = toValidate[:end]
|
||||
} else if end > 0 && toValidate[end]>>6 == 0b10 && toValidate[end-1]>>4 == 0b1110 {
|
||||
// Incomplete 2 byte extension e.g. ⛔ <e2><9b><94> which has been truncated to <e2><9b>
|
||||
toValidate = toValidate[:end-1]
|
||||
} else if end > 1 && toValidate[end]>>6 == 0b10 && toValidate[end-1]>>6 == 0b10 && toValidate[end-2]>>3 == 0b11110 {
|
||||
// Incomplete 3 byte extension e.g. 💩 <f0><9f><92><a9> which has been truncated to <f0><9f><92>
|
||||
toValidate = toValidate[:end-2]
|
||||
// U+0000 U+007F 0yyyzzzz
|
||||
// U+0080 U+07FF 110xxxyy 10yyzzzz
|
||||
// U+0800 U+FFFF 1110wwww 10xxxxyy 10yyzzzz
|
||||
// U+010000 U+10FFFF 11110uvv 10vvwwww 10xxxxyy 10yyzzzz
|
||||
cnt := 0
|
||||
for end >= 0 && cnt < 4 {
|
||||
c := toValidate[end]
|
||||
if c>>5 == 0b110 || c>>4 == 0b1110 || c>>3 == 0b11110 {
|
||||
// a leading byte
|
||||
toValidate = toValidate[:end]
|
||||
break
|
||||
} else if c>>6 == 0b10 {
|
||||
// a continuation byte
|
||||
end--
|
||||
} else {
|
||||
// not an utf-8 byte
|
||||
break
|
||||
}
|
||||
cnt++
|
||||
}
|
||||
|
||||
if utf8.Valid(toValidate) {
|
||||
log.Debug("Detected encoding: utf-8 (fast)")
|
||||
return "UTF-8", nil
|
||||
}
|
||||
|
||||
@@ -160,7 +148,7 @@ func DetectEncoding(content []byte) (string, error) {
|
||||
if len(content) < 1024 {
|
||||
// Check if original content is valid
|
||||
if _, err := textDetector.DetectBest(content); err != nil {
|
||||
return "", err
|
||||
return util.IfZero(setting.Repository.AnsiCharset, "UTF-8"), err
|
||||
}
|
||||
times := 1024 / len(content)
|
||||
detectContent = make([]byte, 0, times*len(content))
|
||||
@@ -171,14 +159,10 @@ func DetectEncoding(content []byte) (string, error) {
|
||||
detectContent = content
|
||||
}
|
||||
|
||||
// Now we can't use DetectBest or just results[0] because the result isn't stable - so we need a tie break
|
||||
// Now we can't use DetectBest or just results[0] because the result isn't stable - so we need a tie-break
|
||||
results, err := textDetector.DetectAll(detectContent)
|
||||
if err != nil {
|
||||
if err == chardet.NotDetectedError && len(setting.Repository.AnsiCharset) > 0 {
|
||||
log.Debug("Using default AnsiCharset: %s", setting.Repository.AnsiCharset)
|
||||
return setting.Repository.AnsiCharset, nil
|
||||
}
|
||||
return "", err
|
||||
return util.IfZero(setting.Repository.AnsiCharset, "UTF-8"), err
|
||||
}
|
||||
|
||||
topConfidence := results[0].Confidence
|
||||
@@ -201,11 +185,9 @@ func DetectEncoding(content []byte) (string, error) {
|
||||
}
|
||||
|
||||
// FIXME: to properly decouple this function the fallback ANSI charset should be passed as an argument
|
||||
if topResult.Charset != "UTF-8" && len(setting.Repository.AnsiCharset) > 0 {
|
||||
log.Debug("Using default AnsiCharset: %s", setting.Repository.AnsiCharset)
|
||||
if topResult.Charset != "UTF-8" && setting.Repository.AnsiCharset != "" {
|
||||
return setting.Repository.AnsiCharset, err
|
||||
}
|
||||
|
||||
log.Debug("Detected encoding: %s", topResult.Charset)
|
||||
return topResult.Charset, err
|
||||
return topResult.Charset, nil
|
||||
}
|
||||
|
||||
@@ -4,108 +4,89 @@
|
||||
package charset
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func resetDefaultCharsetsOrder() {
|
||||
defaultDetectedCharsetsOrder := make([]string, 0, len(setting.Repository.DetectedCharsetsOrder))
|
||||
for _, charset := range setting.Repository.DetectedCharsetsOrder {
|
||||
defaultDetectedCharsetsOrder = append(defaultDetectedCharsetsOrder, strings.ToLower(strings.TrimSpace(charset)))
|
||||
}
|
||||
func TestMain(m *testing.M) {
|
||||
setting.Repository.DetectedCharsetScore = map[string]int{}
|
||||
i := 0
|
||||
for _, charset := range defaultDetectedCharsetsOrder {
|
||||
canonicalCharset := strings.ToLower(strings.TrimSpace(charset))
|
||||
if _, has := setting.Repository.DetectedCharsetScore[canonicalCharset]; !has {
|
||||
setting.Repository.DetectedCharsetScore[canonicalCharset] = i
|
||||
i++
|
||||
}
|
||||
for i, charset := range setting.Repository.DetectedCharsetsOrder {
|
||||
setting.Repository.DetectedCharsetScore[strings.ToLower(charset)] = i
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestMaybeRemoveBOM(t *testing.T) {
|
||||
res := MaybeRemoveBOM([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{})
|
||||
res := maybeRemoveBOM([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{})
|
||||
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
|
||||
|
||||
res = MaybeRemoveBOM([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{})
|
||||
res = maybeRemoveBOM([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{})
|
||||
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
|
||||
}
|
||||
|
||||
func TestToUTF8(t *testing.T) {
|
||||
resetDefaultCharsetsOrder()
|
||||
|
||||
// Note: golang compiler seems so behave differently depending on the current
|
||||
// locale, so some conversions might behave differently. For that reason, we don't
|
||||
// depend on particular conversions but in expected behaviors.
|
||||
|
||||
res, err := ToUTF8([]byte{0x41, 0x42, 0x43}, ConvertOpts{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "ABC", res)
|
||||
res := ToUTF8([]byte{0x41, 0x42, 0x43}, ConvertOpts{})
|
||||
assert.Equal(t, "ABC", string(res))
|
||||
|
||||
// "áéíóú"
|
||||
res, err = ToUTF8([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, []byte(res))
|
||||
res = ToUTF8([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{})
|
||||
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
|
||||
|
||||
// "áéíóú"
|
||||
res, err = ToUTF8([]byte{
|
||||
res = ToUTF8([]byte{
|
||||
0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3,
|
||||
0xc3, 0xba,
|
||||
}, ConvertOpts{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, []byte(res))
|
||||
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
|
||||
|
||||
res, err = ToUTF8([]byte{
|
||||
res = ToUTF8([]byte{
|
||||
0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63,
|
||||
0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73, 0x41, 0x41, 0x41, 0x2e,
|
||||
}, ConvertOpts{})
|
||||
assert.NoError(t, err)
|
||||
stringMustStartWith(t, "Hola,", res)
|
||||
stringMustEndWith(t, "AAA.", res)
|
||||
|
||||
res, err = ToUTF8([]byte{
|
||||
res = ToUTF8([]byte{
|
||||
0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63,
|
||||
0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73, 0x41, 0x41, 0x41, 0x2e,
|
||||
}, ConvertOpts{})
|
||||
assert.NoError(t, err)
|
||||
stringMustStartWith(t, "Hola,", res)
|
||||
stringMustEndWith(t, "AAA.", res)
|
||||
|
||||
res, err = ToUTF8([]byte{
|
||||
res = ToUTF8([]byte{
|
||||
0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63,
|
||||
0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73, 0x41, 0x41, 0x41, 0x2e,
|
||||
}, ConvertOpts{})
|
||||
assert.NoError(t, err)
|
||||
stringMustStartWith(t, "Hola,", res)
|
||||
stringMustEndWith(t, "AAA.", res)
|
||||
|
||||
// Japanese (Shift-JIS)
|
||||
// 日属秘ぞしちゅ。
|
||||
res, err = ToUTF8([]byte{
|
||||
res = ToUTF8([]byte{
|
||||
0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82,
|
||||
0xBF, 0x82, 0xE3, 0x81, 0x42,
|
||||
}, ConvertOpts{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte{
|
||||
0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3,
|
||||
0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82,
|
||||
},
|
||||
[]byte(res))
|
||||
}, res)
|
||||
|
||||
res, err = ToUTF8([]byte{0x00, 0x00, 0x00, 0x00}, ConvertOpts{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, []byte(res))
|
||||
res = ToUTF8([]byte{0x00, 0x00, 0x00, 0x00}, ConvertOpts{})
|
||||
assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, res)
|
||||
}
|
||||
|
||||
func TestToUTF8WithFallback(t *testing.T) {
|
||||
resetDefaultCharsetsOrder()
|
||||
// "ABC"
|
||||
res := ToUTF8WithFallback([]byte{0x41, 0x42, 0x43}, ConvertOpts{})
|
||||
assert.Equal(t, []byte{0x41, 0x42, 0x43}, res)
|
||||
@@ -152,54 +133,58 @@ func TestToUTF8WithFallback(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestToUTF8DropErrors(t *testing.T) {
|
||||
resetDefaultCharsetsOrder()
|
||||
// "ABC"
|
||||
res := ToUTF8DropErrors([]byte{0x41, 0x42, 0x43}, ConvertOpts{})
|
||||
res := ToUTF8DropErrors([]byte{0x41, 0x42, 0x43})
|
||||
assert.Equal(t, []byte{0x41, 0x42, 0x43}, res)
|
||||
|
||||
// "áéíóú"
|
||||
res = ToUTF8DropErrors([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{})
|
||||
res = ToUTF8DropErrors([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba})
|
||||
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
|
||||
|
||||
// UTF8 BOM + "áéíóú"
|
||||
res = ToUTF8DropErrors([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{})
|
||||
res = ToUTF8DropErrors([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba})
|
||||
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
|
||||
|
||||
// "Hola, así cómo ños"
|
||||
res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73}, ConvertOpts{})
|
||||
res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73})
|
||||
assert.Equal(t, []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73}, res[:8])
|
||||
assert.Equal(t, []byte{0x73}, res[len(res)-1:])
|
||||
|
||||
// "Hola, así cómo "
|
||||
minmatch := []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63, 0xC3, 0xB3, 0x6D, 0x6F, 0x20}
|
||||
|
||||
res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73}, ConvertOpts{})
|
||||
res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73})
|
||||
// Do not fail for differences in invalid cases, as the library might change the conversion criteria for those
|
||||
assert.Equal(t, minmatch, res[0:len(minmatch)])
|
||||
|
||||
res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73}, ConvertOpts{})
|
||||
res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73})
|
||||
// Do not fail for differences in invalid cases, as the library might change the conversion criteria for those
|
||||
assert.Equal(t, minmatch, res[0:len(minmatch)])
|
||||
|
||||
// Japanese (Shift-JIS)
|
||||
// "日属秘ぞしちゅ。"
|
||||
res = ToUTF8DropErrors([]byte{0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, 0xBF, 0x82, 0xE3, 0x81, 0x42}, ConvertOpts{})
|
||||
res = ToUTF8DropErrors([]byte{0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, 0xBF, 0x82, 0xE3, 0x81, 0x42})
|
||||
assert.Equal(t, []byte{
|
||||
0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3,
|
||||
0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82,
|
||||
}, res)
|
||||
|
||||
res = ToUTF8DropErrors([]byte{0x00, 0x00, 0x00, 0x00}, ConvertOpts{})
|
||||
res = ToUTF8DropErrors([]byte{0x00, 0x00, 0x00, 0x00})
|
||||
assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, res)
|
||||
}
|
||||
|
||||
func TestDetectEncoding(t *testing.T) {
|
||||
resetDefaultCharsetsOrder()
|
||||
testSuccess := func(b []byte, expected string) {
|
||||
encoding, err := DetectEncoding(b)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, encoding)
|
||||
}
|
||||
|
||||
// invalid bytes
|
||||
encoding, err := DetectEncoding([]byte{0xfa})
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "UTF-8", encoding)
|
||||
|
||||
// utf-8
|
||||
b := []byte("just some ascii")
|
||||
testSuccess(b, "UTF-8")
|
||||
@@ -214,169 +199,49 @@ func TestDetectEncoding(t *testing.T) {
|
||||
|
||||
// iso-8859-1: d<accented e>cor<newline>
|
||||
b = []byte{0x44, 0xe9, 0x63, 0x6f, 0x72, 0x0a}
|
||||
encoding, err := DetectEncoding(b)
|
||||
encoding, err = DetectEncoding(b)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, encoding, "ISO-8859-1")
|
||||
|
||||
old := setting.Repository.AnsiCharset
|
||||
setting.Repository.AnsiCharset = "placeholder"
|
||||
defer func() {
|
||||
setting.Repository.AnsiCharset = old
|
||||
}()
|
||||
testSuccess(b, "placeholder")
|
||||
|
||||
// invalid bytes
|
||||
b = []byte{0xfa}
|
||||
_, err = DetectEncoding(b)
|
||||
assert.Error(t, err)
|
||||
defer test.MockVariableValue(&setting.Repository.AnsiCharset, "MyEncoding")()
|
||||
testSuccess(b, "MyEncoding")
|
||||
}
|
||||
|
||||
func stringMustStartWith(t *testing.T, expected, value string) {
|
||||
assert.Equal(t, expected, value[:len(expected)])
|
||||
func stringMustStartWith(t *testing.T, expected string, value []byte) {
|
||||
assert.Equal(t, expected, string(value[:len(expected)]))
|
||||
}
|
||||
|
||||
func stringMustEndWith(t *testing.T, expected, value string) {
|
||||
assert.Equal(t, expected, value[len(value)-len(expected):])
|
||||
func stringMustEndWith(t *testing.T, expected string, value []byte) {
|
||||
assert.Equal(t, expected, string(value[len(value)-len(expected):]))
|
||||
}
|
||||
|
||||
func TestToUTF8WithFallbackReader(t *testing.T) {
|
||||
resetDefaultCharsetsOrder()
|
||||
test.MockVariableValue(&ToUTF8WithFallbackReaderPrefetchSize)
|
||||
|
||||
for testLen := range 2048 {
|
||||
pattern := " test { () }\n"
|
||||
input := ""
|
||||
for len(input) < testLen {
|
||||
input += pattern
|
||||
}
|
||||
input = input[:testLen]
|
||||
input += "// Выключаем"
|
||||
rd := ToUTF8WithFallbackReader(bytes.NewReader([]byte(input)), ConvertOpts{})
|
||||
block := "aá啊🤔"
|
||||
runes := []rune(block)
|
||||
assert.Len(t, string(runes[0]), 1)
|
||||
assert.Len(t, string(runes[1]), 2)
|
||||
assert.Len(t, string(runes[2]), 3)
|
||||
assert.Len(t, string(runes[3]), 4)
|
||||
|
||||
content := strings.Repeat(block, 2)
|
||||
for i := 1; i < len(content); i++ {
|
||||
encoding, err := DetectEncoding([]byte(content[:i]))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "UTF-8", encoding)
|
||||
|
||||
ToUTF8WithFallbackReaderPrefetchSize = i
|
||||
rd := ToUTF8WithFallbackReader(strings.NewReader(content), ConvertOpts{})
|
||||
r, _ := io.ReadAll(rd)
|
||||
assert.Equalf(t, input, string(r), "testing string len=%d", testLen)
|
||||
assert.Equal(t, content, string(r))
|
||||
}
|
||||
for _, r := range runes {
|
||||
content = "abc abc " + string(r) + string(r) + string(r)
|
||||
for i := 0; i < len(content); i++ {
|
||||
encoding, err := DetectEncoding([]byte(content[:i]))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "UTF-8", encoding)
|
||||
}
|
||||
}
|
||||
|
||||
truncatedOneByteExtension := failFastBytes
|
||||
encoding, _ := DetectEncoding(truncatedOneByteExtension)
|
||||
assert.Equal(t, "UTF-8", encoding)
|
||||
|
||||
truncatedTwoByteExtension := failFastBytes
|
||||
truncatedTwoByteExtension[len(failFastBytes)-1] = 0x9b
|
||||
truncatedTwoByteExtension[len(failFastBytes)-2] = 0xe2
|
||||
|
||||
encoding, _ = DetectEncoding(truncatedTwoByteExtension)
|
||||
assert.Equal(t, "UTF-8", encoding)
|
||||
|
||||
truncatedThreeByteExtension := failFastBytes
|
||||
truncatedThreeByteExtension[len(failFastBytes)-1] = 0x92
|
||||
truncatedThreeByteExtension[len(failFastBytes)-2] = 0x9f
|
||||
truncatedThreeByteExtension[len(failFastBytes)-3] = 0xf0
|
||||
|
||||
encoding, _ = DetectEncoding(truncatedThreeByteExtension)
|
||||
assert.Equal(t, "UTF-8", encoding)
|
||||
}
|
||||
|
||||
var failFastBytes = []byte{
|
||||
0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x6f, 0x72, 0x67, 0x2e, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x74, 0x6f,
|
||||
0x6f, 0x6c, 0x73, 0x2e, 0x61, 0x6e, 0x74, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x64, 0x65, 0x66, 0x73, 0x2e, 0x63, 0x6f, 0x6e,
|
||||
0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4f, 0x73, 0x0a, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x6f, 0x72, 0x67,
|
||||
0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f,
|
||||
0x74, 0x2e, 0x67, 0x72, 0x61, 0x64, 0x6c, 0x65, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x72, 0x75, 0x6e, 0x2e, 0x42,
|
||||
0x6f, 0x6f, 0x74, 0x52, 0x75, 0x6e, 0x0a, 0x0a, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20,
|
||||
0x20, 0x20, 0x69, 0x64, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d,
|
||||
0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x22, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x65,
|
||||
0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a,
|
||||
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x61, 0x70, 0x69, 0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d,
|
||||
0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74,
|
||||
0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x61, 0x70, 0x69, 0x2d, 0x64, 0x6f, 0x63, 0x73, 0x22, 0x29,
|
||||
0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x64, 0x62,
|
||||
0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a,
|
||||
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a,
|
||||
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x66,
|
||||
0x73, 0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x3a, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x6d, 0x71, 0x22, 0x29, 0x29, 0x0a, 0x0a,
|
||||
0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22,
|
||||
0x6a, 0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
|
||||
0x2d, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2d, 0x73, 0x74, 0x61, 0x72, 0x74,
|
||||
0x65, 0x72, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6a, 0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63,
|
||||
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2d, 0x68, 0x61, 0x6c, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c,
|
||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6a, 0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e,
|
||||
0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x29, 0x0a,
|
||||
0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28,
|
||||
0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b,
|
||||
0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x2d, 0x73, 0x74,
|
||||
0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x77, 0x65, 0x62, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c,
|
||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69,
|
||||
0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x3a, 0x73, 0x70, 0x72,
|
||||
0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x2d, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x61, 0x6f, 0x70,
|
||||
0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f,
|
||||
0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x2d,
|
||||
0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x61, 0x63, 0x74, 0x75, 0x61, 0x74, 0x6f, 0x72, 0x22, 0x29, 0x0a, 0x20,
|
||||
0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f,
|
||||
0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x63,
|
||||
0x6c, 0x6f, 0x75, 0x64, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x74,
|
||||
0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x22, 0x29, 0x0a, 0x20, 0x20,
|
||||
0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72,
|
||||
0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x63, 0x6c,
|
||||
0x6f, 0x75, 0x64, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x74, 0x61,
|
||||
0x72, 0x74, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2d, 0x61, 0x6c, 0x6c, 0x22, 0x29, 0x0a, 0x20, 0x20,
|
||||
0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72,
|
||||
0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x63, 0x6c,
|
||||
0x6f, 0x75, 0x64, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x74, 0x61,
|
||||
0x72, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x6c, 0x65, 0x75, 0x74, 0x68, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d,
|
||||
0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70,
|
||||
0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x72, 0x65, 0x74, 0x72, 0x79, 0x3a,
|
||||
0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x72, 0x65, 0x74, 0x72, 0x79, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
|
||||
0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x63, 0x68, 0x2e, 0x71,
|
||||
0x6f, 0x73, 0x2e, 0x6c, 0x6f, 0x67, 0x62, 0x61, 0x63, 0x6b, 0x3a, 0x6c, 0x6f, 0x67, 0x62, 0x61, 0x63, 0x6b, 0x2d, 0x63,
|
||||
0x6c, 0x61, 0x73, 0x73, 0x69, 0x63, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d,
|
||||
0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x69, 0x6f, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x6d, 0x65,
|
||||
0x74, 0x65, 0x72, 0x3a, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x2d, 0x72, 0x65, 0x67, 0x69, 0x73,
|
||||
0x74, 0x72, 0x79, 0x2d, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20,
|
||||
0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6b, 0x6f, 0x74,
|
||||
0x6c, 0x69, 0x6e, 0x28, 0x22, 0x73, 0x74, 0x64, 0x6c, 0x69, 0x62, 0x22, 0x29, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
|
||||
0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
|
||||
0x2f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64,
|
||||
0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
|
||||
0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74,
|
||||
0x65, 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6a,
|
||||
0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2d,
|
||||
0x74, 0x65, 0x73, 0x74, 0x22, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x20, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4a,
|
||||
0x61, 0x72, 0x20, 0x62, 0x79, 0x20, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
|
||||
0x69, 0x6e, 0x67, 0x28, 0x4a, 0x61, 0x72, 0x3a, 0x3a, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20,
|
||||
0x20, 0x20, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2e,
|
||||
0x73, 0x65, 0x74, 0x28, 0x22, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
|
||||
0x76, 0x61, 0x6c, 0x20, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x70, 0x61, 0x74, 0x68,
|
||||
0x20, 0x62, 0x79, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x67,
|
||||
0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74,
|
||||
0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65,
|
||||
0x73, 0x28, 0x22, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x2d, 0x50, 0x61, 0x74, 0x68, 0x22, 0x20, 0x74, 0x6f, 0x20, 0x6f, 0x62,
|
||||
0x6a, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70,
|
||||
0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x20, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x3d,
|
||||
0x20, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x3a, 0x2f, 0x2b, 0x22, 0x2e, 0x74, 0x6f, 0x52, 0x65, 0x67, 0x65, 0x78, 0x28, 0x29,
|
||||
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
|
||||
0x65, 0x20, 0x66, 0x75, 0x6e, 0x20, 0x74, 0x6f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x29, 0x3a, 0x20, 0x53, 0x74,
|
||||
0x72, 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x70,
|
||||
0x61, 0x74, 0x68, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x54, 0x6f, 0x53, 0x74, 0x72, 0x69,
|
||||
0x6e, 0x67, 0x28, 0x22, 0x20, 0x22, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x74, 0x2e, 0x74, 0x6f, 0x55, 0x52, 0x49, 0x28, 0x29, 0x2e, 0x74, 0x6f, 0x55,
|
||||
0x52, 0x4c, 0x28, 0x29, 0x2e, 0x74, 0x6f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x29, 0x2e, 0x72, 0x65, 0x70, 0x6c,
|
||||
0x61, 0x63, 0x65, 0x46, 0x69, 0x72, 0x73, 0x74, 0x28, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x2c, 0x20, 0x22, 0x2f,
|
||||
0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x61, 0x73,
|
||||
0x6b, 0x73, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x3c, 0x42, 0x6f, 0x6f, 0x74, 0x52, 0x75, 0x6e, 0x3e, 0x28, 0x22, 0x62,
|
||||
0x6f, 0x6f, 0x74, 0x52, 0x75, 0x6e, 0x22, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x4f,
|
||||
0x73, 0x2e, 0x69, 0x73, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x28, 0x4f, 0x73, 0x2e, 0x46, 0x41, 0x4d, 0x49, 0x4c, 0x59,
|
||||
0x5f, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x53, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x70, 0x61, 0x74, 0x68, 0x20, 0x3d, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x28, 0x73,
|
||||
0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x28, 0x22, 0x6d, 0x61, 0x69,
|
||||
0x6e, 0x22, 0x29, 0x2e, 0x6d, 0x61, 0x70, 0x20, 0x7b, 0x20, 0x69, 0x74, 0x2e, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20,
|
||||
0x7d, 0x2c, 0x20, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4a, 0x61, 0x72, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a,
|
||||
0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0xd0,
|
||||
}
|
||||
|
||||
@@ -96,8 +96,8 @@ func (attrs *Attributes) GetGitlabLanguage() optional.Option[string] {
|
||||
// gitlab-language may have additional parameters after the language
|
||||
// ignore them and just use the main language
|
||||
// https://docs.gitlab.com/ee/user/project/highlighting.html#override-syntax-highlighting-for-a-file-type
|
||||
if idx := strings.IndexByte(raw, '?'); idx >= 0 {
|
||||
return optional.Some(raw[:idx])
|
||||
if before, _, ok := strings.Cut(raw, "?"); ok {
|
||||
return optional.Some(before)
|
||||
}
|
||||
}
|
||||
return attrStr
|
||||
|
||||
@@ -77,13 +77,12 @@ func NewBatchChecker(repo *git.Repository, treeish string, attributes []string)
|
||||
_ = lw.Close()
|
||||
}()
|
||||
stdErr := new(bytes.Buffer)
|
||||
err := cmd.Run(ctx, &gitcmd.RunOpts{
|
||||
Env: envs,
|
||||
Dir: repo.Path,
|
||||
Stdin: stdinReader,
|
||||
Stdout: lw,
|
||||
Stderr: stdErr,
|
||||
})
|
||||
err := cmd.WithEnv(envs).
|
||||
WithDir(repo.Path).
|
||||
WithStdin(stdinReader).
|
||||
WithStdout(lw).
|
||||
WithStderr(stdErr).
|
||||
Run(ctx)
|
||||
|
||||
if err != nil && !git.IsErrCanceledOrKilled(err) {
|
||||
log.Error("Attribute checker for commit %s exits with error: %v", treeish, err)
|
||||
|
||||
@@ -71,12 +71,11 @@ func CheckAttributes(ctx context.Context, gitRepo *git.Repository, treeish strin
|
||||
stdOut := new(bytes.Buffer)
|
||||
stdErr := new(bytes.Buffer)
|
||||
|
||||
if err := cmd.Run(ctx, &gitcmd.RunOpts{
|
||||
Env: append(os.Environ(), envs...),
|
||||
Dir: gitRepo.Path,
|
||||
Stdout: stdOut,
|
||||
Stderr: stdErr,
|
||||
}); err != nil {
|
||||
if err := cmd.WithEnv(append(os.Environ(), envs...)).
|
||||
WithDir(gitRepo.Path).
|
||||
WithStdout(stdOut).
|
||||
WithStderr(stdErr).
|
||||
Run(ctx); err != nil {
|
||||
return nil, fmt.Errorf("failed to run check-attr: %w\n%s\n%s", err, stdOut.String(), stdErr.String())
|
||||
}
|
||||
|
||||
|
||||
@@ -31,10 +31,9 @@ type WriteCloserError interface {
|
||||
func ensureValidGitRepository(ctx context.Context, repoPath string) error {
|
||||
stderr := strings.Builder{}
|
||||
err := gitcmd.NewCommand("rev-parse").
|
||||
Run(ctx, &gitcmd.RunOpts{
|
||||
Dir: repoPath,
|
||||
Stderr: &stderr,
|
||||
})
|
||||
WithDir(repoPath).
|
||||
WithStderr(&stderr).
|
||||
Run(ctx)
|
||||
if err != nil {
|
||||
return gitcmd.ConcatenateError(err, (&stderr).String())
|
||||
}
|
||||
@@ -63,14 +62,12 @@ func catFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError,
|
||||
go func() {
|
||||
stderr := strings.Builder{}
|
||||
err := gitcmd.NewCommand("cat-file", "--batch-check").
|
||||
Run(ctx, &gitcmd.RunOpts{
|
||||
Dir: repoPath,
|
||||
Stdin: batchStdinReader,
|
||||
Stdout: batchStdoutWriter,
|
||||
Stderr: &stderr,
|
||||
|
||||
UseContextTimeout: true,
|
||||
})
|
||||
WithDir(repoPath).
|
||||
WithStdin(batchStdinReader).
|
||||
WithStdout(batchStdoutWriter).
|
||||
WithStderr(&stderr).
|
||||
WithUseContextTimeout(true).
|
||||
Run(ctx)
|
||||
if err != nil {
|
||||
_ = batchStdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String()))
|
||||
_ = batchStdinReader.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String()))
|
||||
@@ -111,14 +108,12 @@ func catFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi
|
||||
go func() {
|
||||
stderr := strings.Builder{}
|
||||
err := gitcmd.NewCommand("cat-file", "--batch").
|
||||
Run(ctx, &gitcmd.RunOpts{
|
||||
Dir: repoPath,
|
||||
Stdin: batchStdinReader,
|
||||
Stdout: batchStdoutWriter,
|
||||
Stderr: &stderr,
|
||||
|
||||
UseContextTimeout: true,
|
||||
})
|
||||
WithDir(repoPath).
|
||||
WithStdin(batchStdinReader).
|
||||
WithStdout(batchStdoutWriter).
|
||||
WithStderr(&stderr).
|
||||
WithUseContextTimeout(true).
|
||||
Run(ctx)
|
||||
if err != nil {
|
||||
_ = batchStdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String()))
|
||||
_ = batchStdinReader.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String()))
|
||||
|
||||
@@ -166,12 +166,11 @@ func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath
|
||||
go func() {
|
||||
stderr := bytes.Buffer{}
|
||||
// TODO: it doesn't work for directories (the directories shouldn't be "blamed"), and the "err" should be returned by "Read" but not by "Close"
|
||||
err := cmd.Run(ctx, &gitcmd.RunOpts{
|
||||
UseContextTimeout: true,
|
||||
Dir: repoPath,
|
||||
Stdout: stdout,
|
||||
Stderr: &stderr,
|
||||
})
|
||||
err := cmd.WithDir(repoPath).
|
||||
WithUseContextTimeout(true).
|
||||
WithStdout(stdout).
|
||||
WithStderr(&stderr).
|
||||
Run(ctx)
|
||||
done <- err
|
||||
_ = stdout.Close()
|
||||
if err != nil {
|
||||
|
||||
@@ -5,17 +5,13 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
@@ -93,7 +89,7 @@ func AddChanges(ctx context.Context, repoPath string, all bool, files ...string)
|
||||
cmd.AddArguments("--all")
|
||||
}
|
||||
cmd.AddDashesAndList(files...)
|
||||
_, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
_, _, err := cmd.WithDir(repoPath).RunStdString(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -122,7 +118,7 @@ func CommitChanges(ctx context.Context, repoPath string, opts CommitChangesOptio
|
||||
}
|
||||
cmd.AddOptionFormat("--message=%s", opts.Message)
|
||||
|
||||
_, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
_, _, err := cmd.WithDir(repoPath).RunStdString(ctx)
|
||||
// No stderr but exit status 1 means nothing to commit.
|
||||
if err != nil && err.Error() == "exit status 1" {
|
||||
return nil
|
||||
@@ -130,65 +126,6 @@ func CommitChanges(ctx context.Context, repoPath string, opts CommitChangesOptio
|
||||
return err
|
||||
}
|
||||
|
||||
// AllCommitsCount returns count of all commits in repository
|
||||
func AllCommitsCount(ctx context.Context, repoPath string, hidePRRefs bool, files ...string) (int64, error) {
|
||||
cmd := gitcmd.NewCommand("rev-list")
|
||||
if hidePRRefs {
|
||||
cmd.AddArguments("--exclude=" + PullPrefix + "*")
|
||||
}
|
||||
cmd.AddArguments("--all", "--count")
|
||||
if len(files) > 0 {
|
||||
cmd.AddDashesAndList(files...)
|
||||
}
|
||||
|
||||
stdout, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
|
||||
}
|
||||
|
||||
// CommitsCountOptions the options when counting commits
|
||||
type CommitsCountOptions struct {
|
||||
RepoPath string
|
||||
Not string
|
||||
Revision []string
|
||||
RelPath []string
|
||||
Since string
|
||||
Until string
|
||||
}
|
||||
|
||||
// CommitsCount returns number of total commits of until given revision.
|
||||
func CommitsCount(ctx context.Context, opts CommitsCountOptions) (int64, error) {
|
||||
cmd := gitcmd.NewCommand("rev-list", "--count")
|
||||
|
||||
cmd.AddDynamicArguments(opts.Revision...)
|
||||
|
||||
if opts.Not != "" {
|
||||
cmd.AddOptionValues("--not", opts.Not)
|
||||
}
|
||||
|
||||
if len(opts.RelPath) > 0 {
|
||||
cmd.AddDashesAndList(opts.RelPath...)
|
||||
}
|
||||
|
||||
stdout, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: opts.RepoPath})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
|
||||
}
|
||||
|
||||
// CommitsCount returns number of total commits of until current revision.
|
||||
func (c *Commit) CommitsCount() (int64, error) {
|
||||
return CommitsCount(c.repo.Ctx, CommitsCountOptions{
|
||||
RepoPath: c.repo.Path,
|
||||
Revision: []string{c.ID.String()},
|
||||
})
|
||||
}
|
||||
|
||||
// CommitsByRange returns the specific page commits before current revision, every page's number default by CommitsRangeSize
|
||||
func (c *Commit) CommitsByRange(page, pageSize int, not, since, until string) ([]*Commit, error) {
|
||||
return c.repo.commitsByRangeWithTime(c.ID, page, pageSize, not, since, until)
|
||||
@@ -208,7 +145,10 @@ func (c *Commit) HasPreviousCommit(objectID ObjectID) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
_, _, err := gitcmd.NewCommand("merge-base", "--is-ancestor").AddDynamicArguments(that, this).RunStdString(c.repo.Ctx, &gitcmd.RunOpts{Dir: c.repo.Path})
|
||||
_, _, err := gitcmd.NewCommand("merge-base", "--is-ancestor").
|
||||
AddDynamicArguments(that, this).
|
||||
WithDir(c.repo.Path).
|
||||
RunStdString(c.repo.Ctx)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
@@ -354,7 +294,7 @@ func (c *Commit) GetBranchName() (string, error) {
|
||||
cmd.AddArguments("--exclude", "refs/tags/*")
|
||||
}
|
||||
cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String())
|
||||
data, _, err := cmd.RunStdString(c.repo.Ctx, &gitcmd.RunOpts{Dir: c.repo.Path})
|
||||
data, _, err := cmd.WithDir(c.repo.Path).RunStdString(c.repo.Ctx)
|
||||
if err != nil {
|
||||
// handle special case where git can not describe commit
|
||||
if strings.Contains(err.Error(), "cannot describe") {
|
||||
@@ -368,87 +308,12 @@ func (c *Commit) GetBranchName() (string, error) {
|
||||
return strings.SplitN(strings.TrimSpace(data), "~", 2)[0], nil
|
||||
}
|
||||
|
||||
// CommitFileStatus represents status of files in a commit.
|
||||
type CommitFileStatus struct {
|
||||
Added []string
|
||||
Removed []string
|
||||
Modified []string
|
||||
}
|
||||
|
||||
// NewCommitFileStatus creates a CommitFileStatus
|
||||
func NewCommitFileStatus() *CommitFileStatus {
|
||||
return &CommitFileStatus{
|
||||
[]string{}, []string{}, []string{},
|
||||
}
|
||||
}
|
||||
|
||||
func parseCommitFileStatus(fileStatus *CommitFileStatus, stdout io.Reader) {
|
||||
rd := bufio.NewReader(stdout)
|
||||
peek, err := rd.Peek(1)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if peek[0] == '\n' || peek[0] == '\x00' {
|
||||
_, _ = rd.Discard(1)
|
||||
}
|
||||
for {
|
||||
modifier, err := rd.ReadString('\x00')
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
file, err := rd.ReadString('\x00')
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
file = file[:len(file)-1]
|
||||
switch modifier[0] {
|
||||
case 'A':
|
||||
fileStatus.Added = append(fileStatus.Added, file)
|
||||
case 'D':
|
||||
fileStatus.Removed = append(fileStatus.Removed, file)
|
||||
case 'M':
|
||||
fileStatus.Modified = append(fileStatus.Modified, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetCommitFileStatus returns file status of commit in given repository.
|
||||
func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*CommitFileStatus, error) {
|
||||
stdout, w := io.Pipe()
|
||||
done := make(chan struct{})
|
||||
fileStatus := NewCommitFileStatus()
|
||||
go func() {
|
||||
parseCommitFileStatus(fileStatus, stdout)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
stderr := new(bytes.Buffer)
|
||||
err := gitcmd.NewCommand("log", "--name-status", "-m", "--pretty=format:", "--first-parent", "--no-renames", "-z", "-1").AddDynamicArguments(commitID).Run(ctx, &gitcmd.RunOpts{
|
||||
Dir: repoPath,
|
||||
Stdout: w,
|
||||
Stderr: stderr,
|
||||
})
|
||||
w.Close() // Close writer to exit parsing goroutine
|
||||
if err != nil {
|
||||
return nil, gitcmd.ConcatenateError(err, stderr.String())
|
||||
}
|
||||
|
||||
<-done
|
||||
return fileStatus, nil
|
||||
}
|
||||
|
||||
// GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository.
|
||||
func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) {
|
||||
commitID, _, err := gitcmd.NewCommand("rev-parse").AddDynamicArguments(shortID).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
commitID, _, err := gitcmd.NewCommand("rev-parse").
|
||||
AddDynamicArguments(shortID).
|
||||
WithDir(repoPath).
|
||||
RunStdString(ctx)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "exit status 128") {
|
||||
return "", ErrNotExist{shortID, ""}
|
||||
|
||||
@@ -173,7 +173,6 @@ func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
|
||||
} else if entries, err = commit.Tree.ListEntries(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
entries.Sort()
|
||||
b.ResetTimer()
|
||||
b.Run(benchmark.name, func(b *testing.B) {
|
||||
for b.Loop() {
|
||||
|
||||
@@ -14,33 +14,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCommitsCountSha256(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256")
|
||||
|
||||
commitsCount, err := CommitsCount(t.Context(),
|
||||
CommitsCountOptions{
|
||||
RepoPath: bareRepo1Path,
|
||||
Revision: []string{"f004f41359117d319dedd0eaab8c5259ee2263da839dcba33637997458627fdc"},
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(3), commitsCount)
|
||||
}
|
||||
|
||||
func TestCommitsCountWithoutBaseSha256(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256")
|
||||
|
||||
commitsCount, err := CommitsCount(t.Context(),
|
||||
CommitsCountOptions{
|
||||
RepoPath: bareRepo1Path,
|
||||
Not: "main",
|
||||
Revision: []string{"branch1"},
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(2), commitsCount)
|
||||
}
|
||||
|
||||
func TestGetFullCommitIDSha256(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256")
|
||||
|
||||
@@ -157,39 +130,3 @@ func TestHasPreviousCommitSha256(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, selfNot)
|
||||
}
|
||||
|
||||
func TestGetCommitFileStatusMergesSha256(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo6_merge_sha256")
|
||||
|
||||
commitFileStatus, err := GetCommitFileStatus(t.Context(), bareRepo1Path, "d2e5609f630dd8db500f5298d05d16def282412e3e66ed68cc7d0833b29129a1")
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := CommitFileStatus{
|
||||
[]string{
|
||||
"add_file.txt",
|
||||
},
|
||||
[]string{},
|
||||
[]string{
|
||||
"to_modify.txt",
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, expected.Added, commitFileStatus.Added)
|
||||
assert.Equal(t, expected.Removed, commitFileStatus.Removed)
|
||||
assert.Equal(t, expected.Modified, commitFileStatus.Modified)
|
||||
|
||||
expected = CommitFileStatus{
|
||||
[]string{},
|
||||
[]string{
|
||||
"to_remove.txt",
|
||||
},
|
||||
[]string{},
|
||||
}
|
||||
|
||||
commitFileStatus, err = GetCommitFileStatus(t.Context(), bareRepo1Path, "da1ded40dc8e5b7c564171f4bf2fc8370487decfb1cb6a99ef28f3ed73d09172")
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expected.Added, commitFileStatus.Added)
|
||||
assert.Equal(t, expected.Removed, commitFileStatus.Removed)
|
||||
assert.Equal(t, expected.Modified, commitFileStatus.Modified)
|
||||
}
|
||||
|
||||
@@ -13,33 +13,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCommitsCount(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
|
||||
commitsCount, err := CommitsCount(t.Context(),
|
||||
CommitsCountOptions{
|
||||
RepoPath: bareRepo1Path,
|
||||
Revision: []string{"8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"},
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(3), commitsCount)
|
||||
}
|
||||
|
||||
func TestCommitsCountWithoutBase(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
|
||||
commitsCount, err := CommitsCount(t.Context(),
|
||||
CommitsCountOptions{
|
||||
RepoPath: bareRepo1Path,
|
||||
Not: "master",
|
||||
Revision: []string{"branch1"},
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(2), commitsCount)
|
||||
}
|
||||
|
||||
func TestGetFullCommitID(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
|
||||
@@ -212,134 +185,6 @@ func TestHasPreviousCommit(t *testing.T) {
|
||||
assert.False(t, selfNot)
|
||||
}
|
||||
|
||||
func TestParseCommitFileStatus(t *testing.T) {
|
||||
type testcase struct {
|
||||
output string
|
||||
added []string
|
||||
removed []string
|
||||
modified []string
|
||||
}
|
||||
|
||||
kases := []testcase{
|
||||
{
|
||||
// Merge commit
|
||||
output: "MM\x00options/locale/locale_en-US.ini\x00",
|
||||
modified: []string{
|
||||
"options/locale/locale_en-US.ini",
|
||||
},
|
||||
added: []string{},
|
||||
removed: []string{},
|
||||
},
|
||||
{
|
||||
// Spaces commit
|
||||
output: "D\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00",
|
||||
removed: []string{
|
||||
"b",
|
||||
"b b/b",
|
||||
},
|
||||
modified: []string{},
|
||||
added: []string{
|
||||
"b b/b b/b b/b",
|
||||
"b b/b b/b b/b b/b",
|
||||
},
|
||||
},
|
||||
{
|
||||
// larger commit
|
||||
output: "M\x00go.mod\x00M\x00go.sum\x00M\x00modules/ssh/ssh.go\x00M\x00vendor/github.com/gliderlabs/ssh/circle.yml\x00M\x00vendor/github.com/gliderlabs/ssh/context.go\x00A\x00vendor/github.com/gliderlabs/ssh/go.mod\x00A\x00vendor/github.com/gliderlabs/ssh/go.sum\x00M\x00vendor/github.com/gliderlabs/ssh/server.go\x00M\x00vendor/github.com/gliderlabs/ssh/session.go\x00M\x00vendor/github.com/gliderlabs/ssh/ssh.go\x00M\x00vendor/golang.org/x/sys/unix/mkerrors.sh\x00M\x00vendor/golang.org/x/sys/unix/syscall_darwin.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_linux.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go\x00M\x00vendor/modules.txt\x00",
|
||||
modified: []string{
|
||||
"go.mod",
|
||||
"go.sum",
|
||||
"modules/ssh/ssh.go",
|
||||
"vendor/github.com/gliderlabs/ssh/circle.yml",
|
||||
"vendor/github.com/gliderlabs/ssh/context.go",
|
||||
"vendor/github.com/gliderlabs/ssh/server.go",
|
||||
"vendor/github.com/gliderlabs/ssh/session.go",
|
||||
"vendor/github.com/gliderlabs/ssh/ssh.go",
|
||||
"vendor/golang.org/x/sys/unix/mkerrors.sh",
|
||||
"vendor/golang.org/x/sys/unix/syscall_darwin.go",
|
||||
"vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go",
|
||||
"vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go",
|
||||
"vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go",
|
||||
"vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go",
|
||||
"vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go",
|
||||
"vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go",
|
||||
"vendor/golang.org/x/sys/unix/zerrors_linux.go",
|
||||
"vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go",
|
||||
"vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go",
|
||||
"vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go",
|
||||
"vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go",
|
||||
"vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go",
|
||||
"vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go",
|
||||
"vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go",
|
||||
"vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go",
|
||||
"vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go",
|
||||
"vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go",
|
||||
"vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go",
|
||||
"vendor/modules.txt",
|
||||
},
|
||||
added: []string{
|
||||
"vendor/github.com/gliderlabs/ssh/go.mod",
|
||||
"vendor/github.com/gliderlabs/ssh/go.sum",
|
||||
},
|
||||
removed: []string{},
|
||||
},
|
||||
{
|
||||
// git 1.7.2 adds an unnecessary \x00 on merge commit
|
||||
output: "\x00MM\x00options/locale/locale_en-US.ini\x00",
|
||||
modified: []string{
|
||||
"options/locale/locale_en-US.ini",
|
||||
},
|
||||
added: []string{},
|
||||
removed: []string{},
|
||||
},
|
||||
{
|
||||
// git 1.7.2 adds an unnecessary \n on normal commit
|
||||
output: "\nD\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00",
|
||||
removed: []string{
|
||||
"b",
|
||||
"b b/b",
|
||||
},
|
||||
modified: []string{},
|
||||
added: []string{
|
||||
"b b/b b/b b/b",
|
||||
"b b/b b/b b/b b/b",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, kase := range kases {
|
||||
fileStatus := NewCommitFileStatus()
|
||||
parseCommitFileStatus(fileStatus, strings.NewReader(kase.output))
|
||||
|
||||
assert.Equal(t, kase.added, fileStatus.Added)
|
||||
assert.Equal(t, kase.removed, fileStatus.Removed)
|
||||
assert.Equal(t, kase.modified, fileStatus.Modified)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCommitFileStatusMerges(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo6_merge")
|
||||
|
||||
commitFileStatus, err := GetCommitFileStatus(t.Context(), bareRepo1Path, "022f4ce6214973e018f02bf363bf8a2e3691f699")
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := CommitFileStatus{
|
||||
[]string{
|
||||
"add_file.txt",
|
||||
},
|
||||
[]string{
|
||||
"to_remove.txt",
|
||||
},
|
||||
[]string{
|
||||
"to_modify.txt",
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, expected.Added, commitFileStatus.Added)
|
||||
assert.Equal(t, expected.Removed, commitFileStatus.Removed)
|
||||
assert.Equal(t, expected.Modified, commitFileStatus.Modified)
|
||||
}
|
||||
|
||||
func Test_GetCommitBranchStart(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
repo, err := OpenRepository(t.Context(), bareRepo1Path)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user