Compare commits

..

63 Commits

Author SHA1 Message Date
ᴊᴏᴇ ᴄʜᴇɴ 878caa7378 ci: notarize macOS release archives (#8297) 2026-05-24 23:08:45 -04:00
ᴊᴏᴇ ᴄʜᴇɴ adea243ee8 feat(web): migrate account activation page to React (#8296) 2026-05-24 22:35:41 -04:00
ᴊᴏᴇ ᴄʜᴇɴ 44f0222a71 web: migrate /user/sign-out to Flamego (#8294) 2026-05-24 11:16:57 -04:00
ᴊᴏᴇ ᴄʜᴇɴ 26483c41c6 feat(web): add React sign-up page with Flamego captcha (#8291) 2026-05-23 23:33:41 -04:00
dependabot[bot] 403db931cf mod: bump filippo.io/edwards25519 from 1.1.0 to 1.1.1 (#8292) 2026-05-23 22:25:56 -04:00
dependabot[bot] cd2f94a85b mod: bump github.com/redis/go-redis/v9 from 9.5.1 to 9.5.5 (#8293) 2026-05-23 22:25:41 -04:00
ᴊᴏᴇ ᴄʜᴇɴ 4935e7a63b web: move password reset to React (#8290) 2026-05-23 21:55:22 -04:00
ᴊᴏᴇ ᴄʜᴇɴ 71dfd3c7ac chore: fix up README image positions
[skip ci]
2026-05-22 16:31:17 -04:00
ᴊᴏᴇ ᴄʜᴇɴ ecb04beadd chore: align-center README images
[skip ci]
2026-05-22 16:28:22 -04:00
ᴊᴏᴇ ᴄʜᴇɴ 83a48c286d fix: remove forgeable remember-me cookie, persist sessions by default (#8289) 2026-05-22 16:24:39 -04:00
ᴊᴏᴇ ᴄʜᴇɴ f739682e9a Move sign-in MFA step to React with /api/web/user/mfa (#8288) 2026-05-22 15:33:06 -04:00
ᴊᴏᴇ ᴄʜᴇɴ d54f98f5a4 feat(web): adopt Pierre theme palette and reorder sign-in tab stops (#8287) 2026-05-22 11:19:38 -04:00
ᴊᴏᴇ ᴄʜᴇɴ e7d0cb646d Update README banner for dark mode (#8286) 2026-05-22 00:56:13 -04:00
ᴊᴏᴇ ᴄʜᴇɴ dd6be39208 feat: React-based sign-in page with /api/web/user/sign-in (#8285) 2026-05-22 00:28:27 -04:00
ᴊᴏᴇ ᴄʜᴇɴ c93373baec feat: add /api/web/user/sign-out and nest user info under /user/info (#8284) 2026-05-21 15:05:19 -04:00
ᴊᴏᴇ ᴄʜᴇɴ 90790b2966 Embed WebContext and add /api/web/user-info endpoint (#8282) 2026-05-21 14:43:09 -04:00
ᴊᴏᴇ ᴄʜᴇɴ e9310ea08f chore: remove Packager.io build support (#8281) 2026-05-20 22:55:08 -04:00
ᴊᴏᴇ ᴄʜᴇɴ 75f99c9435 chore: remove Taskfile usage (#8280) 2026-05-20 19:22:50 -04:00
ᴊᴏᴇ ᴄʜᴇɴ 343007e78a ci(moon): set default VCS branch to main (#8279) 2026-05-20 17:49:52 -04:00
ᴊᴏᴇ ᴄʜᴇɴ b67c13c6bb feat: introduce React web frontend and migrate home + 404 pages (#8276) 2026-05-20 17:45:31 -04:00
ᴊᴏᴇ ᴄʜᴇɴ a3c9f4acef Replace Docker latest on main with an edge tag (#8278) 2026-05-20 15:47:42 -04:00
ᴊᴏᴇ ᴄʜᴇɴ 4c80cbc7eb refactor: inline disallowed-username regex in UsersStore.Authenticate (#8274) 2026-05-19 12:52:06 -04:00
ᴊᴏᴇ ᴄʜᴇɴ bfec14a857 refactor: render mail templates with html/template directly (#8272) 2026-05-19 12:20:13 -04:00
ᴊᴏᴇ ᴄʜᴇɴ cc8036e081 chore: remove DeepSource configuration (#8275)
[skip ci]
2026-05-19 12:11:37 -04:00
ᴊᴏᴇ ᴄʜᴇɴ 765c0e96db refactor: replace toolbox middleware with a minimal /healthcheck handler (#8271) 2026-05-18 22:37:13 -04:00
ᴊᴏᴇ ᴄʜᴇɴ f3e563d854 refactor: prepare email package for web framework migration (#8270) 2026-05-18 22:22:46 -04:00
ᴊᴏᴇ ᴄʜᴇɴ 199cf4fd5b security: don't follow redirects on webhook delivery (#8263) 2026-05-18 21:08:46 -04:00
ᴊᴏᴇ ᴄʜᴇɴ 0089c4c8e5 auth: trust reverse proxy auth header only from configured proxies (#8264) 2026-05-18 13:42:46 -04:00
ᴊᴏᴇ ᴄʜᴇɴ 6734dd46c3 docs: add changelog entry for GHSA-3qq3-668m-v9mj (#8266)
[skip ci]
2026-05-17 20:59:17 -04:00
ᴊᴏᴇ ᴄʜᴇɴ a5d3439e2d chore: replace reflect.Ptr with reflect.Pointer (#8262) 2026-05-17 20:39:09 -04:00
dependabot[bot] d7571322a0 mod: bump golang.org/x/image from 0.36.0 to 0.38.0 (#8218)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 16:43:28 -04:00
dependabot[bot] edc83e6ab2 mod: bump github.com/cockroachdb/errors from 1.12.0 to 1.13.0 (#8245)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 08:48:53 -04:00
dependabot[bot] 7297aee50d mod: bump github.com/Azure/go-ntlmssp from 0.0.0-20221128193559-754e69321358 to 0.1.1 (#8238)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 22:09:12 -04:00
dependabot[bot] 27e92f8463 mod: bump github.com/jackc/pgx/v5 from 5.9.0 to 5.9.2 (#8237)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-22 21:57:16 -04:00
dependabot[bot] b36ba5b60e mod: bump github.com/jackc/pgx/v5 from 5.6.0 to 5.9.0 (#8233)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-16 18:58:35 -04:00
dependabot[bot] 84e23a403e mod: bump golang.org/x/crypto from 0.48.0 to 0.49.0 (#8221)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-05 16:20:18 -04:00
dependabot[bot] b53d316233 mod: bump gopkg.in/ini.v1 from 1.67.0 to 1.67.1 (#8193)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-15 14:59:48 -04:00
dependabot[bot] 47bff199cc mod: bump golang.org/x/crypto from 0.47.0 to 0.48.0 (#8192)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-15 14:58:42 -04:00
ᴊᴏᴇ ᴄʜᴇɴ 998512edfb ci: bump aquasecurity/trivy-action from 0.33.1 to 0.35.0 (#8204)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 13:58:53 -04:00
dependabot[bot] 1ed882b611 mod: bump golang.org/x/image from 0.35.0 to 0.36.0 (#8194)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-04 12:49:44 -05:00
dependabot[bot] 391edc74c2 mod: bump golang.org/x/text from 0.33.0 to 0.34.0 (#8195)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 20:58:12 -05:00
dependabot[bot] 944bfeb57e mod: bump github.com/prometheus/client_golang from 1.23.0 to 1.23.2 (#8196)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 20:29:21 -05:00
ᴊᴏᴇ ᴄʜᴇɴ e8b6dea462 release: fix up sentence case for release name
[skip ci]
2026-02-18 22:45:51 -05:00
Joe Chen 2989605fd8 Update CHANGELOG for 0.14.2 release
[skip ci]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 19:22:40 -05:00
JSS df467d8ff1 Fix git reset --end-of-options error on file upload and edit (#8184) 2026-02-18 19:04:31 -05:00
ᴊᴏᴇ ᴄʜᴇɴ 36d56d5525 all: rename packages ending with "util" to end with "x" (#8182)
Co-authored-by: JSS <jss@unknwon.dev>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 13:25:19 -05:00
ᴊᴏᴇ ᴄʜᴇɴ 5f17b670b3 docker: pin Go 1.26 in builder stage images (#8180)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 23:44:13 -05:00
ᴊᴏᴇ ᴄʜᴇɴ ea682c5bbc all: upgrade to Go 1.26 (#8179)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 23:31:14 -05:00
ᴊᴏᴇ ᴄʜᴇɴ 9001a68cdd js: use safe DOM construction for milestone and assignee selection (#8178)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 23:24:52 -05:00
ᴊᴏᴇ ᴄʜᴇɴ 295bfba729 context: reject access tokens passed via URL query parameters (#8177) 2026-02-13 15:27:48 -05:00
ᴊᴏᴇ ᴄʜᴇɴ ac21150a53 template: escape untrusted names in locale strings piped through Safe (#8176)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-12 21:42:23 -05:00
ᴊᴏᴇ ᴄʜᴇɴ a000f0c7a6 database: use safe git-module API for tag deletion (#8175)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-12 21:24:44 -05:00
Joe Chen a976fd2f9c chore: minor case fixup
[skip ci]
2026-02-12 21:09:36 -05:00
ᴊᴏᴇ ᴄʜᴇɴ 441c64d7bd markup: restrict data URI scheme to safe image MIME types (#8174)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 22:26:31 -05:00
ᴊᴏᴇ ᴄʜᴇɴ 5c67d47512 database: remove MSSQL backend support (#8173) 2026-02-10 18:41:31 -05:00
ᴊᴏᴇ ᴄʜᴇɴ 94d6e53dc2 email: replace gomail with go-mail (#8164)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 17:44:01 -05:00
ᴊᴏᴇ ᴄʜᴇɴ a1fa62b270 all: decouple API types from go-gogs-client SDK (#8171)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-10 10:56:17 -05:00
Copilot 317e28b908 Remove non-existent README_ZH.md from release workflow (#8170)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-02-09 09:22:24 -05:00
ᴊᴏᴇ ᴄʜᴇɴ 069d3535d6 chore: fix broken links to gogs.io (#8169) 2026-02-09 09:09:59 -05:00
ᴊᴏᴇ ᴄʜᴇɴ 81ee883644 lfs: verify content hash and prevent object overwrite (#8166)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
2026-02-08 17:14:12 -05:00
Copilot 400ae7bd28 Add CLI reference doc page under advancing and normalize gogs command references (#8165)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-02-08 10:18:41 -05:00
ᴊᴏᴇ ᴄʜᴇɴ 630ae0b3b0 cmd: migrate from urfave/cli v1 to v3 (#8160) 2026-02-08 00:58:05 -05:00
ᴊᴏᴇ ᴄʜᴇɴ 3c358ede6d all: migrate from json-iterator to encoding/json (#8159) 2026-02-08 00:34:36 -05:00
382 changed files with 15215 additions and 4809 deletions
+13
View File
@@ -0,0 +1,13 @@
Analyze and help fix the GitHub Security Advisory (GHSA) at: $ARGUMENTS
Steps:
1. Fetch the GHSA page using `gh api repos/gogs/gogs/security-advisories` and understand the vulnerability details (description, severity, affected versions, CWE).
2. Verify the reported vulnerability actually exists, and why.
3. Identify the affected code in this repository.
4. Propose a fix with a clear explanation of the root cause and how the fix addresses it. Check for prior art in the codebase to stay consistent with existing patterns.
5. Implement the fix. Only add tests when there is something meaningful to test at our layer.
6. Run all the usual build and test commands.
7. If a changelog entry is warranted (user will specify), add it to CHANGELOG.md with a placeholder for the PR link.
8. Create a branch named after the GHSA ID, commit, and push.
9. Create a pull request with a proper title and description, do not reveal too much detail and link the GHSA.
10. If a changelog entry was added, update it with the PR link, then commit and push again.
-26
View File
@@ -1,26 +0,0 @@
version = 1
exclude_patterns = ["**/mocks_test.go"]
[[analyzers]]
name = "docker"
enabled = true
[[analyzers]]
name = "shell"
enabled = true
[[analyzers]]
name = "go"
enabled = true
[analyzers.meta]
import_root = "github.com/gogs/gogs"
[[transformers]]
name = "gofumpt"
enabled = true
[[transformers]]
name = "gofmt"
enabled = true
+5 -4
View File
@@ -1,5 +1,3 @@
.packager
.packager/**
scripts
scripts/**
.github/
@@ -11,5 +9,8 @@ scripts/**
.gitignore
Dockerfile*
gogs
!Taskfile.yml
node_modules
**/node_modules
public/dist
**/*.tsbuildinfo
**/.vite
+3 -4
View File
@@ -29,7 +29,7 @@ In addition to the general guides with open source contributions, you would also
### Ask for help
Before opening an issue, please make sure the problem you're encountering isn't already addressed on the [Troubleshooting](https://gogs.io/docs/intro/troubleshooting.html) and [FAQs](https://gogs.io/docs/intro/faqs.html) pages.
Before opening an issue, please make sure the problem you're encountering isn't already addressed on the [Troubleshooting](https://gogs.io/asking/troubleshooting) and [FAQs](https://gogs.io/asking/faq) pages.
### Create a new issue
@@ -65,13 +65,12 @@ Contributing to another codebase is not as simple as code changes, it is also ab
### Things we do not accept
1. Updates to locale files (`conf/locale_xx-XX.ini`) other than the `conf/locale_en-US.ini`. Please read the [guide for localizing Gogs](https://gogs.io/docs/features/i18n).
1. Updates to locale files (`conf/locale_xx-XX.ini`) other than the `conf/locale_en-US.ini`. Please read the [guide for localizing Gogs](https://gogs.io/advancing/localization).
1. Docker compose files.
### Coding guidelines
1. Please read the Sourcegraph's [Go style guide](https://docs.sourcegraph.com/dev/background-information/languages/go).
1. **NO** direct modifications to `.css` files, `.css` files are all generated by `.less` files. You can regenerate `.css` files by executing `task less`.
1. Please read the Sourcegraph's [Go style guide](https://github.com/sourcegraph/sourcegraph-public-snapshot/blob/main/doc/dev/background-information/languages/go.md).
## Your PR is merged!
@@ -42,5 +42,5 @@ On the `main` branch:
- [ ] Send a tweet on the [official Twitter account](https://twitter.com/GogsHQ) for the minor release.
- [ ] Close the milestone for the minor release.
- [ ] [Bump the hard-coded version](https://github.com/gogs/gogs/commit/a98968436cd5841cf691bb0b80c54c81470d1676) to the new develop version, e.g. `0.14.0+dev` -> `0.15.0+dev`.
- [ ] Run `task legacy` to identify deprecated code that is aimed to be removed in current develop version.
- [ ] Run `grep -rnw "\(LEGACY\|Deprecated\)" internal` to identify deprecated code that is aimed to be removed in current develop version.
- [ ] **After 14 days**, publish [GitHub security advisories](https://github.com/gogs/gogs/security) for security patches included in the release.
@@ -13,7 +13,7 @@ _This is generated from the [patch release template](https://github.com/gogs/gog
On the release branch:
- [ ] Make sure all commits are cherry-picked from the `main` branch by checking the patch milestone.
- Run `task build` for every cherry-picked commit to make sure there is no compilation error.
- Run `moon run gogs:build-prod --force` for every cherry-picked commit to make sure there is no compilation error.
- [ ] [Update CHANGELOG on the `main` branch](https://github.com/gogs/gogs/commit/f1102a7a7c545ec221d2906f02fa19170d96f96d) to include entries for the current patch release.
## During release
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
- name: Check out repository
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
# We must fetch at least the immediate parents so that if this is
+72 -77
View File
@@ -15,65 +15,6 @@ on:
types: [ published ]
jobs:
buildx:
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'gogs/gogs' }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
runs-on: ubuntu-latest
permissions:
actions: write
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
with:
platforms: linux/amd64,linux/arm64,linux/arm/v7
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Inspect builder
run: |
echo "Name: ${{ steps.buildx.outputs.name }}"
echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}"
echo "Status: ${{ steps.buildx.outputs.status }}"
echo "Flags: ${{ steps.buildx.outputs.flags }}"
echo "Platforms: ${{ steps.buildx.outputs.platforms }}"
- name: Login to Docker Hub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push images
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: |
gogs/gogs:latest
ghcr.io/gogs/gogs:latest
- name: Scan for container vulnerabilities
uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1
with:
image-ref: gogs/gogs:latest
exit-code: '1'
- name: Send email on failure
uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
if: ${{ failure() }}
with:
smtp_username: ${{ secrets.SMTP_USERNAME }}
smtp_password: ${{ secrets.SMTP_PASSWORD }}
buildx-next:
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'gogs/gogs' }}
concurrency:
@@ -85,7 +26,7 @@ jobs:
contents: read
packages: write
steps:
- name: Checkout code
- name: Check out code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
@@ -126,13 +67,13 @@ jobs:
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: |
gogs/gogs:next-latest
ghcr.io/gogs/gogs:next-latest
registry.digitalocean.com/gogs/gogs:next-latest
gogs/gogs:edge
ghcr.io/gogs/gogs:edge
registry.digitalocean.com/gogs/gogs:edge
- name: Scan for container vulnerabilities
uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
with:
image-ref: gogs/gogs:next-latest
image-ref: gogs/gogs:edge
exit-code: '1'
- name: Send email on failure
uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
@@ -173,7 +114,7 @@ jobs:
permissions:
contents: read
steps:
- name: Checkout code
- name: Check out code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up Docker Buildx
id: buildx
@@ -201,7 +142,7 @@ jobs:
tags: |
ttl.sh/gogs/gogs-${{ steps.short-sha.outputs.sha }}:7d
- name: Scan for container vulnerabilities
uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
with:
image-ref: ttl.sh/gogs/gogs-${{ steps.short-sha.outputs.sha }}:7d
exit-code: '1'
@@ -212,7 +153,7 @@ jobs:
permissions:
contents: read
steps:
- name: Checkout code
- name: Check out code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up Docker Buildx
id: buildx
@@ -241,7 +182,7 @@ jobs:
tags: |
ttl.sh/gogs/gogs-next-${{ steps.short-sha.outputs.sha }}:7d
- name: Scan for container vulnerabilities
uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
with:
image-ref: ttl.sh/gogs/gogs-next-${{ steps.short-sha.outputs.sha }}:7d
exit-code: '1'
@@ -255,6 +196,13 @@ jobs:
contents: read
packages: write
steps:
- name: Check out code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
# Full history with tags is required so the next step can determine
# whether this release is the highest stable version across the repo.
fetch-depth: 0
fetch-tags: true
- name: Compute image tags
run: |
IMAGE_TAG=$(echo $GITHUB_REF_NAME | cut -c 2-)
@@ -263,19 +211,34 @@ jobs:
TAGS="gogs/gogs:$IMAGE_TAG
ghcr.io/gogs/gogs:$IMAGE_TAG"
# Add minor version tag for stable releases (no prerelease suffix per semver).
# For stable releases (no prerelease suffix per semver), add the
# minor-version tag only if this release is the highest patch of that
# minor line, and add `latest` only if this release is the highest
# stable version across the repository. Back-patches on older lines
# must not clobber moving tags.
if [[ ! "$IMAGE_TAG" =~ - ]]; then
STABLE_TAGS=$(git tag --list 'v*' | sed 's/^v//' | grep -v -- '-' || true)
HIGHEST_STABLE=$(echo "$STABLE_TAGS" | sort -V | tail -n 1)
MINOR_TAG=$(echo "$IMAGE_TAG" | cut -d. -f1,2)
TAGS="$TAGS
# `|| true` keeps the step running when `grep` finds no matches,
# since bash runs with `-e -o pipefail` in GitHub Actions.
HIGHEST_IN_MINOR=$(echo "$STABLE_TAGS" | { grep "^${MINOR_TAG}\." || true; } | sort -V | tail -n 1)
if [[ "$IMAGE_TAG" == "$HIGHEST_IN_MINOR" ]]; then
TAGS="$TAGS
gogs/gogs:$MINOR_TAG
ghcr.io/gogs/gogs:$MINOR_TAG"
fi
if [[ "$IMAGE_TAG" == "$HIGHEST_STABLE" ]]; then
TAGS="$TAGS
gogs/gogs:latest
ghcr.io/gogs/gogs:latest"
fi
fi
echo "TAGS<<EOF" >> $GITHUB_ENV
echo "$TAGS" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
with:
@@ -308,6 +271,11 @@ jobs:
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: ${{ env.TAGS }}
- name: Scan for container vulnerabilities
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
with:
image-ref: gogs/gogs:${{ env.IMAGE_TAG }}
exit-code: '1'
- name: Send email on failure
uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
if: ${{ failure() }}
@@ -324,6 +292,13 @@ jobs:
contents: read
packages: write
steps:
- name: Check out code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
# Full history with tags is required so the next step can determine
# whether this release is the highest stable version across the repo.
fetch-depth: 0
fetch-tags: true
- name: Compute image tags
run: |
IMAGE_TAG=$(echo $GITHUB_REF_NAME | cut -c 2-)
@@ -332,19 +307,34 @@ jobs:
TAGS="gogs/gogs:next-$IMAGE_TAG
ghcr.io/gogs/gogs:next-$IMAGE_TAG"
# Add minor version tag for stable releases (no prerelease suffix per semver).
# For stable releases (no prerelease suffix per semver), add the
# minor-version tag only if this release is the highest patch of that
# minor line, and add `next-latest` only if this release is the
# highest stable version across the repository. Back-patches on older
# lines must not clobber moving tags.
if [[ ! "$IMAGE_TAG" =~ - ]]; then
STABLE_TAGS=$(git tag --list 'v*' | sed 's/^v//' | grep -v -- '-' || true)
HIGHEST_STABLE=$(echo "$STABLE_TAGS" | sort -V | tail -n 1)
MINOR_TAG=$(echo "$IMAGE_TAG" | cut -d. -f1,2)
TAGS="$TAGS
# `|| true` keeps the step running when `grep` finds no matches,
# since bash runs with `-e -o pipefail` in GitHub Actions.
HIGHEST_IN_MINOR=$(echo "$STABLE_TAGS" | { grep "^${MINOR_TAG}\." || true; } | sort -V | tail -n 1)
if [[ "$IMAGE_TAG" == "$HIGHEST_IN_MINOR" ]]; then
TAGS="$TAGS
gogs/gogs:next-$MINOR_TAG
ghcr.io/gogs/gogs:next-$MINOR_TAG"
fi
if [[ "$IMAGE_TAG" == "$HIGHEST_STABLE" ]]; then
TAGS="$TAGS
gogs/gogs:next-latest
ghcr.io/gogs/gogs:next-latest"
fi
fi
echo "TAGS<<EOF" >> $GITHUB_ENV
echo "$TAGS" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
with:
@@ -378,6 +368,11 @@ jobs:
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: ${{ env.TAGS }}
- name: Scan for container vulnerabilities
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
with:
image-ref: gogs/gogs:next-${{ env.IMAGE_TAG }}
exit-code: '1'
- name: Send email on failure
uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
if: ${{ failure() }}
+12 -16
View File
@@ -29,26 +29,22 @@ jobs:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout code
- name: Check out code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Install Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: 1.25.x
- name: Install Task
uses: arduino/setup-task@b91d5d2c96a56797b48ac1e0e89220bf64044611 # v2.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
go-version: 1.26.x
- name: Check Go module tidiness and generated files
shell: bash
run: |
go mod tidy
task generate
go generate ./...
STATUS=$(git status --porcelain)
if [ ! -z "$STATUS" ]; then
echo "Unstaged files:"
echo $STATUS
echo "Run 'go mod tidy' or 'task generate' commit them"
echo "Run 'go mod tidy' or 'go generate ./...' and commit them"
exit 1
fi
- name: Run golangci-lint
@@ -61,11 +57,11 @@ jobs:
name: Test
strategy:
matrix:
go-version: [ 1.25.x ]
go-version: [ 1.26.x ]
platform: [ ubuntu-latest, macos-latest ]
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout code
- name: Check out code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Install Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
@@ -89,11 +85,11 @@ jobs:
name: Test Windows
strategy:
matrix:
go-version: [ 1.25.x ]
go-version: [ 1.26.x ]
platform: [ windows-latest ]
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout code
- name: Check out code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Install Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
@@ -112,7 +108,7 @@ jobs:
name: Postgres
strategy:
matrix:
go-version: [ 1.25.x ]
go-version: [ 1.26.x ]
platform: [ ubuntu-latest ]
runs-on: ${{ matrix.platform }}
services:
@@ -128,7 +124,7 @@ jobs:
ports:
- 5432:5432
steps:
- name: Checkout code
- name: Check out code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Install Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
@@ -151,13 +147,13 @@ jobs:
name: MySQL
strategy:
matrix:
go-version: [ 1.25.x ]
go-version: [ 1.26.x ]
platform: [ ubuntu-22.04 ] # Use the lowest version possible for backwards compatibility
runs-on: ${{ matrix.platform }}
steps:
- name: Start MySQL server
run: sudo systemctl start mysql
- name: Checkout code
- name: Check out code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Install Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
+93 -23
View File
@@ -23,29 +23,41 @@ permissions:
jobs:
build:
name: Build ${{ matrix.goos }}/${{ matrix.goarch }}${{ matrix.suffix }}
runs-on: ubuntu-latest
if: ${{ github.repository == 'gogs/gogs' }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- {goos: linux, goarch: amd64}
- {goos: linux, goarch: arm64}
- {goos: linux, goarch: "386"}
- {goos: darwin, goarch: amd64}
- {goos: darwin, goarch: arm64}
- {goos: windows, goarch: amd64}
- {goos: windows, goarch: arm64}
- {goos: windows, goarch: "386"}
- {goos: windows, goarch: amd64, suffix: "_mws", tags: minwinsvc}
- {goos: windows, goarch: arm64, suffix: "_mws", tags: minwinsvc}
- {goos: windows, goarch: "386", suffix: "_mws", tags: minwinsvc}
- {goos: linux, goarch: amd64, runner: ubuntu-latest}
- {goos: linux, goarch: arm64, runner: ubuntu-latest}
- {goos: linux, goarch: "386", runner: ubuntu-latest}
- {goos: darwin, goarch: amd64, runner: macos-latest}
- {goos: darwin, goarch: arm64, runner: macos-latest}
- {goos: windows, goarch: amd64, runner: ubuntu-latest}
- {goos: windows, goarch: arm64, runner: ubuntu-latest}
- {goos: windows, goarch: "386", runner: ubuntu-latest}
- {goos: windows, goarch: amd64, suffix: "_mws", tags: minwinsvc, runner: ubuntu-latest}
- {goos: windows, goarch: arm64, suffix: "_mws", tags: minwinsvc, runner: ubuntu-latest}
- {goos: windows, goarch: "386", suffix: "_mws", tags: minwinsvc, runner: ubuntu-latest}
steps:
- name: Checkout code
- name: Check out code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Go
- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: 1.25.x
go-version: 1.26.x
- name: Set up pnpm
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- name: Set up Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24
cache: pnpm
- name: Build web assets
run: |
pnpm install --frozen-lockfile
pnpm --filter gogs-web run build
- name: Determine version
id: version
run: |
@@ -70,9 +82,9 @@ jobs:
BINARY_NAME="gogs.exe"
fi
TAGS_FLAG=""
TAGS="prod"
if [ -n "${{ matrix.tags }}" ]; then
TAGS_FLAG="-tags ${{ matrix.tags }}"
TAGS="$TAGS ${{ matrix.tags }}"
fi
go build -v \
@@ -80,8 +92,44 @@ jobs:
-X \"gogs.io/gogs/internal/conf.BuildTime=$(date -u '+%Y-%m-%d %I:%M:%S %Z')\"
-X \"gogs.io/gogs/internal/conf.BuildCommit=$(git rev-parse HEAD)\"
" \
$TAGS_FLAG \
-tags "$TAGS" \
-trimpath -o "$BINARY_NAME" ./cmd/gogs
- name: Import Apple signing certificate
if: ${{ matrix.goos == 'darwin' }}
env:
APPLE_DEVELOPER_ID_CERTIFICATE_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_BASE64 }}
APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }}
APPLE_KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
run: |
if [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" ] || [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" ] || [ -z "$APPLE_KEYCHAIN_PASSWORD" ]; then
echo "Missing required Apple signing secrets." >&2
exit 1
fi
CERTIFICATE_PATH="$RUNNER_TEMP/developer_id_application.p12"
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
printf '%s' "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" | base64 -d > "$CERTIFICATE_PATH"
security create-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "$CERTIFICATE_PATH" -P "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
security list-keychains -d user -s "$KEYCHAIN_PATH"
security default-keychain -s "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
- name: Sign macOS binary
if: ${{ matrix.goos == 'darwin' }}
env:
APPLE_DEVELOPER_IDENTITY: ${{ secrets.APPLE_DEVELOPER_IDENTITY }}
run: |
if [ -z "$APPLE_DEVELOPER_IDENTITY" ]; then
echo "Missing required Apple signing identity secret." >&2
exit 1
fi
security find-identity -v -p codesigning
codesign --force --options runtime --timestamp --sign "$APPLE_DEVELOPER_IDENTITY" "gogs"
codesign --verify --verbose=2 "gogs"
- name: Prepare archive contents
run: |
mkdir -p dist/gogs
@@ -90,7 +138,7 @@ jobs:
BINARY_NAME="gogs.exe"
fi
cp "$BINARY_NAME" dist/gogs/
cp LICENSE README.md README_ZH.md dist/gogs/
cp LICENSE README.md dist/gogs/
cp -r scripts dist/gogs/
- name: Create archives
working-directory: dist
@@ -103,6 +151,28 @@ jobs:
if [ "${{ matrix.goos }}" = "linux" ]; then
tar -czvf "${ARCHIVE_BASE}.tar.gz" gogs
fi
- name: Notarize macOS archive
if: ${{ matrix.goos == 'darwin' }}
env:
APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }}
APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
run: |
if [ -z "$APPLE_NOTARY_ISSUER_ID" ] || [ -z "$APPLE_NOTARY_KEY_BASE64" ] || [ -z "$APPLE_NOTARY_KEY_ID" ]; then
echo "Missing required Apple notarization secrets." >&2
exit 1
fi
VERSION="${{ steps.version.outputs.version }}"
ARCHIVE_PATH="dist/gogs_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}${{ matrix.suffix }}.zip"
NOTARY_KEY_PATH="$RUNNER_TEMP/AuthKey_${APPLE_NOTARY_KEY_ID}.p8"
printf '%s' "$APPLE_NOTARY_KEY_BASE64" | base64 -d > "$NOTARY_KEY_PATH"
xcrun notarytool submit "$ARCHIVE_PATH" \
--key "$NOTARY_KEY_PATH" \
--key-id "$APPLE_NOTARY_KEY_ID" \
--issuer "$APPLE_NOTARY_ISSUER_ID" \
--wait
- name: Upload to release
env:
GH_TOKEN: ${{ github.token }}
@@ -112,14 +182,14 @@ jobs:
if [ "${{ github.event_name }}" != "release" ]; then
git tag -f "$RELEASE_TAG"
git push origin "$RELEASE_TAG" --force || true
RELEASE_TITLE="Release Archive Testing"
RELEASE_TITLE="Release archive testing"
RELEASE_NOTES="Automated testing release for workflow development."
if [ "$RELEASE_TAG" = "latest-commit-build" ]; then
RELEASE_TITLE="Latest Commit Build"
RELEASE_TITLE="Latest commit build"
RELEASE_NOTES="Automated build from the latest commit on main branch. This release is updated automatically with every push to main."
fi
gh release view "$RELEASE_TAG" || gh release create "$RELEASE_TAG" --title "$RELEASE_TITLE" --notes "$RELEASE_NOTES" --prerelease
fi
+54
View File
@@ -0,0 +1,54 @@
name: Web
on:
push:
branches:
- main
- 'release/**'
paths:
- 'web/**'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- 'package.json'
- 'conf/locale/locale_*.ini'
- '.github/workflows/web.yml'
pull_request:
paths:
- 'web/**'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- 'package.json'
- 'conf/locale/locale_*.ini'
- '.github/workflows/web.yml'
permissions:
contents: read
jobs:
web:
name: Lint and build
runs-on: ubuntu-latest
env:
MOON_COLOR: "true"
FORCE_COLOR: "1"
steps:
- name: Check out code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 0
- name: Set up pnpm
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- name: Set up Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24
cache: pnpm
- name: Set up moon
uses: moonrepo/setup-toolchain@261c62cb5b0f580c7be7c8cd0f023a2e96756095 # v0.6.4
with:
auto-install: false
- name: Lint
run: moon run web:lint
- name: Build
run: moon run web:build
- name: Check for uncommitted changes
run: git diff --exit-code --stat
+5 -2
View File
@@ -1,6 +1,10 @@
# Build artifacts
# Build artifacts and cache
.bin/
dist/
.moon/cache/
node_modules/
.agents/skills/
.claude/skills
# Runtime data
log/
@@ -9,7 +13,6 @@ data/
# Configuration and application files
.idea/
.task/
.envrc
# System junk
+4
View File
@@ -0,0 +1,4 @@
$schema: "https://moonrepo.dev/schemas/tasks.json"
taskOptions:
outputStyle: "stream"
+9
View File
@@ -0,0 +1,9 @@
$schema: "https://moonrepo.dev/schemas/workspace.json"
projects:
gogs: "."
web: "web"
vcs:
client: "git"
defaultBranch: "main"
-1
View File
@@ -1 +0,0 @@
main
-1
View File
@@ -1 +0,0 @@
web: ./gogs web -p ${PORT:=3000}
-24
View File
@@ -1,24 +0,0 @@
#!/bin/sh
set -e
APP_NAME="gogs"
CLI="${APP_NAME}"
APP_USER=$(${CLI} config:get APP_USER)
APP_GROUP=$(${CLI} config:get APP_GROUP)
APP_CONFIG="/etc/${APP_NAME}/conf/app.ini"
mkdir -p "$(dirname ${APP_CONFIG})"
chown "${APP_USER}"."${APP_GROUP}" "$(dirname ${APP_CONFIG})"
[ -f ${APP_CONFIG} ] || ${CLI} run cp conf/app.ini ${APP_CONFIG}
${CLI} config:set USER="${APP_USER}"
sed -i "s|RUN_USER = git|RUN_USER = ${APP_USER}|" ${APP_CONFIG}
sed -i "s|RUN_MODE = dev|RUN_MODE = prod|" ${APP_CONFIG}
${CLI} config:set GOGS_CUSTOM=/etc/${APP_NAME}
# scale
${CLI} scale web=1 || true
# restart the service
service gogs restart || true
-35
View File
@@ -1,35 +0,0 @@
targets:
debian-11: &debian
build_dependencies:
- libpam0g-dev
dependencies:
- libpam0g
- git
debian-12:
<<: *debian
debian-13:
<<: *debian
debian-14:
<<: *debian
ubuntu-18.04:
<<: *debian
ubuntu-20.04:
<<: *debian
ubuntu-22.04:
<<: *debian
ubuntu-24.04:
<<: *debian
centos-9:
build_dependencies:
- pam-devel
# required for Go buildpack
- perl-Digest-SHA
dependencies:
- pam
- git
before:
- mv .packager/Procfile .
after:
- mv bin/gogs gogs
after_install: ./.packager/hooks/postinst
buildpack: https://github.com/heroku/heroku-buildpack-go.git#main
+18 -4
View File
@@ -9,6 +9,8 @@ This applies to all texts, including but not limited to UI, documentation, code
- Use sentence case. Preserve original casing for brand names.
- End with a period for a full sentence.
- Never use em dashes (`—`) or en dashes (``) in prose. Rewrite the sentence with a comma, period, colon, or parentheses instead. Exception: em/en dashes are allowed as visual separators in UI design (e.g., between a title and description, in a terminal prompt label) where they function as a graphic element rather than punctuation.
- Do not overuse semicolons. Two short sentences are almost always clearer than one sentence joined by a semicolon. Reserve the semicolon for the rare case where the two clauses are so tightly coupled that splitting them loses meaning, never as a default em-dash replacement or a way to chain related thoughts.
- Do not add comments that repeat what the code is doing, always prefer more descriptive names. Do add comments for intentions that aren't obvious via reading the code alone. This rule takes precedence over matching existing patterns.
## Coding guidelines
@@ -16,10 +18,21 @@ This applies to all texts, including but not limited to UI, documentation, code
- Use `github.com/cockroachdb/errors` for error handling.
- Use `github.com/stretchr/testify` for assertions in tests. Be mindful about the choice of `require` and `assert`, the former should be used when the test cannot proceed meaningfully after a failed assertion.
## Localization
- Only edit `conf/locale/locale_en-US.ini`. The other `locale_*.ini` files are community-maintained translations. Do not add, remove, or rewrite keys in them, even when removing keys that are dead on the Go/template side.
## UI guidelines
- Design mobile-friendly. Every UI must look and work well on narrow viewports before adding desktop refinements via responsive breakpoints. Test at ~375px width before considering a UI done.
- Meet WCAG 2.2 AA at minimum. Specifically: every interactive control has a discernible accessible name (visible label or `aria-label`); color is never the sole carrier of information (pair with text, icon, or shape); text and meaningful icons meet 4.5:1 contrast against their background (3:1 for large text and UI components); focus is always visible and never trapped; touch targets are at least 24×24 CSS px (40×40 preferred). When unsure, lean toward more contrast, larger targets, and explicit labels.
- For work under `web/`, follow the patterns in [`web/DESIGN.md`](web/DESIGN.md) (typography, color hierarchy, surface chrome, file naming, accessibility specifics). Update that doc when a pattern is used in two places.
- When a page needs server data to render, fetch it in the TanStack Router route's `loader` so the page only mounts after the response arrives. Do not fire that fetch from a `useEffect` inside the page component, which causes a flash of empty UI before the data lands.
## Build instructions
- Prefer `task` command over vanilla `go` command when available. Use `--force` flag when necessary.
- Run `task lint` after every time you finish changing code, and fix all linter errors.
- Prefer `moon run <project>:<task>` over vanilla `go` or `pnpm` commands when available (e.g. `moon run gogs:build`, `moon run web:dev`). Pass `--force` to bypass cache when necessary.
- Run `moon run gogs:lint` after every time you finish changing Go code, and `moon run web:lint` after changing frontend code; fix all linter errors.
## Tool-use guidance
@@ -28,5 +41,6 @@ This applies to all texts, including but not limited to UI, documentation, code
## Source code control
- When pushing changes to a pull request from a fork, use SSH address and do not add remote.
- Never automatically executes commands that touches Git history even if the session does not require approvals, including but not limited to `rebase`, `commit`, `push`, `pull`, `reset`, `amend`. Exceptions are only allowed case-by-case.
- Do not amend commits unless being explicitly asked to do so.
- Never commit on the `main` branch directly unless being explicitly asked to do so. A single ask only grants a single commit action on the `main` branch.
- Never amend commits unless being explicitly asked to do so.
- When creating a git worktree, the worktree directory name must match its branch name. Do not use random or generated suffixes.
+30
View File
@@ -4,9 +4,39 @@ All notable changes to Gogs are documented in this file.
## 0.15.0+dev (`main`)
### Changed
- Docker builds from `main` are now published only as `gogs/gogs:edge`, using the next-generation `Dockerfile.next`. The legacy `Dockerfile` no longer produces `main` builds. The `gogs/gogs:latest` and `gogs/gogs:next-latest` tags now always point to the highest published stable release, never to a back-patch on an older line. [#8278](https://github.com/gogs/gogs/pull/8278)
### Fixed
- _Security:_ Denial of service in repository and wiki file listing pages via crafted file names. [#8116](https://github.com/gogs/gogs/pull/8116) - [GHSA-3qq3-668m-v9mj](https://github.com/gogs/gogs/security/advisories/GHSA-3qq3-668m-v9mj)
- _Security:_ Reverse proxy authentication header was honored from any remote address, allowing user impersonation when Gogs was reachable directly. The header is now only trusted from addresses listed in `[auth] TRUSTED_PROXY_IPS`. [#8264](https://github.com/gogs/gogs/pull/8264) - [GHSA-w6j9-vw59-27wv](https://github.com/gogs/gogs/security/advisories/GHSA-w6j9-vw59-27wv)
- _Security:_ Server-side request forgery in webhook deliveries via HTTP redirects to local network addresses. [#8263](https://github.com/gogs/gogs/pull/8263) - [GHSA-c4v7-xg93-qf8g](https://github.com/gogs/gogs/security/advisories/GHSA-c4v7-xg93-qf8g)
- _Security:_ The "remember me" auto-login cookie was derived from database columns, so an attacker with a database dump could forge a valid cookie for any user. The auto-login cookie path has been removed entirely. Persistence is now provided by the server-issued session cookie. [#8289](https://github.com/gogs/gogs/pull/8289) - [GHSA-4pph-25p3-pw73](https://github.com/gogs/gogs/security/advisories/GHSA-4pph-25p3-pw73)
### Removed
- The `gogs cert` subcommand. [#8153](https://github.com/gogs/gogs/pull/8153)
- The `[email] DISABLE_HELO` configuration option. HELO/EHLO is now always sent during SMTP handshake. [#8164](https://github.com/gogs/gogs/pull/8164)
- Support for MSSQL as the database backend. Stay on 0.14 for continued usage. [#8173](https://github.com/gogs/gogs/pull/8173)
- Support for `memcache` as the cache adapter. Stay on 0.14 for continued usage. [#8270](https://github.com/gogs/gogs/pull/8270)
- The `/debug`, `/debug/pprof/*`, `/debug/profile/*`, and `/urlmap.json` endpoints. [#8271](https://github.com/gogs/gogs/pull/8271)
## 0.14.2
### Fixed
- _Security:_ Cross-repository LFS object overwrite via missing content hash verification. [#8166](https://github.com/gogs/gogs/pull/8166) - [GHSA-gmf8-978x-2fg2](https://github.com/gogs/gogs/security/advisories/GHSA-gmf8-978x-2fg2)
- _Security:_ Stored XSS via data URI in issue comments. [#8174](https://github.com/gogs/gogs/pull/8174) - [GHSA-xrcr-gmf5-2r8j](https://github.com/gogs/gogs/security/advisories/GHSA-xrcr-gmf5-2r8j)
- _Security:_ Release tag option injection in release deletion. [#8175](https://github.com/gogs/gogs/pull/8175) - [GHSA-v9vm-r24h-6rqm](https://github.com/gogs/gogs/security/advisories/GHSA-v9vm-r24h-6rqm)
- _Security:_ Stored XSS in branch and wiki views through author and committer names. [#8176](https://github.com/gogs/gogs/pull/8176) - [GHSA-vgvf-m4fw-938j](https://github.com/gogs/gogs/security/advisories/GHSA-vgvf-m4fw-938j)
- _Security:_ DOM-based XSS via issue meta selection on the issue page. [#8178](https://github.com/gogs/gogs/pull/8178) - [GHSA-vgjm-2cpf-4g7c](https://github.com/gogs/gogs/security/advisories/GHSA-vgjm-2cpf-4g7c)
- Unable to update files via web editor and API. [#8184](https://github.com/gogs/gogs/pull/8184)
### Removed
- Support for passing API access tokens via URL query parameters (`token`, `access_token`). Use the `Authorization` header instead. [#8177](https://github.com/gogs/gogs/pull/8177) - [GHSA-x9p5-w45c-7ffc](https://github.com/gogs/gogs/security/advisories/GHSA-x9p5-w45c-7ffc)
## 0.14.1
+17 -5
View File
@@ -1,4 +1,13 @@
FROM golang:alpine3.21 AS binarybuilder
FROM --platform=$BUILDPLATFORM node:24-alpine AS webbuilder
RUN corepack enable
WORKDIR /src
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY web ./web
COPY conf/locale ./conf/locale
RUN pnpm install --frozen-lockfile
RUN pnpm --filter gogs-web run build
FROM golang:1.26-alpine3.23 AS binarybuilder
RUN apk --no-cache --no-progress add --virtual \
build-deps \
build-base \
@@ -7,11 +16,13 @@ RUN apk --no-cache --no-progress add --virtual \
WORKDIR /gogs.io/gogs
COPY . .
COPY --from=webbuilder /src/public/dist ./public/dist
RUN ./docker/build/install-task.sh
RUN TAGS="cert pam" task build
RUN go build -v -trimpath -tags "pam prod" \
-ldflags "-X 'gogs.io/gogs/internal/conf.BuildTime=$(date -u '+%Y-%m-%d %I:%M:%S %Z')' -X 'gogs.io/gogs/internal/conf.BuildCommit=$(git rev-parse HEAD)'" \
-o .bin/gogs ./cmd/gogs
FROM alpine:3.21
FROM alpine:3.23
RUN apk --no-cache --no-progress add \
bash \
ca-certificates \
@@ -23,7 +34,8 @@ RUN apk --no-cache --no-progress add \
shadow \
socat \
tzdata \
rsync
rsync \
"zlib>1.3.2"
ENV GOGS_CUSTOM=/data/gogs
+16 -4
View File
@@ -1,4 +1,13 @@
FROM golang:alpine3.23 AS binarybuilder
FROM --platform=$BUILDPLATFORM node:24-alpine AS webbuilder
RUN corepack enable
WORKDIR /src
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY web ./web
COPY conf/locale ./conf/locale
RUN pnpm install --frozen-lockfile
RUN pnpm --filter gogs-web run build
FROM golang:1.26-alpine3.23 AS binarybuilder
RUN apk --no-cache --no-progress add --virtual \
build-deps \
build-base \
@@ -7,9 +16,11 @@ RUN apk --no-cache --no-progress add --virtual \
WORKDIR /gogs.io/gogs
COPY . .
COPY --from=webbuilder /src/public/dist ./public/dist
RUN ./docker/build/install-task.sh
RUN TAGS="cert pam" task build
RUN go build -v -trimpath -tags "pam prod" \
-ldflags "-X 'gogs.io/gogs/internal/conf.BuildTime=$(date -u '+%Y-%m-%d %I:%M:%S %Z')' -X 'gogs.io/gogs/internal/conf.BuildCommit=$(git rev-parse HEAD)'" \
-o .bin/gogs ./cmd/gogs
FROM alpine:3.23
@@ -26,7 +37,8 @@ RUN apk --no-cache --no-progress add \
curl \
git \
linux-pam \
openssh-keygen
openssh-keygen \
"zlib>1.3.2"
ENV GOGS_CUSTOM=/data/gogs
+21 -17
View File
@@ -1,8 +1,19 @@
![gogs-brand](https://user-images.githubusercontent.com/2946214/146899259-6a8b58ad-8d6e-40d2-ab02-79dc6aadabbf.png)
[![GitHub Workflow Status](https://img.shields.io/github/checks-status/gogs/gogs/main?logo=github&style=for-the-badge)](https://github.com/gogs/gogs/actions?query=branch%3Amain) [![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg?style=for-the-badge&logo=sourcegraph)](https://sourcegraph.com/github.com/gogs/gogs)
👉 Deploy on DigitalOcean and [get $200 in free credits](https://m.do.co/c/5aeb02268b55)!
<p>
<div align="center">
<img src="docs/images/logo-light.svg#gh-light-mode-only" alt="Gogs">
<img src="docs/images/logo-dark.svg#gh-dark-mode-only" alt="Gogs">
</div>
<div align="center">
<a href="https://github.com/gogs/gogs/actions?query=branch%3Amain"><img
src="https://img.shields.io/github/checks-status/gogs/gogs/main?logo=github&style=for-the-badge" alt="GitHub Workflow Status"></a>
<a href="https://sourcegraph.com/github.com/gogs/gogs"><img
src="https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg?style=for-the-badge&logo=sourcegraph" alt="Sourcegraph"></a>
</div>
<div align="center">
👉 Deploy on DigitalOcean and <a href="https://m.do.co/c/5aeb02268b55">get $200 in free credits</a>!
</div>
</p>
## 🔮 Vision
@@ -13,10 +24,10 @@ The Gogs (`/gɑgz/`) project aims to build a simple, stable and extensible self-
- Please visit [our home page](https://gogs.io) for user documentation.
- Please refer to [CHANGELOG.md](CHANGELOG.md) for list of changes in each releases.
- Want to try it before doing anything else? Do it [online](https://try.gogs.io/gogs/gogs)!
- Having trouble? Help yourself with [troubleshooting](https://gogs.io/docs/intro/troubleshooting.html) or ask questions in [Discussions](https://github.com/gogs/gogs/discussions).
- Want to help with localization? Check out the [localization documentation](https://gogs.io/docs/features/i18n.html).
- Having trouble? Help yourself with [troubleshooting](https://gogs.io/asking/troubleshooting) or ask questions in [Discussions](https://github.com/gogs/gogs/discussions).
- Want to help with localization? Check out the [localization documentation](https://gogs.io/advancing/localization).
- Ready to get hands dirty? Read our [contributing guide](.github/CONTRIBUTING.md).
- Hmm... What about APIs? We have experimental support with [documentation](https://github.com/gogs/docs-api).
- Hmm... What about APIs? We have experimental support with [documentation](https://gogs.io/api-reference).
## 💌 Features
@@ -47,15 +58,7 @@ The Gogs (`/gɑgz/`) project aims to build a simple, stable and extensible self-
## 📜 Installation
Make sure you install the [prerequisites](https://gogs.io/docs/installation) first.
There are 6 ways to install Gogs:
- [Install from binary](https://gogs.io/docs/installation/install_from_binary.html)
- [Install from source](https://gogs.io/docs/installation/install_from_source.html)
- [Install from packages](https://gogs.io/docs/installation/install_from_packages.html)
- [Ship with Docker](https://github.com/gogs/gogs/tree/main/docker)
- [Try with Vagrant](https://github.com/geerlingguy/ansible-vagrant-examples/tree/master/gogs)
Please follow [the guide in our documentation](https://gogs.io/getting-started/installation).
### Deploy to cloud
@@ -94,7 +97,8 @@ There are 6 ways to install Gogs:
Other acknowledgments:
- Thanks [Egon Elbre](https://twitter.com/egonelbre) for designing the original version of the logo.
- Thanks [Crowdin](https://crowdin.com/project/gogs) for sponsoring open source translation plan.
- Thanks [Mintlify](https://mintlify.com) for sponsoring open source documentation plan.
- Thanks [Crowdin](https://crowdin.com) for sponsoring open source translation plan.
- Thanks [Buildkite](https://buildkite.com) for sponsoring open source CI/CD plan.
## 👋 Contributors
-102
View File
@@ -1,102 +0,0 @@
# Gogs
Gogs 是一款极易搭建的自助 Git 服务。
## 项目愿景
Gogs`/gɑgz/`)项目旨在打造一个以最简便的方式搭建简单、稳定和可扩展的自助 Git 服务。使用 Go 语言开发使得 Gogs 能够通过独立的二进制分发,并且支持 Go 语言支持的 **所有平台**,包括 Linux、macOS、Windows 和基于 ARM 的操作系统。
## 概览
- 请移步[官网](https://gogs.io)查看用户使用文档
- 请通过 [CHANGELOG.md](CHANGELOG.md) 文件查看各个版本的变更历史
- 想要先睹为快?直接去[在线体验](https://try.gogs.io/gogs/gogs)吧!
- 使用过程中遇到问题?尝试[故障排查](https://gogs.io/docs/intro/troubleshooting.html)或者前往[用户论坛](https://discuss.gogs.io/)获取帮助
- 希望帮助多国语言的翻译吗?请查看[本地化文档](https://gogs.io/docs/features/i18n.html)
- 准备搞点事情?请阅读[开发指南](docs/dev/local_development.md)配置开发环境
- 想调用 API 吗?请查看[文档](https://github.com/gogs/docs-api)吧
## 主要特性
- 控制面板、用户页面以及活动时间线
- 通过 SSH、HTTP 和 HTTPS 协议操作仓库
- 管理用户、组织和仓库
- 仓库和组织级 Webhook,包括 Slack、Discord 和钉钉
- 仓库 Git 钩子、部署密钥和 Git LFS
- 仓库工单(Issue)、合并请求(Pull Request)、Wiki、保护分支和多人协作
- 从其它代码平台迁移和镜像仓库以及 Wiki
- 在线编辑仓库文件和 Wiki
- Jupyter Notebook 和 PDF 的渲染
- 通过 SMTP、LDAP、反向代理、GitHub.com 和 GitHub 企业版进行用户认证
- 开启两步验证(2FA)登录
- 自定义 HTML 模板、静态文件和许多其它组件
- 多样的数据库后端,包括 PostgreSQL、MySQL、SQLite3 和 [TiDB](https://github.com/pingcap/tidb)
- 超过 [31 种语言](https://crowdin.com/project/gogs)的本地化
## 硬件要求
- 最低的系统硬件要求为一个廉价的树莓派
- 如果用于团队项目管理,建议使用 2 核 CPU 及 512MB 内存
- 当团队成员大量增加时,可以考虑添加 CPU 核数,内存占用保持不变
## 浏览器支持
- 请根据 [Semantic UI](https://github.com/Semantic-Org/Semantic-UI#browser-support) 查看具体支持的浏览器版本。
- 官方支持的最小 UI 尺寸为 **1024*768**,UI 不一定会在更小尺寸的设备上被破坏,但我们无法保证且不会修复。
## 安装部署
在安装 Gogs 之前,您需要先安装 [基本环境](https://gogs.io/docs/installation)。
然后,您可以通过以下 6 种方式来安装 Gogs:
- [二进制安装](https://gogs.io/docs/installation/install_from_binary.html)
- [源码安装](https://gogs.io/docs/installation/install_from_source.html)
- [包管理安装](https://gogs.io/docs/installation/install_from_packages.html)
- [采用 Docker 部署](https://github.com/gogs/gogs/tree/main/docker)
- [通过 Vagrant 安装](https://github.com/geerlingguy/ansible-vagrant-examples/tree/master/gogs)
- [通过基于 Kubernetes 的 Helm Charts](https://github.com/helm/charts/tree/master/incubator/gogs)
### 云端部署
- [OpenShift](https://github.com/tkisme/gogs-openshift)
- [Cloudron](https://cloudron.io/appstore.html#io.gogs.cloudronapp)
- [Scaleway](https://www.scaleway.com/imagehub/gogs/)
- [Sandstorm](https://github.com/cem/gogs-sandstorm)
- [sloppy.io](https://github.com/sloppyio/quickstarters/tree/master/gogs)
- [YunoHost](https://github.com/mbugeia/gogs_ynh)
- [DPlatform](https://github.com/j8r/DPlatform)
- [LunaNode](https://github.com/LunaNode/launchgogs)
### 使用教程
- [使用 Gogs 搭建自己的 Git 服务器](https://blog.mynook.info/post/host-your-own-git-server-using-gogs/)
- [阿里云上 Ubuntu 14.04 64 位安装 Gogs](http://my.oschina.net/luyao/blog/375654)
## 软件、服务以及产品支持
- [Fabric8](http://fabric8.io/)DevOps
- [Jenkins](https://plugins.jenkins.io/gogs-webhook/)CI
- [Taiga](https://taiga.io/)(项目管理)
- [Puppet](https://forge.puppet.com/Siteminds/gogs)IT
- [Kanboard](https://github.com/kanboard/plugin-gogs-webhook)(项目管理)
- [BearyChat](https://bearychat.com/)(团队交流)
- [GitPitch](https://gitpitch.com/)Markdown 演示)
- [Synology](https://www.synology.com)Docker
- [Syncloud](https://syncloud.org/)(应用商店)
## 特别鸣谢
- 感谢 [Egon Elbre](https://twitter.com/egonelbre) 设计的 Logo。
- 感谢 [DigitalOcean](https://www.digitalocean.com) 和 [MonoVM](https://monovm.com) 提供服务器赞助。
- 感谢 [Crowdin](https://crowdin.com/project/gogs) 提供免费的开源项目本地化支持。
- 感谢 [Buildkite](https://buildkite.com) 提供免费的开源项目 CI/CD 支持。
## 贡献成员
- 您可以通过查看 [贡献者页面](https://github.com/gogs/gogs/graphs/contributors) 获取 TOP 100 的贡献者列表。
- 您可以通过查看 [TRANSLATORS](conf/locale/TRANSLATORS) 文件获取公开的翻译人员列表。
## 授权许可
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/gogs/gogs/blob/main/LICENSE) 文件中。
-101
View File
@@ -1,101 +0,0 @@
version: '3'
vars:
BINARY_EXT:
sh: echo '{{if eq OS "windows"}}.exe{{end}}'
tasks:
default:
deps: [build]
web:
desc: Build the binary and start the web server
deps: [build]
env:
GOGS_WORK_DIR: '{{.ROOT_DIR}}'
cmds:
- .bin/gogs web
build:
desc: Build the binary
cmds:
- go build -v
-ldflags '
-X "{{.PKG_PATH}}.BuildTime={{.BUILD_TIME}}"
-X "{{.PKG_PATH}}.BuildCommit={{.BUILD_COMMIT}}"
'
-tags '{{.TAGS}}'
-trimpath -o .bin/gogs{{.BINARY_EXT}} ./cmd/gogs
vars:
PKG_PATH: gogs.io/gogs/internal/conf
BUILD_TIME:
sh: date -u '+%Y-%m-%d %I:%M:%S %Z'
BUILD_COMMIT:
sh: git rev-parse HEAD
sources:
- go.mod
- cmd/gogs/*.go
- internal/**/*.go
- conf/**/*
- public/**/*
- templates/**/*
- custom/**/*
method: timestamp
generate-schemadoc:
desc: Generate database schema documentation
cmds:
- go generate ./internal/database/schemadoc
generate:
desc: Run all go:generate commands
cmds:
- go generate ./...
test:
desc: Run all tests.
cmds:
- go test -cover -race ./...
clean:
desc: Cleans up system meta files
cmds:
- find . -name "*.DS_Store" -type f -delete
less:
desc: Generate CSS from LESS files
cmds:
- lessc --clean-css --source-map "public/less/gogs.less" public/css/gogs.min.css
fixme:
desc: Show all occurrences of "FIXME"
cmds:
- grep -rnw "FIXME" internal
todo:
desc: Show all occurrences of "TODO"
cmds:
- grep -rnw "TODO" internal
legacy:
desc: Identify legacy and deprecated lines
cmds:
- grep -rnw "\(LEGACY\|Deprecated\)" internal
drop-test-db:
desc: Drop the test database
cmds:
- |
for dbname in $(psql -Xc "copy (select datname from pg_database where datname like 'gogs-%') to stdout"); do
dropdb "$dbname"
echo "dropped $dbname"
done
lint:
desc: Run all linters
cmds:
- golangci-lint run
docs:
desc: Start docs server
cmds:
- cd docs && mint dev --port 3333
+23 -23
View File
@@ -7,7 +7,7 @@ import (
"runtime"
"github.com/cockroachdb/errors"
"github.com/urfave/cli"
"github.com/urfave/cli/v3"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/database"
@@ -19,15 +19,15 @@ var (
Usage: "Perform admin operations on command line",
Description: `Allow using internal logic of Gogs without hacking into the source code
to make automatic initialization process more smoothly`,
Subcommands: []cli.Command{
subcmdCreateUser,
subcmdDeleteInactivateUsers,
subcmdDeleteRepositoryArchives,
subcmdDeleteMissingRepositories,
subcmdGitGcRepos,
subcmdRewriteAuthorizedKeys,
subcmdSyncRepositoryHooks,
subcmdReinitMissingRepositories,
Commands: []*cli.Command{
&subcmdCreateUser,
&subcmdDeleteInactivateUsers,
&subcmdDeleteRepositoryArchives,
&subcmdDeleteMissingRepositories,
&subcmdGitGcRepos,
&subcmdRewriteAuthorizedKeys,
&subcmdSyncRepositoryHooks,
&subcmdReinitMissingRepositories,
},
}
@@ -129,16 +129,16 @@ to make automatic initialization process more smoothly`,
}
)
func runCreateUser(c *cli.Context) error {
if !c.IsSet("name") {
func runCreateUser(ctx context.Context, cmd *cli.Command) error {
if !cmd.IsSet("name") {
return errors.New("Username is not specified")
} else if !c.IsSet("password") {
} else if !cmd.IsSet("password") {
return errors.New("Password is not specified")
} else if !c.IsSet("email") {
} else if !cmd.IsSet("email") {
return errors.New("Email is not specified")
}
err := conf.Init(c.String("config"))
err := conf.Init(configFromLineage(cmd))
if err != nil {
return errors.Wrap(err, "init configuration")
}
@@ -149,13 +149,13 @@ func runCreateUser(c *cli.Context) error {
}
user, err := database.Handle.Users().Create(
context.Background(),
c.String("name"),
c.String("email"),
ctx,
cmd.String("name"),
cmd.String("email"),
database.CreateUserOptions{
Password: c.String("password"),
Password: cmd.String("password"),
Activated: true,
Admin: c.Bool("admin"),
Admin: cmd.Bool("admin"),
},
)
if err != nil {
@@ -166,9 +166,9 @@ func runCreateUser(c *cli.Context) error {
return nil
}
func adminDashboardOperation(operation func() error, successMessage string) func(*cli.Context) error {
return func(c *cli.Context) error {
err := conf.Init(c.String("config"))
func adminDashboardOperation(operation func() error, successMessage string) func(context.Context, *cli.Command) error {
return func(_ context.Context, cmd *cli.Command) error {
err := conf.Init(configFromLineage(cmd))
if err != nil {
return errors.Wrap(err, "init configuration")
}
+13 -13
View File
@@ -11,13 +11,13 @@ import (
"github.com/cockroachdb/errors"
"github.com/unknwon/cae/zip"
"github.com/urfave/cli"
"github.com/urfave/cli/v3"
"gopkg.in/ini.v1"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/database"
"gogs.io/gogs/internal/osutil"
"gogs.io/gogs/internal/osx"
)
var backupCommand = cli.Command{
@@ -44,10 +44,10 @@ const (
archiveRootDir = "gogs-backup"
)
func runBackup(c *cli.Context) error {
zip.Verbose = c.Bool("verbose")
func runBackup(ctx context.Context, cmd *cli.Command) error {
zip.Verbose = cmd.Bool("verbose")
err := conf.Init(c.String("config"))
err := conf.Init(configFromLineage(cmd))
if err != nil {
return errors.Wrap(err, "init configuration")
}
@@ -58,8 +58,8 @@ func runBackup(c *cli.Context) error {
return errors.Wrap(err, "set engine")
}
tmpDir := c.String("tempdir")
if !osutil.Exist(tmpDir) {
tmpDir := cmd.String("tempdir")
if !osx.Exist(tmpDir) {
log.Fatal("'--tempdir' does not exist: %s", tmpDir)
}
rootDir, err := os.MkdirTemp(tmpDir, "gogs-backup-")
@@ -78,7 +78,7 @@ func runBackup(c *cli.Context) error {
log.Fatal("Failed to save metadata '%s': %v", metaFile, err)
}
archiveName := filepath.Join(c.String("target"), c.String("archive-name"))
archiveName := filepath.Join(cmd.String("target"), cmd.String("archive-name"))
log.Info("Packing backup files to: %s", archiveName)
z, err := zip.Create(archiveName)
@@ -91,14 +91,14 @@ func runBackup(c *cli.Context) error {
// Database
dbDir := filepath.Join(rootDir, "db")
if err = database.DumpDatabase(context.Background(), conn, dbDir, c.Bool("verbose")); err != nil {
if err = database.DumpDatabase(ctx, conn, dbDir, cmd.Bool("verbose")); err != nil {
log.Fatal("Failed to dump database: %v", err)
}
if err = z.AddDir(archiveRootDir+"/db", dbDir); err != nil {
log.Fatal("Failed to include 'db': %v", err)
}
if !c.Bool("database-only") {
if !cmd.Bool("database-only") {
// Custom files
err = addCustomDirToBackup(z)
if err != nil {
@@ -108,7 +108,7 @@ func runBackup(c *cli.Context) error {
// Data files
for _, dir := range []string{"ssh", "attachments", "avatars", "repo-avatars"} {
dirPath := filepath.Join(conf.Server.AppDataPath, dir)
if !osutil.IsDir(dirPath) {
if !osx.IsDir(dirPath) {
continue
}
@@ -119,10 +119,10 @@ func runBackup(c *cli.Context) error {
}
// Repositories
if !c.Bool("exclude-repos") && !c.Bool("database-only") {
if !cmd.Bool("exclude-repos") && !cmd.Bool("database-only") {
reposDump := filepath.Join(rootDir, "repositories.zip")
log.Info("Dumping repositories in %q", conf.Repository.Root)
if c.Bool("exclude-mirror-repos") {
if cmd.Bool("exclude-mirror-repos") {
repos, err := database.GetNonMirrorRepositories()
if err != nil {
log.Fatal("Failed to get non-mirror repositories: %v", err)
+43 -7
View File
@@ -1,20 +1,56 @@
package main
import (
"github.com/urfave/cli"
"strings"
"github.com/urfave/cli/v3"
)
func stringFlag(name, value, usage string) cli.StringFlag {
return cli.StringFlag{
Name: name,
func stringFlag(name, value, usage string) *cli.StringFlag {
parts := strings.SplitN(name, ", ", 2)
f := &cli.StringFlag{
Name: parts[0],
Value: value,
Usage: usage,
}
if len(parts) > 1 {
f.Aliases = []string{parts[1]}
}
return f
}
func boolFlag(name, usage string) cli.BoolFlag {
return cli.BoolFlag{
Name: name,
// configFromLineage walks the command lineage to find the --config flag value.
// This is needed because subcommands may not directly see flags set on parent commands.
func configFromLineage(cmd *cli.Command) string {
for _, c := range cmd.Lineage() {
if c.IsSet("config") {
return c.String("config")
}
}
return ""
}
func intFlag(name string, value int, usage string) *cli.IntFlag {
parts := strings.SplitN(name, ", ", 2)
f := &cli.IntFlag{
Name: parts[0],
Value: value,
Usage: usage,
}
if len(parts) > 1 {
f.Aliases = []string{parts[1]}
}
return f
}
func boolFlag(name, usage string) *cli.BoolFlag {
parts := strings.SplitN(name, ", ", 2)
f := &cli.BoolFlag{
Name: parts[0],
Usage: usage,
}
if len(parts) > 1 {
f.Aliases = []string{parts[1]}
}
return f
}
+17 -16
View File
@@ -3,6 +3,7 @@ package main
import (
"bufio"
"bytes"
"context"
"crypto/tls"
"fmt"
"net/url"
@@ -12,7 +13,7 @@ import (
"strconv"
"strings"
"github.com/urfave/cli"
"github.com/urfave/cli/v3"
log "unknwon.dev/clog/v2"
"github.com/gogs/git-module"
@@ -21,7 +22,7 @@ import (
"gogs.io/gogs/internal/database"
"gogs.io/gogs/internal/email"
"gogs.io/gogs/internal/httplib"
"gogs.io/gogs/internal/osutil"
"gogs.io/gogs/internal/osx"
)
var (
@@ -32,10 +33,10 @@ var (
Flags: []cli.Flag{
stringFlag("config, c", "", "Custom configuration file path"),
},
Subcommands: []cli.Command{
subcmdHookPreReceive,
subcmdHookUpadte,
subcmdHookPostReceive,
Commands: []*cli.Command{
&subcmdHookPreReceive,
&subcmdHookUpadte,
&subcmdHookPostReceive,
},
}
@@ -59,11 +60,11 @@ var (
}
)
func runHookPreReceive(c *cli.Context) error {
func runHookPreReceive(_ context.Context, cmd *cli.Command) error {
if os.Getenv("SSH_ORIGINAL_COMMAND") == "" {
return nil
}
setup(c, "pre-receive.log", true)
setup(cmd, "pre-receive.log", true)
isWiki := strings.Contains(os.Getenv(database.EnvRepoCustomHooksPath), ".wiki.git/")
@@ -132,7 +133,7 @@ func runHookPreReceive(c *cli.Context) error {
}
customHooksPath := filepath.Join(os.Getenv(database.EnvRepoCustomHooksPath), "pre-receive")
if !osutil.IsFile(customHooksPath) {
if !osx.IsFile(customHooksPath) {
return nil
}
@@ -152,13 +153,13 @@ func runHookPreReceive(c *cli.Context) error {
return nil
}
func runHookUpdate(c *cli.Context) error {
func runHookUpdate(_ context.Context, cmd *cli.Command) error {
if os.Getenv("SSH_ORIGINAL_COMMAND") == "" {
return nil
}
setup(c, "update.log", false)
setup(cmd, "update.log", false)
args := c.Args()
args := cmd.Args().Slice()
if len(args) != 3 {
fail("Arguments received are not equal to three", "Arguments received are not equal to three")
} else if args[0] == "" {
@@ -166,7 +167,7 @@ func runHookUpdate(c *cli.Context) error {
}
customHooksPath := filepath.Join(os.Getenv(database.EnvRepoCustomHooksPath), "update")
if !osutil.IsFile(customHooksPath) {
if !osx.IsFile(customHooksPath) {
return nil
}
@@ -186,11 +187,11 @@ func runHookUpdate(c *cli.Context) error {
return nil
}
func runHookPostReceive(c *cli.Context) error {
func runHookPostReceive(_ context.Context, cmd *cli.Command) error {
if os.Getenv("SSH_ORIGINAL_COMMAND") == "" {
return nil
}
setup(c, "post-receive.log", true)
setup(cmd, "post-receive.log", true)
// Post-receive hook does more than just gather Git information,
// so we need to setup additional services for email notifications.
@@ -251,7 +252,7 @@ func runHookPostReceive(c *cli.Context) error {
}
customHooksPath := filepath.Join(os.Getenv(database.EnvRepoCustomHooksPath), "post-receive")
if !osutil.IsFile(customHooksPath) {
if !osx.IsFile(customHooksPath) {
return nil
}
+16 -15
View File
@@ -3,16 +3,17 @@ package main
import (
"bufio"
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"time"
"github.com/cockroachdb/errors"
"github.com/urfave/cli"
"github.com/urfave/cli/v3"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/osutil"
"gogs.io/gogs/internal/osx"
)
var (
@@ -21,8 +22,8 @@ var (
Usage: "Import portable data as local Gogs data",
Description: `Allow user import data from other Gogs installations to local instance
without manually hacking the data files`,
Subcommands: []cli.Command{
subcmdImportLocale,
Commands: []*cli.Command{
&subcmdImportLocale,
},
}
@@ -38,19 +39,19 @@ without manually hacking the data files`,
}
)
func runImportLocale(c *cli.Context) error {
if !c.IsSet("source") {
func runImportLocale(_ context.Context, cmd *cli.Command) error {
if !cmd.IsSet("source") {
return errors.New("source directory is not specified")
} else if !c.IsSet("target") {
} else if !cmd.IsSet("target") {
return errors.New("target directory is not specified")
}
if !osutil.IsDir(c.String("source")) {
return errors.Newf("source directory %q does not exist or is not a directory", c.String("source"))
} else if !osutil.IsDir(c.String("target")) {
return errors.Newf("target directory %q does not exist or is not a directory", c.String("target"))
if !osx.IsDir(cmd.String("source")) {
return errors.Newf("source directory %q does not exist or is not a directory", cmd.String("source"))
} else if !osx.IsDir(cmd.String("target")) {
return errors.Newf("target directory %q does not exist or is not a directory", cmd.String("target"))
}
err := conf.Init(c.String("config"))
err := conf.Init(configFromLineage(cmd))
if err != nil {
return errors.Wrap(err, "init configuration")
}
@@ -64,9 +65,9 @@ func runImportLocale(c *cli.Context) error {
// Cut out en-US.
for _, lang := range conf.I18n.Langs[1:] {
name := fmt.Sprintf("locale_%s.ini", lang)
source := filepath.Join(c.String("source"), name)
target := filepath.Join(c.String("target"), name)
if !osutil.IsFile(source) {
source := filepath.Join(cmd.String("source"), name)
target := filepath.Join(cmd.String("target"), name)
if !osx.IsFile(source) {
continue
}
+89
View File
@@ -0,0 +1,89 @@
package web
import (
"crypto/tls"
"strconv"
"strings"
"time"
"github.com/cockroachdb/errors"
"github.com/flamego/cache"
"github.com/flamego/cache/redis"
"gopkg.in/ini.v1"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/strx"
)
func parseCacheOptions(confOpts conf.CacheOptions) (cache.Options, error) {
opts := cache.Options{
GCInterval: time.Duration(confOpts.Interval) * time.Second,
}
switch strx.Coalesce(strings.ToLower(confOpts.Adapter), "memory") {
case "memory":
opts.Initer = cache.MemoryIniter()
case "file":
opts.Initer = cache.FileIniter()
opts.Config = cache.FileConfig{RootDir: confOpts.Host}
case "redis":
cfg, err := parseRedisConfig(confOpts.Host)
if err != nil {
return cache.Options{}, errors.Wrap(err, "parse redis config")
}
opts.Initer = redis.Initer()
opts.Config = cfg
default:
return cache.Options{}, errors.Errorf("unsupported adapter %q", confOpts.Adapter)
}
return opts, nil
}
func parseRedisConfig(host string) (redis.Config, error) {
cfg, err := ini.Load([]byte(strings.ReplaceAll(host, ",", "\n")))
if err != nil {
return redis.Config{}, errors.Wrap(err, "load HOST")
}
var config redis.Config
for k, v := range cfg.Section("").KeysHash() {
switch k {
case "network":
config.Options.Network = v
case "addr":
config.Options.Addr = v
case "password":
config.Options.Password = v
case "db":
n, err := strconv.Atoi(v)
if err != nil {
return redis.Config{}, errors.Wrapf(err, "parse db %q", v)
}
config.Options.DB = n
case "pool_size":
n, err := strconv.Atoi(v)
if err != nil {
return redis.Config{}, errors.Wrapf(err, "parse pool_size %q", v)
}
config.Options.PoolSize = n
case "idle_timeout":
d, err := time.ParseDuration(v + "s")
if err != nil {
return redis.Config{}, errors.Wrapf(err, "parse idle_timeout %q", v)
}
config.Options.ConnMaxIdleTime = d
case "prefix":
config.KeyPrefix = v
case "tls":
// Matches go-macaron/session/redis: any non-empty `tls=` value enables
// TLS with InsecureSkipVerify.
config.Options.TLSConfig = &tls.Config{InsecureSkipVerify: true}
case "hset_name":
// Macaron stored values in a single Redis hash named by this key,
// whereas Flamego stores per-key with KeyPrefix, so this knob has no equivalent.
default:
return redis.Config{}, errors.Errorf("unsupported redis HOST key %q", k)
}
}
return config, nil
}
+228 -160
View File
@@ -1,7 +1,8 @@
package main
package web
import (
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net"
@@ -9,18 +10,20 @@ import (
"net/http/fcgi"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/cockroachdb/errors"
"github.com/flamego/cache"
"github.com/flamego/captcha"
"github.com/flamego/flamego"
"github.com/go-macaron/binding"
"github.com/go-macaron/cache"
"github.com/go-macaron/captcha"
macaroncache "github.com/go-macaron/cache"
"github.com/go-macaron/csrf"
"github.com/go-macaron/gzip"
"github.com/go-macaron/i18n"
"github.com/go-macaron/session"
"github.com/go-macaron/toolbox"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/urfave/cli"
"gopkg.in/macaron.v1"
log "unknwon.dev/clog/v2"
@@ -30,145 +33,39 @@ import (
"gogs.io/gogs/internal/context"
"gogs.io/gogs/internal/database"
"gogs.io/gogs/internal/form"
"gogs.io/gogs/internal/osutil"
"gogs.io/gogs/internal/osx"
"gogs.io/gogs/internal/route"
"gogs.io/gogs/internal/route/admin"
apiv1 "gogs.io/gogs/internal/route/api/v1"
"gogs.io/gogs/internal/route/dev"
"gogs.io/gogs/internal/route/lfs"
"gogs.io/gogs/internal/route/org"
"gogs.io/gogs/internal/route/repo"
"gogs.io/gogs/internal/route/user"
"gogs.io/gogs/internal/template"
"gogs.io/gogs/internal/urlx"
"gogs.io/gogs/public"
"gogs.io/gogs/templates"
)
var webCommand = cli.Command{
Name: "web",
Usage: "Start web server",
Description: `Gogs web server is the only thing you need to run,
and it takes care of all the other things for you`,
Action: runWeb,
Flags: []cli.Flag{
stringFlag("port, p", "3000", "Temporary port number to prevent conflict"),
stringFlag("config, c", filepath.Join(conf.CustomDir(), "conf", "app.ini"), "Custom configuration file path"),
},
}
// newMacaron initializes Macaron instance.
func newMacaron() *macaron.Macaron {
m := macaron.New()
if !conf.Server.DisableRouterLog {
m.Use(macaron.Logger())
}
m.Use(macaron.Recovery())
if conf.Server.EnableGzip {
m.Use(gzip.Gziper())
}
if conf.Server.Protocol == "fcgi" {
m.SetURLPrefix(conf.Server.Subpath)
}
// Register custom middleware first to make it possible to override files under "public".
m.Use(macaron.Static(
filepath.Join(conf.CustomDir(), "public"),
macaron.StaticOptions{
SkipLogging: conf.Server.DisableRouterLog,
},
))
var publicFs http.FileSystem
if !conf.Server.LoadAssetsFromDisk {
publicFs = http.FS(public.Files)
}
m.Use(macaron.Static(
filepath.Join(conf.WorkDir(), "public"),
macaron.StaticOptions{
ETag: true,
SkipLogging: conf.Server.DisableRouterLog,
FileSystem: publicFs,
},
))
m.Use(macaron.Static(
conf.Picture.AvatarUploadPath,
macaron.StaticOptions{
ETag: true,
Prefix: conf.UsersAvatarPathPrefix,
SkipLogging: conf.Server.DisableRouterLog,
},
))
m.Use(macaron.Static(
conf.Picture.RepositoryAvatarUploadPath,
macaron.StaticOptions{
ETag: true,
Prefix: database.RepoAvatarURLPrefix,
SkipLogging: conf.Server.DisableRouterLog,
},
))
customDir := filepath.Join(conf.CustomDir(), "templates")
renderOpt := macaron.RenderOptions{
Directory: filepath.Join(conf.WorkDir(), "templates"),
AppendDirectories: []string{customDir},
Funcs: template.FuncMap(),
IndentJSON: macaron.Env != macaron.PROD,
}
if !conf.Server.LoadAssetsFromDisk {
renderOpt.TemplateFileSystem = templates.NewTemplateFileSystem("", customDir)
}
m.Use(macaron.Renderer(renderOpt))
localeNames, err := embedConf.FileNames("locale")
// Run starts the web server with the given configuration path and port override.
func Run(configPath string, portOverride int) error {
err := route.GlobalInit(configPath)
if err != nil {
log.Fatal("Failed to list locale files: %v", err)
return errors.Wrap(err, "initialize application")
}
localeFiles := make(map[string][]byte)
for _, name := range localeNames {
localeFiles[name], err = embedConf.Files.ReadFile("locale/" + name)
if err != nil {
log.Fatal("Failed to read locale file %q: %v", name, err)
}
}
m.Use(i18n.I18n(i18n.Options{
SubURL: conf.Server.Subpath,
Files: localeFiles,
CustomDirectory: filepath.Join(conf.CustomDir(), "conf", "locale"),
Langs: conf.I18n.Langs,
Names: conf.I18n.Names,
DefaultLang: "en-US",
Redirect: true,
}))
m.Use(cache.Cacher(cache.Options{
Adapter: conf.Cache.Adapter,
AdapterConfig: conf.Cache.Host,
Interval: conf.Cache.Interval,
}))
m.Use(captcha.Captchaer(captcha.Options{
SubURL: conf.Server.Subpath,
}))
m.Use(toolbox.Toolboxer(m, toolbox.Options{
HealthCheckFuncs: []*toolbox.HealthCheckFuncDesc{
{
Desc: "Database connection",
Func: database.Ping,
},
},
}))
return m
}
func runWeb(c *cli.Context) error {
err := route.GlobalInit(c.String("config"))
m, err := newMacaron()
if err != nil {
log.Fatal("Failed to initialize application: %v", err)
return errors.Wrap(err, "initialize macaron")
}
m := newMacaron()
webHandler, err := newRoutingHandler()
if err != nil {
return errors.Wrap(err, "initialize web handler")
}
reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true})
ignSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: conf.Auth.RequireSigninView})
reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true})
bindIgnErr := binding.BindIgnErr
@@ -189,20 +86,6 @@ func runWeb(c *cli.Context) error {
m.Get("/^:type(issues|pulls)$", reqSignIn, user.Issues)
// ***** START: User *****
m.Group("/user", func() {
m.Group("/login", func() {
m.Combo("").Get(user.Login).
Post(bindIgnErr(form.SignIn{}), user.LoginPost)
m.Combo("/two_factor").Get(user.LoginTwoFactor).Post(user.LoginTwoFactorPost)
m.Combo("/two_factor_recovery_code").Get(user.LoginTwoFactorRecoveryCode).Post(user.LoginTwoFactorRecoveryCodePost)
})
m.Get("/sign_up", user.SignUp)
m.Post("/sign_up", bindIgnErr(form.Register{}), user.SignUpPost)
m.Get("/reset_password", user.ResetPasswd)
m.Post("/reset_password", user.ResetPasswdPost)
}, reqSignOut)
m.Group("/user/settings", func() {
m.Get("", user.Settings)
m.Post("", bindIgnErr(form.UpdateProfile{}), user.SettingsPost)
@@ -244,12 +127,8 @@ func runWeb(c *cli.Context) error {
})
m.Group("/user", func() {
m.Any("/activate", user.Activate)
m.Any("/activate_email", user.ActivateEmail)
m.Get("/email2user", user.Email2User)
m.Get("/forget_password", user.ForgotPasswd)
m.Post("/forget_password", user.ForgotPasswdPost)
m.Post("/logout", user.SignOut)
})
// ***** END: User *****
@@ -307,7 +186,7 @@ func runWeb(c *cli.Context) error {
if err != nil {
c.NotFoundOrError(err, "get attachment by UUID")
return
} else if !osutil.IsFile(attach.LocalPath()) {
} else if !osx.IsFile(attach.LocalPath()) {
c.NotFound()
return
}
@@ -339,10 +218,6 @@ func runWeb(c *cli.Context) error {
m.Post("/action/:action", user.Action)
}, reqSignIn, context.InjectParamsUser())
if macaron.Env == macaron.DEV {
m.Get("/template/*", dev.TemplatePreview)
}
reqRepoAdmin := context.RequireRepoAdmin()
reqRepoWriter := context.RequireRepoWriter()
@@ -633,6 +508,11 @@ func runWeb(c *cli.Context) error {
m.Group("/api", func() {
apiv1.RegisterRoutes(m)
}, ignSignIn)
m.Any("/api/web/*", flamegoBridger(webHandler))
m.Get("/redirect", flamegoBridger(webHandler))
m.Get("/captcha/*", flamegoBridger(webHandler))
m.Any("/*", func(c *context.Context) { c.ServeWeb() })
},
session.Sessioner(session.Options{
Provider: conf.Session.Provider,
@@ -642,6 +522,7 @@ func runWeb(c *cli.Context) error {
Gclifetime: conf.Session.GCInterval,
Maxlifetime: conf.Session.MaxLifeTime,
Secure: conf.Session.CookieSecure,
CookieLifeTime: 86400 * conf.Security.LoginRememberDays,
}),
csrf.Csrfer(csrf.Options{
Secret: conf.Security.SecretKey,
@@ -653,7 +534,7 @@ func runWeb(c *cli.Context) error {
SetCookie: true,
Secure: conf.Server.URL.Scheme == "https",
}),
context.Contexter(context.NewStore()),
context.Contexter(context.NewStore(), webHandler),
)
// ***************************
@@ -667,7 +548,18 @@ func runWeb(c *cli.Context) error {
lfs.RegisterRoutes(m.Router)
})
m.Route("/*", "GET,POST,OPTIONS", context.ServeGoGet(), repo.HTTPContexter(repo.NewStore()), repo.HTTP)
gitHTTP := []macaron.Handler{context.ServeGoGet(), repo.HTTPContexter(repo.NewStore()), repo.HTTP}
m.Route("/info/refs", "GET,OPTIONS", gitHTTP...)
m.Route("/HEAD", "GET,OPTIONS", gitHTTP...)
m.Route("/git-upload-pack", "POST,OPTIONS", gitHTTP...)
m.Route("/git-receive-pack", "POST,OPTIONS", gitHTTP...)
m.Route("/objects/info/alternates", "GET,OPTIONS", gitHTTP...)
m.Route("/objects/info/http-alternates", "GET,OPTIONS", gitHTTP...)
m.Route("/objects/info/packs", "GET,OPTIONS", gitHTTP...)
m.Route("/objects/info/*", "GET,OPTIONS", gitHTTP...)
m.Route("/objects/:prefix([0-9a-f]{2})/:suffix([0-9a-f]{38})", "GET,OPTIONS", gitHTTP...)
m.Route("/objects/pack/pack-:sha([0-9a-f]{40}).pack", "GET,OPTIONS", gitHTTP...)
m.Route("/objects/pack/pack-:sha([0-9a-f]{40}).idx", "GET,OPTIONS", gitHTTP...)
})
// ***************************
@@ -694,20 +586,19 @@ func runWeb(c *cli.Context) error {
}
})
m.NotFound(route.NotFound)
// Flag for port number in case first time run conflict.
if c.IsSet("port") {
conf.Server.URL.Host = strings.Replace(conf.Server.URL.Host, ":"+conf.Server.URL.Port(), ":"+c.String("port"), 1)
if portOverride > 0 {
port := strconv.Itoa(portOverride)
conf.Server.URL.Host = strings.Replace(conf.Server.URL.Host, ":"+conf.Server.URL.Port(), ":"+port, 1)
conf.Server.ExternalURL = conf.Server.URL.String()
conf.Server.HTTPPort = c.String("port")
conf.Server.HTTPPort = portOverride
}
var listenAddr string
if conf.Server.Protocol == "unix" {
listenAddr = conf.Server.HTTPAddr
} else {
listenAddr = fmt.Sprintf("%s:%s", conf.Server.HTTPAddr, conf.Server.HTTPPort)
listenAddr = fmt.Sprintf("%s:%d", conf.Server.HTTPAddr, conf.Server.HTTPPort)
}
log.Info("Available on %s", conf.Server.ExternalURL)
@@ -749,33 +640,210 @@ func runWeb(c *cli.Context) error {
err = fcgi.Serve(nil, m)
case "unix":
if osutil.Exist(listenAddr) {
if osx.Exist(listenAddr) {
err = os.Remove(listenAddr)
if err != nil {
log.Fatal("Failed to remove existing Unix domain socket: %v", err)
return errors.Wrap(err, "remove existing Unix domain socket")
}
}
var listener *net.UnixListener
listener, err = net.ListenUnix("unix", &net.UnixAddr{Name: listenAddr, Net: "unix"})
if err != nil {
log.Fatal("Failed to listen on Unix networks: %v", err)
return errors.Wrap(err, "listen on Unix network")
}
// FIXME: add proper implementation of signal capture on all protocols
// execute this on SIGTERM or SIGINT: listener.Close()
if err = os.Chmod(listenAddr, conf.Server.UnixSocketMode); err != nil {
log.Fatal("Failed to change permission of Unix domain socket: %v", err)
return errors.Wrap(err, "change permission of Unix domain socket")
}
err = http.Serve(listener, m)
default:
log.Fatal("Unexpected server protocol: %s", conf.Server.Protocol)
return errors.Newf("unexpected server protocol: %s", conf.Server.Protocol)
}
if err != nil {
log.Fatal("Failed to start server: %v", err)
return errors.Wrap(err, "start server")
}
return nil
}
func newRoutingHandler() (http.Handler, error) {
f := flamego.New()
f.Use(flamego.Recovery())
f.Use(flamegoInjector)
f.Use(captcha.Captchaer(captcha.Options{URLPrefix: "/captcha/"}))
cacherOpts, err := parseCacheOptions(conf.Cache)
if err != nil {
return nil, errors.Wrap(err, "parse cache options")
}
f.Use(cache.Cacher(cacherOpts))
f.Get("/redirect", getRedirect)
// The captcha middleware writes the image response itself when the request path
// matches its URLPrefix. This route just needs to exist so the request reaches
// the middleware chain.
f.Get("/captcha/image.jpeg", func() {})
mountWebAPIRoutes(f)
err = mountWebAppRoutes(f)
if err != nil {
return nil, errors.Wrap(err, "mount web app routes")
}
return f, nil
}
func getRedirect(c flamego.Context) {
to := c.Request().URL.Query().Get("to")
if !urlx.IsSameSite(to) {
to = conf.Server.Subpath + "/"
}
c.Redirect(to, http.StatusSeeOther)
}
// newMacaron initializes Macaron instance.
func newMacaron() (*macaron.Macaron, error) {
m := macaron.New()
if !conf.Server.DisableRouterLog {
m.Use(macaron.Logger())
}
m.Use(macaron.Recovery())
if conf.Server.EnableGzip {
m.Use(gzip.Gziper())
}
if conf.Server.Protocol == "fcgi" {
m.SetURLPrefix(conf.Server.Subpath)
}
// Register custom middleware first to make it possible to override files under "public".
m.Use(macaron.Static(
filepath.Join(conf.CustomDir(), "public"),
macaron.StaticOptions{
SkipLogging: conf.Server.DisableRouterLog,
},
))
var publicFs http.FileSystem
if !conf.Server.LoadAssetsFromDisk {
publicFs = http.FS(public.Files)
}
m.Use(macaron.Static(
filepath.Join(conf.WorkDir(), "public"),
macaron.StaticOptions{
ETag: true,
SkipLogging: conf.Server.DisableRouterLog,
FileSystem: publicFs,
},
))
m.Use(macaron.Static(
conf.Picture.AvatarUploadPath,
macaron.StaticOptions{
ETag: true,
Prefix: conf.UsersAvatarPathPrefix,
SkipLogging: conf.Server.DisableRouterLog,
},
))
m.Use(macaron.Static(
conf.Picture.RepositoryAvatarUploadPath,
macaron.StaticOptions{
ETag: true,
Prefix: database.RepoAvatarURLPrefix,
SkipLogging: conf.Server.DisableRouterLog,
},
))
customDir := filepath.Join(conf.CustomDir(), "templates")
renderOpt := macaron.RenderOptions{
Directory: filepath.Join(conf.WorkDir(), "templates"),
AppendDirectories: []string{customDir},
Funcs: template.FuncMap(),
IndentJSON: macaron.Env != macaron.PROD,
}
if !conf.Server.LoadAssetsFromDisk {
renderOpt.TemplateFileSystem = templates.NewTemplateFileSystem("", customDir)
}
m.Use(macaron.Renderer(renderOpt))
localeNames, err := embedConf.FileNames("locale")
if err != nil {
return nil, errors.Wrap(err, "list locale files")
}
localeFiles := make(map[string][]byte)
for _, name := range localeNames {
localeFiles[name], err = embedConf.Files.ReadFile("locale/" + name)
if err != nil {
return nil, errors.Wrapf(err, "read locale file %q", name)
}
}
m.Use(i18n.I18n(i18n.Options{
SubURL: conf.Server.Subpath,
Files: localeFiles,
CustomDirectory: filepath.Join(conf.CustomDir(), "conf", "locale"),
Langs: conf.I18n.Langs,
Names: conf.I18n.Names,
DefaultLang: "en-US",
Redirect: true,
}))
m.Use(macaroncache.Cacher(macaroncache.Options{
Adapter: conf.Cache.Adapter,
AdapterConfig: conf.Cache.Host,
Interval: conf.Cache.Interval,
}))
m.Route("/healthcheck", http.MethodHead+","+http.MethodGet, healthCheck)
return m, nil
}
// renderIndex returns the index.html shell with per-request substitutions
// applied for the given WebContext.
func renderIndex(index []byte, wc context.WebContext) ([]byte, error) {
// json.Marshal escapes <, >, and & to their \uXXXX forms by default, so
// the payload cannot break out of the surrounding <script> with "</script>"
// even if a field carries attacker-influenced text.
payload, err := json.Marshal(struct {
Lang string `json:"lang"`
SubURL string `json:"subURL"`
}{
Lang: wc.Lang,
SubURL: wc.SubURL,
})
if err != nil {
return nil, errors.Wrap(err, "marshal web context")
}
script := `<script>window.__webContext=` + string(payload) +
`;document.documentElement.lang=window.__webContext.lang;</script>`
pairs := []string{
"{{.WebContext}}", script,
}
if wc.SubURL != "" {
// Vite bakes absolute root paths into the bundle output. Prefix them
// with the subpath so they resolve correctly under non-root mounts.
pairs = append(pairs,
`src="/assets/`, `src="`+wc.SubURL+`/assets/`,
`href="/assets/`, `href="`+wc.SubURL+`/assets/`,
`src="/src/`, `src="`+wc.SubURL+`/src/`,
`href="/img/`, `href="`+wc.SubURL+`/img/`,
)
}
return []byte(strings.NewReplacer(pairs...).Replace(string(index))), nil
}
func healthCheck(w http.ResponseWriter, r *http.Request) {
if err := database.Ping(); err != nil {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusServiceUnavailable)
_, _ = fmt.Fprintf(w, "* Database connection: %s\n", err)
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusOK)
if r.Method == http.MethodHead {
return
}
_, _ = w.Write([]byte("* Database connection: OK\n"))
}
+732
View File
@@ -0,0 +1,732 @@
package web
import (
stdctx "context"
"encoding/hex"
"encoding/json"
"net/http"
"os"
"reflect"
"regexp"
"strconv"
"strings"
"time"
"github.com/cockroachdb/errors"
"github.com/flamego/binding"
"github.com/flamego/cache"
"github.com/flamego/captcha"
"github.com/flamego/flamego"
"github.com/flamego/session"
"github.com/flamego/validator"
"github.com/go-macaron/i18n"
macaronsession "github.com/go-macaron/session"
"gopkg.in/macaron.v1"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/auth"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/context"
"gogs.io/gogs/internal/database"
"gogs.io/gogs/internal/email"
"gogs.io/gogs/internal/tool"
"gogs.io/gogs/internal/userx"
)
type (
webAPIUserKey struct{}
webAPISessionKey struct{}
webAPIMacaronKey struct{}
webAPILocaleKey struct{}
)
func flamegoBridger(webHandler http.Handler) func(c *context.Context, l i18n.Locale) {
return func(c *context.Context, l i18n.Locale) {
ctx := c.Req.Context()
ctx = stdctx.WithValue(ctx, webAPIUserKey{}, c.User)
ctx = stdctx.WithValue(ctx, webAPISessionKey{}, c.Session)
ctx = stdctx.WithValue(ctx, webAPIMacaronKey{}, c.Context)
ctx = stdctx.WithValue(ctx, webAPILocaleKey{}, l)
webHandler.ServeHTTP(c.Resp, c.Req.WithContext(ctx))
}
}
func flamegoInjector(c flamego.Context) {
ctx := c.Request().Context()
user, _ := ctx.Value(webAPIUserKey{}).(*database.User)
sess, _ := ctx.Value(webAPISessionKey{}).(macaronsession.Store)
mc, _ := ctx.Value(webAPIMacaronKey{}).(*macaron.Context)
l, _ := ctx.Value(webAPILocaleKey{}).(i18n.Locale)
c.Map(user, sess, mc, l)
c.MapTo(flamegoSessionAdapter{sess: sess}, (*session.Session)(nil))
}
// flamegoSessionAdapter exposes the underlying Macaron session via the Flamego
// session interface so the captcha middleware (and any future Flamego-native
// session consumer) can read/write the same session store the rest of the app
// uses.
type flamegoSessionAdapter struct {
sess macaronsession.Store
}
func (s flamegoSessionAdapter) ID() string { return s.sess.ID() }
func (s flamegoSessionAdapter) Get(key interface{}) interface{} { return s.sess.Get(key) }
func (s flamegoSessionAdapter) Set(key, val interface{}) { _ = s.sess.Set(key, val) }
func (s flamegoSessionAdapter) SetFlash(val interface{}) { _ = s.sess.Set("_flash", val) }
func (s flamegoSessionAdapter) Delete(key interface{}) { _ = s.sess.Delete(key) }
func (s flamegoSessionAdapter) Flush() { _ = s.sess.Flush() }
func (s flamegoSessionAdapter) Encode() ([]byte, error) { return nil, nil }
func webAPIBodyLimiter(c flamego.Context) {
r := c.Request().Request
r.Body = http.MaxBytesReader(c.ResponseWriter(), r.Body, 4*1024) // 4 KiB
}
func parseUserFromCode(ctx stdctx.Context, code string) (user *database.User) {
if len(code) <= tool.TimeLimitCodeLength {
return nil
}
hexStr := code[tool.TimeLimitCodeLength:]
if b, err := hex.DecodeString(hexStr); err == nil {
if user, err = database.Handle.Users().GetByUsername(ctx, string(b)); user != nil {
return user
} else if !database.IsErrUserNotExist(err) {
log.Error("parseUserFromCode: get user by name %q: %v", string(b), err)
}
}
return nil
}
func verifyUserActiveCode(ctx stdctx.Context, code string) (user *database.User) {
if user = parseUserFromCode(ctx, code); user != nil {
prefix := code[:tool.TimeLimitCodeLength]
data := strconv.FormatInt(user.ID, 10) + user.Email + user.LowerName + user.Password + user.Rands
if tool.VerifyTimeLimitCode(data, conf.Auth.ActivateCodeLives, prefix) {
return user
}
}
return nil
}
// webAPIValidator is the shared validator instance used by every webapi
// binding. Registering the json-tag name function makes validation errors
// carry the wire field name (e.g. "recoveryCode") via ve.Field(), so the
// 400 payload keys match what the React client sends and reads.
var webAPIValidator = func() *validator.Validate {
v := validator.New()
v.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})
_ = v.RegisterValidation("alphadashdot", func(fl validator.FieldLevel) bool {
return !alphaDashDotInvalid.MatchString(fl.Field().String())
})
return v
}()
var alphaDashDotInvalid = regexp.MustCompile(`[^\d\w\-_\.]`)
// bindJSON binds the request body to T. On binding or validation failure it
// short-circuits with a 400 carrying the standard renderBindingErrors payload,
// so downstream handlers can drop the `if len(bindErrs) > 0` boilerplate and
// the binding.Errors parameter entirely.
func bindJSON(model any) flamego.Handler {
return binding.JSON(model, binding.Options{
Validator: webAPIValidator,
ErrorHandler: func(c flamego.Context, l i18n.Locale, errs binding.Errors) {
w := c.ResponseWriter()
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
_ = json.NewEncoder(w).Encode(renderBindingErrors(l, errs))
},
})
}
func mountWebAPIRoutes(f *flamego.Flame) {
f.ReturnHandler(func(c flamego.Context, statusCode int, resp any, err error) {
w := c.ResponseWriter()
w.Header().Set("Cache-Control", "no-store")
if err != nil {
msg := err.Error()
if statusCode >= http.StatusInternalServerError && conf.IsProdMode() {
msg = "Internal server error"
}
resp = map[string]any{"error": msg}
}
if resp == nil {
w.WriteHeader(statusCode)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(statusCode)
_ = json.NewEncoder(w).Encode(resp)
})
f.Group("/api/web", func() {
f.Group("/user", func() {
f.Get("/info", getUserInfo)
f.Combo("/sign-up").
Get(getUserSignUp).
Post(bindJSON(userSignUpRequest{}), postUserSignUp)
f.Group("/reset-password", func() {
f.Combo("").
Get(getUserResetPassword).
Post(bindJSON(userResetPasswordEmailRequest{}), postUserResetPassword)
f.Post("/complete", bindJSON(userResetPasswordCompleteRequest{}), postUserResetPasswordComplete)
})
f.Combo("/sign-in").
Get(getUserSignIn).
Post(bindJSON(userSignInRequest{}), postUserSignIn)
f.Group("/mfa", func() {
f.Combo("").
Get(getUserMFA).
Post(bindJSON(userMFARequest{}), postUserMFA)
f.Post("/recovery", bindJSON(userMFARecoveryRequest{}), postUserMFARecovery)
})
f.Group("/activate", func() {
f.Combo("").
Get(getUserActivate).
Post(postUserActivate)
f.Post("/complete", bindJSON(userActivateCompleteRequest{}), postUserActivateComplete)
})
f.Post("/sign-out", postUserSignOut)
})
}, webAPIBodyLimiter)
}
// fieldErrors maps JSON field names to per-field localized messages. A non-nil
// value renders inline under the input. A nil value marks the input as
// invalid (highlight + focus eligibility) without duplicating text. Used in
// concert with bindingErrorResponse.Error to surface one banner message while
// highlighting multiple inputs.
type fieldErrors map[string]*string
// bindingErrorResponse carries form-validation failures. Error is the top-level
// message shown as a banner above the form (used when the failure is not tied
// to a specific input, e.g. malformed body, bad credentials).
type bindingErrorResponse struct {
Error string `json:"error,omitempty"`
Fields fieldErrors `json:"fields,omitempty"`
}
// ruleSuffixKeys maps a validator tag to the shared "form.*_error" suffix key
// (e.g. "max" -> "form.max_size_error"). Messages are composed as
// <field label> + <suffix>, mirroring the legacy Macaron binding behavior.
var ruleSuffixKeys = map[string]string{
"required": "form.require_error",
"max": "form.max_size_error",
"min": "form.min_size_error",
"len": "form.size_error",
"email": "form.email_error",
"url": "form.url_error",
"alphadashdot": "form.alpha_dash_dot_error",
}
// renderBindingErrors maps binding.Errors to the response shape, looking up
// localized messages via the request's locale. The per-field label comes from
// "form.<StructField>" (e.g. "form.UserName"); the rule suffix comes from
// ruleSuffixKeys. Rule parameters (e.g. "254" for `max=254`) are passed
// through to the suffix translation for %s expansion. Always HTTP 400.
func renderBindingErrors(l i18n.Locale, errs binding.Errors) *bindingErrorResponse {
for _, e := range errs {
if e.Category == binding.ErrorCategoryDeserialization {
return &bindingErrorResponse{Error: l.Tr("form.invalid_request") + ": " + e.Err.Error()}
}
}
out := make(fieldErrors)
for _, e := range errs {
var ves validator.ValidationErrors
ok := errors.As(e.Err, &ves)
if !ok {
continue
}
for _, ve := range ves {
field := ve.Field()
if _, exists := out[field]; exists {
// Keep the first rule that failed for a given field so the client renders one
// message per input. Subsequent rules surface only after the first is fixed.
continue
}
label := l.Tr("form." + ve.StructField())
suffixKey, known := ruleSuffixKeys[ve.Tag()]
var msg string
switch {
case !known:
msg = l.Tr("form.unknown_error") + " " + ve.Tag()
case ve.Param() != "":
msg = label + l.Tr(suffixKey, ve.Param())
default:
msg = label + l.Tr(suffixKey)
}
out[field] = &msg
}
}
return &bindingErrorResponse{Fields: out}
}
type loginSource struct {
ID int64 `json:"id"`
Name string `json:"name"`
IsDefault bool `json:"isDefault"`
}
type getUserSignInResponse struct {
LoginSources []loginSource `json:"loginSources"`
}
type getUserSignUpResponse struct {
RegistrationDisabled bool `json:"registrationDisabled"`
CaptchaEnabled bool `json:"captchaEnabled"`
}
func getUserSignUp() (statusCode int, resp *getUserSignUpResponse, err error) {
return http.StatusOK, &getUserSignUpResponse{
RegistrationDisabled: conf.Auth.DisableRegistration,
CaptchaEnabled: conf.Auth.EnableRegistrationCaptcha,
}, nil
}
type userSignUpRequest struct {
UserName string `json:"userName" validate:"required,alphadashdot,max=35"`
Email string `json:"email" validate:"required,email,max=254"`
Password string `json:"password" validate:"required,max=255"`
Captcha string `json:"captcha"`
}
type userSignUpResponse struct {
EmailConfirmationRequired bool `json:"emailConfirmationRequired,omitempty"`
Email string `json:"email,omitempty"`
Hours int `json:"hours,omitempty"`
}
func postUserSignUp(r *http.Request, mc *macaron.Context, ca cache.Cache, l i18n.Locale, cpt captcha.Captcha, req userSignUpRequest) (statusCode int, resp any, err error) {
if conf.Auth.DisableRegistration {
return http.StatusForbidden, &bindingErrorResponse{Error: l.Tr("auth.disable_register_prompt")}, nil
}
if conf.Auth.EnableRegistrationCaptcha && !cpt.ValidText(req.Captcha) {
msg := l.Tr("form.captcha_incorrect")
return http.StatusUnauthorized, &bindingErrorResponse{
Fields: fieldErrors{"captcha": &msg},
}, nil
}
u, err := database.Handle.Users().Create(
r.Context(),
req.UserName,
req.Email,
database.CreateUserOptions{
Password: req.Password,
Activated: !conf.Auth.RequireEmailConfirmation,
},
)
if err != nil {
switch {
case database.IsErrUserAlreadyExist(err):
msg := l.Tr("form.username_been_taken")
return http.StatusUnprocessableEntity, &bindingErrorResponse{Fields: fieldErrors{"userName": &msg}}, nil
case database.IsErrEmailAlreadyUsed(err):
msg := l.Tr("form.email_been_used")
return http.StatusUnprocessableEntity, &bindingErrorResponse{Fields: fieldErrors{"email": &msg}}, nil
case database.IsErrNameNotAllowed(err):
msg := l.Tr("user.form.name_not_allowed", err.(database.ErrNameNotAllowed).Value())
return http.StatusBadRequest, &bindingErrorResponse{Fields: fieldErrors{"userName": &msg}}, nil
default:
log.Error("postUserSignUp: create user %q: %v", req.UserName, err)
return http.StatusInternalServerError, nil, errors.Wrap(err, "create user")
}
}
log.Trace("Account created: %s", u.Name)
if database.Handle.Users().Count(r.Context()) == 1 {
v := true
err := database.Handle.Users().Update(
r.Context(),
u.ID,
database.UpdateUserOptions{
IsActivated: &v,
IsAdmin: &v,
},
)
if err != nil {
log.Error("postUserSignUp: update first user %q: %v", u.Name, err)
return http.StatusInternalServerError, nil, errors.Wrap(err, "update user")
}
}
if conf.Auth.RequireEmailConfirmation && u.ID > 1 {
if err := email.SendActivateAccountMail(mc, database.NewMailerUser(u)); err != nil {
log.Error("postUserSignUp: send activation mail to user %q: %v", u.Name, err)
}
if err := ca.Set(r.Context(), userx.MailResendCacheKey(u.ID), 1, 180*time.Second); err != nil {
log.Error("postUserSignUp: put mail resend cache for user %q: %v", u.Name, err)
}
return http.StatusOK, &userSignUpResponse{
EmailConfirmationRequired: true,
Email: u.Email,
Hours: conf.Auth.ActivateCodeLives / 60,
}, nil
}
return http.StatusOK, &userSignUpResponse{}, nil
}
func getUserSignIn(r *http.Request) (statusCode int, resp *getUserSignInResponse, err error) {
sources, err := database.Handle.LoginSources().List(r.Context(), database.ListLoginSourceOptions{OnlyActivated: true})
if err != nil {
log.Error("getUserSignIn: list activated login sources: %v", err)
return http.StatusInternalServerError, nil, errors.Wrap(err, "list activated login sources")
}
loginSources := make([]loginSource, 0, len(sources))
for _, s := range sources {
loginSources = append(loginSources, loginSource{ID: s.ID, Name: s.Name, IsDefault: s.IsDefault})
}
return http.StatusOK, &getUserSignInResponse{LoginSources: loginSources}, nil
}
type userSignInRequest struct {
Username string `json:"username" validate:"required,max=254"`
Password string `json:"password" validate:"required,max=255"`
LoginSource int64 `json:"loginSource"`
}
type getUserResetPasswordResponse struct {
EmailEnabled bool `json:"emailEnabled"`
Valid bool `json:"valid"`
}
func getUserResetPassword(r *http.Request) (statusCode int, resp *getUserResetPasswordResponse, err error) {
code := r.URL.Query().Get("code")
return http.StatusOK, &getUserResetPasswordResponse{
EmailEnabled: conf.Email.Enabled,
Valid: code != "" && verifyUserActiveCode(r.Context(), code) != nil,
}, nil
}
type userResetPasswordEmailRequest struct {
Email string `json:"email" validate:"required,email,max=254"`
}
type userResetPasswordCompleteRequest struct {
Code string `json:"code" validate:"required"`
Password string `json:"password" validate:"required,min=6,max=255"`
}
type userResetPasswordResponse struct {
Hours int `json:"hours,omitempty"`
ResendLimited bool `json:"resendLimited,omitempty"`
}
func postUserResetPassword(r *http.Request, ca cache.Cache, l i18n.Locale, req userResetPasswordEmailRequest) (statusCode int, resp any, err error) {
if !conf.Email.Enabled {
return http.StatusForbidden, &bindingErrorResponse{Error: l.Tr("auth.disable_register_mail")}, nil
}
ctx := r.Context()
u, err := database.Handle.Users().GetByEmail(ctx, req.Email)
if err != nil {
if database.IsErrUserNotExist(err) {
return http.StatusOK, &userResetPasswordResponse{Hours: conf.Auth.ActivateCodeLives / 60}, nil
}
log.Error("postUserResetPassword: get user by email %q: %v", req.Email, err)
return http.StatusInternalServerError, nil, errors.Wrap(err, "get user by email")
}
if !u.IsLocal() {
msg := l.Tr("auth.non_local_account")
return http.StatusForbidden, &bindingErrorResponse{Fields: fieldErrors{"email": &msg}}, nil
}
if _, err := ca.Get(ctx, userx.MailResendCacheKey(u.ID)); err == nil {
return http.StatusOK, &userResetPasswordResponse{
Hours: conf.Auth.ActivateCodeLives / 60,
ResendLimited: true,
}, nil
} else if !errors.Is(err, os.ErrNotExist) {
log.Error("postUserResetPassword: get mail resend cache for user %q: %v", u.Name, err)
}
if err = email.SendResetPasswordMail(l, database.NewMailerUser(u)); err != nil {
log.Error("postUserResetPassword: send reset password mail to user %q: %v", u.Name, err)
}
if err = ca.Set(ctx, userx.MailResendCacheKey(u.ID), 1, 180*time.Second); err != nil {
log.Error("postUserResetPassword: put mail resend cache for user %q: %v", u.Name, err)
}
return http.StatusOK, &userResetPasswordResponse{Hours: conf.Auth.ActivateCodeLives / 60}, nil
}
func postUserResetPasswordComplete(r *http.Request, l i18n.Locale, req userResetPasswordCompleteRequest) (statusCode int, resp any, err error) {
u := verifyUserActiveCode(r.Context(), req.Code)
if u == nil {
return http.StatusBadRequest, &bindingErrorResponse{Error: l.Tr("auth.invalid_code")}, nil
}
if err := database.Handle.Users().Update(r.Context(), u.ID, database.UpdateUserOptions{Password: &req.Password}); err != nil {
log.Error("postUserResetPasswordComplete: update password for user %q: %v", u.Name, err)
return http.StatusInternalServerError, nil, errors.Wrap(err, "update user")
}
log.Trace("User password reset: %s", u.Name)
return http.StatusNoContent, nil, nil
}
type userSignInResponse struct {
// MFA is true when the account has MFA enabled and the password step
// succeeded but a second factor is still required. The client should
// navigate to /user/mfa to complete the challenge.
MFA bool `json:"mfa,omitempty"`
}
func postUserSignIn(r *http.Request, sess session.Session, mc *macaron.Context, l i18n.Locale, req userSignInRequest) (statusCode int, resp any, err error) {
u, err := database.Handle.Users().Authenticate(r.Context(), req.Username, req.Password, req.LoginSource)
if err != nil {
switch {
case auth.IsErrBadCredentials(err):
return http.StatusUnauthorized, &bindingErrorResponse{
Error: l.Tr("form.username_password_incorrect"),
Fields: fieldErrors{"username": nil, "password": nil},
}, nil
case database.IsErrLoginSourceMismatch(err):
return http.StatusUnprocessableEntity, nil, errors.New(l.Tr("form.auth_source_mismatch"))
default:
log.Error("postUserSignIn: authenticate user %q: %v", req.Username, err)
return http.StatusInternalServerError, nil, errors.Wrap(err, "authenticate user")
}
}
if database.Handle.TwoFactors().IsEnabled(r.Context(), u.ID) {
sess.Set("mfaUserID", u.ID)
return http.StatusOK, &userSignInResponse{MFA: true}, nil
}
completeSignIn(sess, mc, u)
return http.StatusOK, &userSignInResponse{}, nil
}
// completeSignIn finalizes the sign-in session for u: writes the auth session,
// clears any in-flight MFA state, and sets the login-status cookie. The
// caller is responsible for navigating to a post-login destination via
// /redirect?to=.
func completeSignIn(sess session.Session, mc *macaron.Context, u *database.User) {
sess.Set("uid", u.ID)
sess.Set("uname", u.Name)
sess.Delete("mfaUserID")
mc.SetCookie(conf.Session.CSRFCookieName, "", -1, conf.Server.Subpath)
if conf.Security.EnableLoginStatusCookie {
mc.SetCookie(conf.Security.LoginStatusCookieName, "true", 0, conf.Server.Subpath)
}
}
func getUserMFA(sess session.Session) (statusCode int, resp any, err error) {
if _, ok := sess.Get("mfaUserID").(int64); !ok {
return http.StatusNotFound, nil, nil
}
return http.StatusNoContent, nil, nil
}
type userMFARequest struct {
Passcode string `json:"passcode" validate:"required,len=6"`
}
type userMFAResponse struct{}
func postUserMFA(r *http.Request, sess session.Session, mc *macaron.Context, ca cache.Cache, l i18n.Locale, req userMFARequest) (statusCode int, resp any, err error) {
userID, ok := sess.Get("mfaUserID").(int64)
if !ok {
return http.StatusUnauthorized, &bindingErrorResponse{Error: l.Tr("auth.mfa_session_expired")}, nil
}
t, err := database.Handle.TwoFactors().GetByUserID(r.Context(), userID)
if err != nil {
log.Error("postUserMFA: get two factor by user ID %d: %v", userID, err)
return http.StatusInternalServerError, nil, errors.Wrap(err, "get two factor by user ID")
}
valid, err := t.ValidateTOTP(req.Passcode)
if err != nil {
log.Error("postUserMFA: validate TOTP for user %d: %v", userID, err)
return http.StatusInternalServerError, nil, errors.Wrap(err, "validate TOTP")
}
if !valid {
msg := l.Tr("auth.mfa_invalid_passcode")
return http.StatusUnauthorized, &bindingErrorResponse{
Fields: fieldErrors{"passcode": &msg},
}, nil
}
cacheKey := userx.TwoFactorCacheKey(userID, req.Passcode)
if _, err := ca.Get(r.Context(), cacheKey); err == nil {
msg := l.Tr("auth.mfa_reused_passcode")
return http.StatusUnauthorized, &bindingErrorResponse{
Fields: fieldErrors{"passcode": &msg},
}, nil
} else if !errors.Is(err, os.ErrNotExist) {
log.Error("postUserMFA: get two factor passcode cache for user %d: %v", userID, err)
}
if err = ca.Set(r.Context(), cacheKey, 1, 60*time.Second); err != nil {
log.Error("postUserMFA: cache two factor passcode for user %d: %v", userID, err)
}
u, err := database.Handle.Users().GetByID(r.Context(), userID)
if err != nil {
log.Error("postUserMFA: get user by ID %d: %v", userID, err)
return http.StatusInternalServerError, nil, errors.Wrap(err, "get user by ID")
}
completeSignIn(sess, mc, u)
return http.StatusOK, &userMFAResponse{}, nil
}
type userMFARecoveryRequest struct {
RecoveryCode string `json:"recoveryCode" validate:"required,len=11"`
}
func postUserMFARecovery(r *http.Request, sess session.Session, mc *macaron.Context, l i18n.Locale, req userMFARecoveryRequest) (statusCode int, resp any, err error) {
userID, ok := sess.Get("mfaUserID").(int64)
if !ok {
return http.StatusUnauthorized, &bindingErrorResponse{Error: l.Tr("auth.mfa_session_expired")}, nil
}
if err := database.Handle.TwoFactors().UseRecoveryCode(r.Context(), userID, req.RecoveryCode); err != nil {
if database.IsTwoFactorRecoveryCodeNotFound(err) {
msg := l.Tr("auth.mfa_invalid_recovery_code")
return http.StatusUnauthorized, &bindingErrorResponse{
Fields: fieldErrors{"recoveryCode": &msg},
}, nil
}
log.Error("postUserMFARecovery: use recovery code for user %d: %v", userID, err)
return http.StatusInternalServerError, nil, errors.Wrap(err, "use recovery code")
}
u, err := database.Handle.Users().GetByID(r.Context(), userID)
if err != nil {
log.Error("postUserMFARecovery: get user by ID %d: %v", userID, err)
return http.StatusInternalServerError, nil, errors.Wrap(err, "get user by ID")
}
completeSignIn(sess, mc, u)
return http.StatusOK, &userMFAResponse{}, nil
}
type userInfo struct {
Username string `json:"username"`
AvatarURL string `json:"avatarURL"`
IsAdmin bool `json:"isAdmin"`
CanCreateOrganization bool `json:"canCreateOrganization"`
}
func getUserInfo(user *database.User) (statusCode int, resp *userInfo, err error) {
if user == nil {
return http.StatusNoContent, nil, nil
}
return http.StatusOK,
&userInfo{
Username: user.Name,
AvatarURL: user.AvatarURL(),
IsAdmin: user.IsAdmin,
CanCreateOrganization: user.CanCreateOrganization(),
},
nil
}
type getUserActivateResponse struct {
Email string `json:"email,omitempty"`
CodeLifetimeHours int `json:"codeLifetimeHours,omitempty"`
}
func getUserActivate(u *database.User) (statusCode int, resp any, err error) {
if u == nil {
return http.StatusUnauthorized, nil, nil
}
// An already-active and authenticated user has no business on the activation page.
if u.IsActive {
return http.StatusNotFound, nil, nil
}
return http.StatusOK, &getUserActivateResponse{
Email: u.Email,
CodeLifetimeHours: conf.Auth.ActivateCodeLives / 60,
}, nil
}
type postUserActivateResponse struct {
RateLimited bool `json:"rateLimited,omitempty"`
CodeLifetimeHours int `json:"codeLifetimeHours,omitempty"`
}
func postUserActivate(r *http.Request, u *database.User, mc *macaron.Context, ca cache.Cache, l i18n.Locale) (statusCode int, resp any, err error) {
if u == nil {
return http.StatusUnauthorized, nil, nil
}
if u.IsActive {
return http.StatusNotFound, nil, nil
}
if !conf.Auth.RequireEmailConfirmation {
return http.StatusForbidden, &bindingErrorResponse{Error: l.Tr("auth.disable_register_mail")}, nil
}
ctx := r.Context()
if _, err := ca.Get(ctx, userx.MailResendCacheKey(u.ID)); err == nil {
return http.StatusOK, &postUserActivateResponse{
RateLimited: true,
CodeLifetimeHours: conf.Auth.ActivateCodeLives / 60,
}, nil
} else if !errors.Is(err, os.ErrNotExist) {
log.Error("postUserActivate: get mail resend cache for user %q: %v", u.Name, err)
}
if err := email.SendActivateAccountMail(mc, database.NewMailerUser(u)); err != nil {
log.Error("postUserActivate: send activation mail to user %q: %v", u.Name, err)
}
if err := ca.Set(ctx, userx.MailResendCacheKey(u.ID), 1, 180*time.Second); err != nil {
log.Error("postUserActivate: put mail resend cache for user %q: %v", u.Name, err)
}
return http.StatusOK, &postUserActivateResponse{CodeLifetimeHours: conf.Auth.ActivateCodeLives / 60}, nil
}
type userActivateCompleteRequest struct {
Code string `json:"code" validate:"required"`
}
func postUserActivateComplete(r *http.Request, sess session.Session, mc *macaron.Context, l i18n.Locale, req userActivateCompleteRequest) (statusCode int, resp any, err error) {
target := verifyUserActiveCode(r.Context(), req.Code)
if target == nil {
return http.StatusBadRequest, &bindingErrorResponse{Error: l.Tr("auth.invalid_code")}, nil
}
v := true
if err := database.Handle.Users().Update(
r.Context(),
target.ID,
database.UpdateUserOptions{
GenerateNewRands: true,
IsActivated: &v,
},
); err != nil {
log.Error("postUserActivateComplete: update user %q: %v", target.Name, err)
return http.StatusInternalServerError, nil, errors.Wrap(err, "update user")
}
log.Trace("User activated: %s", target.Name)
completeSignIn(sess, mc, target)
return http.StatusNoContent, nil, nil
}
type postUserSignOutResponse struct {
RedirectTo string `json:"redirectTo,omitempty"`
}
func postUserSignOut(sess macaronsession.Store, mc *macaron.Context) (statusCode int, resp *postUserSignOutResponse, err error) {
_ = sess.Flush()
_ = sess.Destory(mc)
mc.SetCookie(conf.Session.CSRFCookieName, "", -1, conf.Server.Subpath)
if conf.Auth.CustomLogoutURL != "" {
return http.StatusOK, &postUserSignOutResponse{RedirectTo: conf.Auth.CustomLogoutURL}, nil
}
return http.StatusNoContent, nil, nil
}
+62
View File
@@ -0,0 +1,62 @@
//go:build !prod
package web
import (
"bytes"
"io"
"net/http"
"net/http/httputil"
"net/url"
"strconv"
"strings"
"github.com/cockroachdb/errors"
"github.com/flamego/flamego"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/context"
)
func mountWebAppRoutes(f *flamego.Flame) error {
viteURL, err := url.Parse("http://localhost:5173")
if err != nil {
return errors.Wrap(err, "parse Vite URL")
}
proxy := httputil.NewSingleHostReverseProxy(viteURL)
proxy.ModifyResponse = func(resp *http.Response) error {
if !strings.HasPrefix(resp.Header.Get("Content-Type"), "text/html") {
return nil
}
raw, err := io.ReadAll(resp.Body)
if err != nil {
return errors.Wrap(err, "read Vite response body")
}
_ = resp.Body.Close()
wc := context.WebContextFrom(resp.Request)
body, err := renderIndex(raw, wc)
if err != nil {
log.Error("Failed to render index: %v", err)
body = []byte("Internal Server Error\n")
resp.StatusCode = http.StatusInternalServerError
resp.Status = http.StatusText(http.StatusInternalServerError)
resp.Header.Set("Content-Type", "text/plain; charset=utf-8")
} else if wc.StatusCode > 0 {
resp.StatusCode = wc.StatusCode
resp.Status = http.StatusText(wc.StatusCode)
}
resp.Body = io.NopCloser(bytes.NewReader(body))
resp.ContentLength = int64(len(body))
resp.Header.Set("Content-Length", strconv.Itoa(len(body)))
// The upstream validators describe the unmodified body. Drop them
// so the browser does not satisfy a conditional request from a
// cached copy that has a stale injected lang attribute.
resp.Header.Del("ETag")
resp.Header.Del("Last-Modified")
return nil
}
f.Any("/{**}", func(w http.ResponseWriter, r *http.Request) {
proxy.ServeHTTP(w, r)
})
return nil
}
+68
View File
@@ -0,0 +1,68 @@
//go:build prod
package web
import (
"io/fs"
"net/http"
"github.com/cockroachdb/errors"
"github.com/flamego/flamego"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/context"
"gogs.io/gogs/public"
)
func mountWebAppRoutes(f *flamego.Flame) error {
webFS, err := fs.Sub(public.WebAssets, "dist")
if err != nil {
return errors.Wrap(err, "load embedded web assets")
}
// Prefix matches the path rewrites renderIndex applies to the index
// shell. Without it the browser fetches /<subpath>/assets/... and the
// static handler looks them up in webFS at "<subpath>/assets/...",
// which has no <subpath> directory, so every asset would 404 and fall
// through to the wildcard handler as text/html.
//
// Index is set to a sentinel that does not exist in the FS so flamego.Static
// never serves the raw index.html for "/" requests. The catch-all below
// always renders the shell through renderIndex instead, ensuring template
// substitutions are applied.
f.Use(flamego.Static(flamego.StaticOptions{
FileSystem: http.FS(webFS),
Prefix: conf.Server.Subpath,
Index: "__disabled__",
}))
index, err := public.WebAssets.ReadFile("dist/index.html")
if err != nil {
return errors.Wrap(err, `read "dist/index.html"`)
}
f.Get("/{**}", func(w http.ResponseWriter, r *http.Request) {
wc := context.WebContextFrom(r)
body, err := renderIndex(index, wc)
if err != nil {
log.Error("Failed to render index: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
// The body is rewritten per request (lang injection, future
// runtime config), so caching it would serve stale content to
// any user whose request resolves to a different locale. Use
// no-store rather than no-cache so the browser cannot keep a
// copy at all, not even for revalidation. Static assets keep
// their normal caching via flamego.Static.
w.Header().Set("Cache-Control", "no-store")
status := wc.StatusCode
if status <= 0 {
status = http.StatusOK
}
w.WriteHeader(status)
_, _ = w.Write(body)
})
return nil
}
+35 -14
View File
@@ -2,11 +2,14 @@
package main
import (
"context"
"os"
"path/filepath"
"github.com/urfave/cli"
"github.com/urfave/cli/v3"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/cmd/gogs/internal/web"
"gogs.io/gogs/internal/conf"
)
@@ -14,21 +17,39 @@ func init() {
conf.App.Version = "0.15.0+dev"
}
var webCommand = cli.Command{
Name: "web",
Usage: "Start the web server",
Description: "Serves the web interface, API, and HTTP Git endpoints.",
Action: func(_ context.Context, cmd *cli.Command) error {
var portOverride int
if cmd.IsSet("port") {
portOverride = cmd.Int("port")
}
return web.Run(configFromLineage(cmd), portOverride)
},
Flags: []cli.Flag{
intFlag("port, p", 3000, "Alternative listening port to use"),
stringFlag("config, c", filepath.Join(conf.CustomDir(), "conf", "app.ini"), "Custom configuration file path"),
},
}
func main() {
app := cli.NewApp()
app.Name = "Gogs"
app.Usage = "A painless self-hosted Git service"
app.Version = conf.App.Version
app.Commands = []cli.Command{
webCommand,
servCommand,
hookCommand,
adminCommand,
importCommand,
backupCommand,
restoreCommand,
cmd := &cli.Command{
Name: "Gogs",
Usage: "The painless way to host your own Git service",
Version: conf.App.Version,
Commands: []*cli.Command{
&webCommand,
&servCommand,
&hookCommand,
&adminCommand,
&importCommand,
&backupCommand,
&restoreCommand,
},
}
if err := app.Run(os.Args); err != nil {
if err := cmd.Run(context.Background(), os.Args); err != nil {
log.Fatal("Failed to start application: %v", err)
}
}
+20 -20
View File
@@ -8,14 +8,14 @@ import (
"github.com/cockroachdb/errors"
"github.com/unknwon/cae/zip"
"github.com/urfave/cli"
"github.com/urfave/cli/v3"
"gopkg.in/ini.v1"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/database"
"gogs.io/gogs/internal/osutil"
"gogs.io/gogs/internal/semverutil"
"gogs.io/gogs/internal/osx"
"gogs.io/gogs/internal/semverx"
)
var restoreCommand = cli.Command{
@@ -42,11 +42,11 @@ be skipped and remain unchanged.`,
// format that is able to import.
var lastSupportedVersionOfFormat = map[int]string{}
func runRestore(c *cli.Context) error {
zip.Verbose = c.Bool("verbose")
func runRestore(ctx context.Context, cmd *cli.Command) error {
zip.Verbose = cmd.Bool("verbose")
tmpDir := c.String("tempdir")
if !osutil.IsDir(tmpDir) {
tmpDir := cmd.String("tempdir")
if !osx.IsDir(tmpDir) {
log.Fatal("'--tempdir' does not exist: %s", tmpDir)
}
archivePath := path.Join(tmpDir, archiveRootDir)
@@ -58,15 +58,15 @@ func runRestore(c *cli.Context) error {
}
defer func() { _ = os.RemoveAll(archivePath) }()
log.Info("Restoring backup from: %s", c.String("from"))
err = zip.ExtractTo(c.String("from"), tmpDir)
log.Info("Restoring backup from: %s", cmd.String("from"))
err = zip.ExtractTo(cmd.String("from"), tmpDir)
if err != nil {
log.Fatal("Failed to extract backup archive: %v", err)
}
// Check backup version
metaFile := filepath.Join(archivePath, "metadata.ini")
if !osutil.IsFile(metaFile) {
if !osx.IsFile(metaFile) {
log.Fatal("File 'metadata.ini' is missing")
}
metadata, err := ini.Load(metaFile)
@@ -74,7 +74,7 @@ func runRestore(c *cli.Context) error {
log.Fatal("Failed to load metadata '%s': %v", metaFile, err)
}
backupVersion := metadata.Section("").Key("GOGS_VERSION").MustString("999.0")
if semverutil.Compare(conf.App.Version, "<", backupVersion) {
if semverx.Compare(conf.App.Version, "<", backupVersion) {
log.Fatal("Current Gogs version is lower than backup version: %s < %s", conf.App.Version, backupVersion)
}
formatVersion := metadata.Section("").Key("VERSION").MustInt()
@@ -90,9 +90,9 @@ func runRestore(c *cli.Context) error {
// Otherwise, it's optional to set config file flag.
configFile := filepath.Join(archivePath, "custom", "conf", "app.ini")
var customConf string
if c.IsSet("config") {
customConf = c.String("config")
} else if !osutil.IsFile(configFile) {
if lineageConf := configFromLineage(cmd); lineageConf != "" {
customConf = lineageConf
} else if !osx.IsFile(configFile) {
log.Fatal("'--config' is not specified and custom config file is not found in backup")
} else {
customConf = configFile
@@ -111,13 +111,13 @@ func runRestore(c *cli.Context) error {
// Database
dbDir := path.Join(archivePath, "db")
if err = database.ImportDatabase(context.Background(), conn, dbDir, c.Bool("verbose")); err != nil {
if err = database.ImportDatabase(ctx, conn, dbDir, cmd.Bool("verbose")); err != nil {
log.Fatal("Failed to import database: %v", err)
}
if !c.Bool("database-only") {
if !cmd.Bool("database-only") {
// Custom files
if osutil.IsDir(conf.CustomDir()) {
if osx.IsDir(conf.CustomDir()) {
if err = os.Rename(conf.CustomDir(), conf.CustomDir()+".bak"); err != nil {
log.Fatal("Failed to backup current 'custom': %v", err)
}
@@ -131,12 +131,12 @@ func runRestore(c *cli.Context) error {
for _, dir := range []string{"attachments", "avatars", "repo-avatars"} {
// Skip if backup archive does not have corresponding data
srcPath := filepath.Join(archivePath, "data", dir)
if !osutil.IsDir(srcPath) {
if !osx.IsDir(srcPath) {
continue
}
dirPath := filepath.Join(conf.Server.AppDataPath, dir)
if osutil.IsDir(dirPath) {
if osx.IsDir(dirPath) {
if err = os.Rename(dirPath, dirPath+".bak"); err != nil {
log.Fatal("Failed to backup current 'data': %v", err)
}
@@ -149,7 +149,7 @@ func runRestore(c *cli.Context) error {
// Repositories
reposPath := filepath.Join(archivePath, "repositories.zip")
if !c.Bool("exclude-repos") && !c.Bool("database-only") && osutil.IsFile(reposPath) {
if !cmd.Bool("exclude-repos") && !cmd.Bool("database-only") && osx.IsFile(reposPath) {
if err := zip.ExtractTo(reposPath, filepath.Dir(conf.Repository.Root)); err != nil {
log.Fatal("Failed to extract 'repositories.zip': %v", err)
}
+8 -14
View File
@@ -10,7 +10,7 @@ import (
"strings"
"time"
"github.com/urfave/cli"
"github.com/urfave/cli/v3"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf"
@@ -48,15 +48,10 @@ func fail(userMessage, errMessage string, args ...any) {
os.Exit(1)
}
func setup(c *cli.Context, logFile string, connectDB bool) {
func setup(cmd *cli.Command, logFile string, connectDB bool) {
conf.HookMode = true
var customConf string
if c.IsSet("config") {
customConf = c.String("config")
} else if c.GlobalIsSet("config") {
customConf = c.GlobalString("config")
}
customConf := configFromLineage(cmd)
err := conf.Init(customConf)
if err != nil {
@@ -128,16 +123,15 @@ var allowedCommands = map[string]database.AccessMode{
"git-receive-pack": database.AccessModeWrite,
}
func runServ(c *cli.Context) error {
ctx := context.Background()
setup(c, "serv.log", true)
func runServ(ctx context.Context, cmd *cli.Command) error {
setup(cmd, "serv.log", true)
if conf.SSH.Disabled {
println("Gogs: SSH has been disabled")
return nil
}
if len(c.Args()) < 1 {
if cmd.Args().Len() < 1 {
fail("Not enough arguments", "Not enough arguments")
}
@@ -188,10 +182,10 @@ func runServ(c *cli.Context) error {
// Allow anonymous (user is nil) clone for public repositories.
var user *database.User
keyID, _ := strconv.ParseInt(strings.TrimPrefix(c.Args()[0], "key-"), 10, 64)
keyID, _ := strconv.ParseInt(strings.TrimPrefix(cmd.Args().Get(0), "key-"), 10, 64)
key, err := database.GetPublicKeyByID(keyID)
if err != nil {
fail("Invalid key ID", "Invalid key ID '%s': %v", c.Args()[0], err)
fail("Invalid key ID", "Invalid key ID '%s': %v", cmd.Args().Get(0), err)
}
if requestMode == database.AccessModeWrite || repo.IsPrivate {
+13 -15
View File
@@ -141,8 +141,7 @@ FILE_MAX_SIZE = 3
MAX_FILES = 5
[database]
; The database backend, either "postgres", "mysql" "sqlite3" or "mssql".
; You can connect to TiDB with MySQL protocol.
; The database backend, either "postgres", "mysql" or "sqlite3".
TYPE = postgres
HOST = 127.0.0.1:5432
NAME = gogs
@@ -165,12 +164,8 @@ INSTALL_LOCK = false
; The secret to encrypt cookie values, 2FA code, etc.
; !!CHANGE THIS TO KEEP YOUR USER DATA SAFE!!
SECRET_KEY = !#@FDEWREWR&*(
; The days remembered for auto-login.
; The number of days a sign-in session persists across browser restarts.
LOGIN_REMEMBER_DAYS = 7
; The cookie name to store auto-login information.
COOKIE_REMEMBER_NAME = gogs_incredible
; The cookie name to store logged in username.
COOKIE_USERNAME = gogs_awesome
; Whether to set secure cookie.
COOKIE_SECURE = false
; Whether to set cookie to indicate user login status.
@@ -197,8 +192,6 @@ USER = noreply@gogs.localhost
; The login password.
PASSWORD =
; Whether to disable HELO operation when the hostname is different.
DISABLE_HELO =
; The custom hostname for HELO operation, default is from system.
HELO_HOSTNAME =
@@ -236,6 +229,9 @@ ENABLE_REVERSE_PROXY_AUTHENTICATION = false
ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false
; The HTTP header used as username for reverse proxy authentication.
REVERSE_PROXY_AUTHENTICATION_HEADER = X-WEBAUTH-USER
; Lists the IPs or CIDR ranges whose requests are allowed to set the reverse
; proxy authentication header.
TRUSTED_PROXY_IPS = 127.0.0.0/8,::1/128
[user]
; Whether to enable email notifications for users.
@@ -255,19 +251,20 @@ COOKIE_NAME = i_like_gogs
COOKIE_SECURE = false
; The GC interval in seconds for session data.
GC_INTERVAL = 3600
; The maximum life time in seconds for a session.
MAX_LIFE_TIME = 86400
; The maximum idle time in seconds before a session record is garbage-collected.
; Set lower than `[security] LOGIN_REMEMBER_DAYS * 86400` to enforce a sliding
; idle timeout. Otherwise the session lives for the full cookie lifetime.
MAX_LIFE_TIME = 604800
; The cookie name for CSRF token.
CSRF_COOKIE_NAME = _csrf
[cache]
; The cache adapter, either "memory", "redis", or "memcache".
; The cache adapter, either "memory" or "redis".
ADAPTER = memory
; For "memory" only, GC interval in seconds.
INTERVAL = 60
; For "redis" and "memcache", connection host address:
; For "redis", connection host address:
; - redis: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180
; - memcache: `127.0.0.1:11211`
HOST =
[http]
@@ -279,6 +276,8 @@ ACCESS_CONTROL_ALLOW_ORIGIN =
STORAGE = local
; The root path to store LFS objects on local file system.
OBJECTS_PATH = data/lfs-objects
; The path to temporarily store LFS objects during upload verification.
OBJECTS_TEMP_PATH = data/tmp/lfs-objects
[attachment]
; Whether to enabled upload attachments in general.
@@ -570,6 +569,5 @@ mn-MN = mn
ro-RO = ro
[other]
SHOW_FOOTER_BRANDING = false
; Show time of template execution in the footer
SHOW_FOOTER_TEMPLATE_LOAD_TIME = true
+88 -47
View File
@@ -1,13 +1,13 @@
app_desc = A painless self-hosted Git service
app_desc = The painless way to host your own Git service
home = Home
dashboard = Dashboard
explore = Explore
help = Help
sign_in = Sign In
sign_out = Sign Out
sign_up = Sign Up
register = Register
sign_in = Sign in
sign_out = Sign out
sign_up = Sign up
register = Create account
website = Website
page = Page
template = Template
@@ -17,35 +17,46 @@ user_profile_and_more = User profile and more
signed_in_as = Signed in as
username = Username
username_placeholder = Enter your username or email
new_username_placeholder = Choose a username
email = Email
email_placeholder = Enter your email
password = Password
re_type = Re-Type
password_placeholder = Enter your password
captcha = Captcha
captcha_placeholder = Enter the characters shown above
captcha_image_alt = Captcha image
refresh_captcha = Refresh captcha
click_to_refresh_captcha = Click to refresh
repository = Repository
organization = Organization
mirror = Mirror
new_repo = New Repository
new_migrate = New Migration
new_mirror = New Mirror
new_fork = New Fork Repository
new_org = New Organization
manage_org = Manage Organizations
admin_panel = Admin Panel
new_repo = New repository
new_migrate = New migration
new_mirror = New mirror
new_fork = New fork repository
new_org = New organization
manage_org = Manage organizations
admin_panel = Admin panel
account_settings = Account Settings
settings = Settings
your_profile = Your Profile
your_settings = Your Settings
theme = Theme
theme_light = Light
theme_dark = Dark
theme_system = System
your_profile = Your profile
your_settings = Your settings
activities = Activities
pull_requests = Pull Requests
pull_requests = Pull requests
issues = Issues
cancel = Cancel
[status]
page_not_found = Page Not Found
internal_server_error = Internal Server Error
page_not_found = Page not found
internal_server_error = Internal server error
[install]
install = Installation
@@ -116,11 +127,10 @@ admin_setting_desc = You don't need to create an admin account right now. The fi
admin_title = Admin Account Settings
admin_name = Username
admin_password = Password
confirm_password = Confirm Password
confirm_password = Confirm password
admin_email = Admin Email
install_gogs = Install Gogs
test_git_failed = Failed to test 'git' command: %v
sqlite3_not_available = Your release version does not support SQLite3, please download the official binary version from %s, NOT the gobuild version.
invalid_db_setting = Database setting is not correct: %v
invalid_repo_path = Repository root path is invalid: %v
run_user_not_match = Run user isn't the current user: %s -> %s
@@ -152,39 +162,68 @@ organizations = Organizations
search = Search
[auth]
create_new_account = Create New Account
create_new_account = Create new account
sign_up_submitting = Creating account...
sign_up_failed = Could not create account, please try again.
sign_in_submitting = Signing in...
sign_in_failed = Could not sign in, please try again.
show_password = Show password
hide_password = Hide password
back_to_sign_in = Back to sign in
mfa_title = Multi-factor authentication
mfa_passcode = Passcode
mfa_passcode_placeholder = Enter the 6-digit code from your authenticator
mfa_recovery_code = Recovery code
mfa_recovery_code_placeholder = Enter a recovery code
mfa_use_recovery_code = Use a recovery code instead
mfa_use_passcode = Use a passcode instead
mfa_verify = Verify
mfa_verifying = Verifying...
mfa_session_expired = Your sign-in session has expired. Please sign in again.
mfa_verify_failed = Verification failed. Please try again.
mfa_invalid_passcode = The passcode you entered is not valid.
mfa_reused_passcode = The passcode you entered has already been used, please try another one.
mfa_invalid_recovery_code = Recovery code already used or invalid.
register_hepler_msg = Already have an account? Sign in now!
social_register_hepler_msg = Already have an account? Bind now!
disable_register_prompt = Sorry, registration has been disabled. Please contact the site administrator.
disable_register_mail = Sorry, email services are disabled. Please contact the site administrator.
auth_source = Authentication Source
auth_source = Authentication source
local = Local
remember_me = Remember Me
forgot_password= Forgot Password
forget_password = Forgot password?
sign_up_now = Need an account? Sign up now.
confirmation_mail_sent_prompt = A new confirmation email has been sent to <b>%s</b>, please check your inbox within the next %d hours to complete the registration process.
active_your_account = Activate Your Account
sign_up_now = Create a new account
confirmation_email_sent = A new confirmation email has been sent to <b>%s</b>, please check your inbox within the next %d hours to complete the registration process.
activate_your_account = Activate your account
prohibit_login = Login Prohibited
prohibit_login_desc = Your account is prohibited from logging in. Please contact the site admin.
resent_limit_prompt = Sorry, you already requested an activation email recently. Please wait 3 minutes then try again.
resend_rate_limited = Sorry, you already requested an activation email recently. Please wait 3 minutes then try again.
has_unconfirmed_mail = Hi %s, you have an unconfirmed email address (<b>%s</b>). If you haven't received a confirmation email or need to receive a new one, please click the button below.
resend_mail = Click here to resend your activation email
send_reset_mail = Click here to (re)send your password reset email
reset_password = Reset Your Password
invalid_code = Sorry, your confirmation code has expired or not valid.
reset_password_helper = Click here to reset your password
password_too_short = Password length must be at least 6 characters.
send_activation_email = Send activation email
check_activation_email = Please check your email and click the activation link to finish creating your account.
activation_email_pending = Your email address <email>{email}</email> is not yet confirmed. Click below to send a new activation email valid for <hours>{hours} hours</hours>.
activation_email_sent = A new activation email has been sent to <email>{email}</email>. Please check your inbox within <hours>{hours} hours</hours>.
sending_activation_email = Sending activation email...
send_activation_email_failed = Could not send activation email, please try again.
activating_account = Activating your account...
send_reset_email = Send password reset email
reset_password_email_submitting = Sending password reset email...
reset_password_email_failed = Could not send password reset email, please try again.
reset_password_email_sent = A password reset email has been sent to <email>{email}</email>, please check your inbox within <hours>{hours} hours</hours>.
reset_password = Reset your password
invalid_code = The confirmation code has expired or not valid.
reset_password_submit = Reset password
reset_password_submitting = Resetting password...
reset_password_resend_limited = You already requested a password reset email recently. Please wait 3 minutes then try again.
reset_password_failed = Could not reset password, please try again.
new_password = New password
new_password_placeholder = Enter your new password
confirm_password_placeholder = Re-enter your password
confirm_new_password = Confirm new password
confirm_new_password_placeholder = Re-enter your new password
password_mismatch = The two passwords do not match.
non_local_account = Non-local accounts cannot change passwords through Gogs.
login_two_factor = Two-factor Authentication
login_two_factor_passcode = Authentication Passcode
login_two_factor_enter_recovery_code = Enter a two-factor recovery code
login_two_factor_recovery = Two-factor Recovery
login_two_factor_recovery_code = Recovery Code
login_two_factor_enter_passcode = Enter a two-factor passcode
login_two_factor_invalid_recovery_code = Recovery code already used or invalid.
[mail]
activate_account = Please activate your account
activate_email = Verify your email address
@@ -198,7 +237,9 @@ no = No
modify = Modify
[form]
invalid_request = The request could not be processed
UserName = Username
Username = Username
RepoName = Repository name
Email = Email address
Password = Password
@@ -209,6 +250,8 @@ PayloadUrl = Payload URL
TeamName = Team name
AuthName = Authorization name
AdminEmail = Admin email
Passcode = Passcode
RecoveryCode = Recovery code
NewBranchName = New branch name
CommitSummary = Commit summary
@@ -236,7 +279,7 @@ repo_name_been_taken = Repository name has already been taken.
org_name_been_taken = Organization name has already been taken.
team_name_been_taken = Team name has already been taken.
email_been_used = Email address has already been used.
username_password_incorrect = Username or password is not correct.
username_password_incorrect = Username or password is incorrect.
auth_source_mismatch = The authentication source selected is not associated with the user.
enterred_invalid_repo_name = Please make sure that the repository name you entered is correct.
enterred_invalid_owner_name = Please make sure that the owner name you entered is correct.
@@ -1086,7 +1129,7 @@ users.created = Created
users.send_register_notify = Send Registration Notification To User
users.new_success = New account '%s' has been created successfully.
users.edit = Edit
users.auth_source = Authentication Source
users.auth_source = Authentication source
users.local = Local
users.auth_login_name = Authentication Login Name
users.password_helper = Leave it empty to remain unchanged.
@@ -1119,7 +1162,7 @@ repos.stars = Stars
repos.issues = Issues
repos.size = Size
auths.auth_sources = Authentication Sources
auths.auth_sources = Authentication sources
auths.new = Add New Source
auths.name = Name
auths.type = Type
@@ -1249,8 +1292,6 @@ config.db.max_idle_conns = Maximum idle connections
config.security_config = Security configuration
config.security.login_remember_days = Login remember days
config.security.cookie_remember_name = Remember cookie
config.security.cookie_username = Username cookie
config.security.cookie_secure = Enable secure cookie
config.security.reverse_proxy_auth_user = Reverse proxy authentication header
config.security.enable_login_status_cookie = Enable login status cookie
@@ -1263,7 +1304,6 @@ config.email.subject_prefix = Subject prefix
config.email.host = Host
config.email.from = From
config.email.user = User
config.email.disable_helo = Disable HELO
config.email.helo_hostname = HELO hostname
config.email.skip_verify = Skip certificate verify
config.email.use_certificate = Use custom certificate
@@ -1286,6 +1326,7 @@ config.auth.enable_registration_captcha = Enable registration captcha
config.auth.enable_reverse_proxy_authentication = Enable reverse proxy authentication
config.auth.enable_reverse_proxy_auto_registration = Enable reverse proxy auto registration
config.auth.reverse_proxy_authentication_header = Reverse proxy authentication header
config.auth.trusted_proxy_ips = Trusted proxy IPs
config.user_config = User configuration
config.user.enable_email_notify = Enable email notification
-20
View File
@@ -1,20 +0,0 @@
#!/bin/sh
set -xe
if [ "$(uname -m)" = "aarch64" ]; then
export arch='arm64'
export checksum='17f325293d08f6f964e0530842e9ef1410dd5f83ee6475b493087391032b0cfd'
elif [ "$(uname -m)" = "armv7l" ]; then
export arch='arm'
export checksum='e5b0261e9f6563ce3ace9e038520eb59d2c77c8d85f2b47ab41e1fe7cf321528'
else
export arch='amd64'
export checksum='a35462ec71410cccfc428072de830e4478bc57a919d0131ef7897759270dff8f'
fi
wget --quiet https://github.com/go-task/task/releases/download/v3.40.1/task_linux_${arch}.tar.gz -O task_linux_${arch}.tar.gz
echo "${checksum} task_linux_${arch}.tar.gz" | sha256sum -cs
tar -xzf task_linux_${arch}.tar.gz
mv task /usr/local/bin/task
+67
View File
@@ -0,0 +1,67 @@
---
title: "CLI reference"
description: "Discover all the commands available in the gogs binary"
icon: "terminal"
---
Most people know `gogs web` for starting the server, but the `gogs` binary ships with several other commands that help you manage your instance from the command line.
Run `gogs --help` at any time to see the full list of available commands, and `gogs <command> --help` for details on a specific command.
<Tip>
Every command accepts a `--config` (`-c`) flag to specify a custom configuration file path. The default is `custom/conf/app.ini`.
</Tip>
## Starting the server
```bash
gogs web
```
The `web` command starts the HTTP server that powers the web UI, the REST API, and Git HTTP operations. Use the `--port` (`-p`) flag to override the default listening port.
## Administration
```bash
gogs admin <subcommand>
```
The `admin` command lets you perform maintenance tasks without going through the web interface. Available subcommands include:
| Subcommand | Purpose |
|---|---|
| `create-user` | Create a new user account (with optional `--admin` flag). |
| `delete-inactive-users` | Remove user accounts that were never activated. |
| `delete-repository-archives` | Clean up generated repository archive files. |
| `delete-missing-repositories` | Remove database records for repositories whose Git data is missing on disk. |
| `collect-garbage` | Run `git gc` across all repositories. |
| `rewrite-authorized-keys` | Regenerate the SSH `authorized_keys` file from the database. |
| `resync-hooks` | Re-write Git server-side hooks for all repositories. |
| `reinit-missing-repositories` | Re-initialize bare Git repositories that are missing on disk. |
<Warning>
`rewrite-authorized-keys` replaces the entire `authorized_keys` file. Any non-Gogs keys in that file will be lost.
</Warning>
## Importing data
```bash
gogs import locale --source <dir> --target <dir>
```
The `import` command helps you bring portable data from other Gogs installations into your local instance. Currently the only subcommand is `locale`, which merges locale files from a source directory into a target directory.
## Backup and restore
```bash
gogs backup
gogs restore --from <archive>
```
`backup` dumps the database, repositories, and related files into a single zip archive. `restore` imports everything back from an archive, which is useful for migrating Gogs to another server or switching database engines.
Both commands support `--database-only` and `--exclude-repos` flags to narrow the scope. `backup` additionally supports `--exclude-mirror-repos` and `--target` to control where the archive is saved.
## Internal commands
The `serv` and `hook` commands are used internally by the SSH and Git subsystems. You generally do not need to invoke them directly, but they are the reason Gogs can handle SSH authentication and server-side Git hooks without any external tooling.
+1 -13
View File
@@ -55,23 +55,11 @@ There are two ways to authenticate through the Gogs API. Requests that require a
</Warning>
</Tab>
<Tab title="Access token">
Personal access tokens are the recommended way to authenticate. They can be sent via a request **header** or a **URL query parameter**.
**Using a header:**
Personal access tokens must be sent via the `Authorization` request header.
```bash
curl -H "Authorization: token {YOUR_ACCESS_TOKEN}" https://gogs.example.com/api/v1/user/repos
```
**Using a query parameter:**
```bash
curl https://gogs.example.com/api/v1/user/repos?token={YOUR_ACCESS_TOKEN}
```
<Tip>
Using the `Authorization` header is preferred over the query parameter, as URLs may be logged by proxies and servers.
</Tip>
</Tab>
</Tabs>
-6
View File
@@ -5449,12 +5449,6 @@
"in": "header",
"name": "Authorization",
"description": "Personal access token. Use format: token {YOUR_ACCESS_TOKEN}"
},
"TokenQuery": {
"type": "apiKey",
"in": "query",
"name": "token",
"description": "Access token as query parameter"
}
},
"schemas": {
+2 -2
View File
@@ -13,7 +13,7 @@ Answers to common questions about Gogs configuration, administration, and usage.
You can change the listening port on the first run by passing the `-port` flag:
```bash
./gogs web -port 3001
gogs web -port 3001
```
This flag also updates the port number shown on the install page, so pick the port you intend to keep using.
@@ -58,7 +58,7 @@ Answers to common questions about Gogs configuration, administration, and usage.
```bash
su git
cd /home/git/gogs
./gogs admin create-user --name tmpuser --password tmppassword --admin --email tmp@example.com
gogs admin create-user --name tmpuser --password tmppassword --admin --email tmp@example.com
```
2. **Start Gogs** again, then log in as `tmpuser` in your browser. Navigate to **Admin Panel** > **Users**, click **Edit** next to the original administrator account, and set a new password.
+1 -1
View File
@@ -10,7 +10,7 @@
Locale files has been successfully imported!
```
1. Run `task web` to start the web server, then visit the site in the browser to make sure nothing blows up.
1. Run `moon run gogs:dev` to start the web server, then visit the site in the browser to make sure nothing blows up.
1. Check out a new branch using `git checkout -b update-locales`.
1. Stage changes
1. Run `git commit -m "locale: sync from Crowdin"`.
+9 -5
View File
@@ -23,7 +23,7 @@ Gogs has the following dependencies:
- [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) (v1.8.3 or higher)
- [Go](https://golang.org/doc/install) (v1.20 or higher)
- [Less.js](http://lesscss.org/usage/#command-line-usage-installing)
- [Task](https://github.com/go-task/task) (v3)
- [Moon](https://moonrepo.dev/docs/install)
- [goimports](https://pkg.go.dev/golang.org/x/tools/cmd/goimports)
- [go-mockgen](https://github.com/derision-test/go-mockgen)
- Database upon your choice (pick one, we choose PostgreSQL in this document):
@@ -38,13 +38,16 @@ Gogs has the following dependencies:
1. Install dependencies:
```bash
brew install go postgresql git npm go-task/tap/go-task
brew install go postgresql git npm moon portless
portless trust
npm install -g less
npm install -g less-plugin-clean-css
go install github.com/derision-test/go-mockgen/cmd/go-mockgen@v1.3.3
go install golang.org/x/tools/cmd/goimports@latest
```
`portless trust` adds the local CA to your system trust store so `https://gogs.localhost` works without browser warnings. The `moon run gogs:dev` task will start the proxy and register the route automatically.
1. Configure PostgreSQL to start automatically:
```bash
@@ -78,11 +81,12 @@ Gogs has the following dependencies:
```bash
sudo apt install -y make git-all postgresql postgresql-contrib golang-go nodejs
npm install -g less
go install github.com/go-task/task/v3/cmd/task@latest
go install github.com/derision-test/go-mockgen/cmd/go-mockgen@v1.3.3
go install golang.org/x/tools/cmd/goimports@latest
```
1. Install [Moon](https://moonrepo.dev/docs/install).
1. Configure startup services:
```bash
@@ -146,10 +150,10 @@ SSL_MODE = disable
The following command will start the web server and automatically recompile and restart the server if any Go files changed:
```bash
task web --watch
moon run gogs:dev
```
**NOTE** If you changed any file under `conf/`, `template/` or `public/` directory, be sure to run `task generate` afterwards!
**NOTE** If you changed any file under `conf/`, `templates/` or `public/` directory, be sure to rerun `moon run gogs:dev` afterwards!
## Other nice things
+2 -1
View File
@@ -50,7 +50,8 @@
"advancing/webhooks",
"advancing/git-lfs",
"advancing/custom-templates",
"advancing/localization"
"advancing/localization",
"advancing/cli-reference"
]
},
{
+1 -1
View File
@@ -71,7 +71,7 @@ The work directory (parent of `custom/`) can also be overridden with `GOGS_WORK_
Every Gogs subcommand accepts `-c, --config` to point to a configuration file at a non-default location:
```bash
./gogs web --config /etc/gogs/app.ini
gogs web --config /etc/gogs/app.ini
```
### What lives in `custom/`
+1 -2
View File
@@ -57,7 +57,7 @@ If you choose to use MySQL or PostgreSQL as your database backend, you need to f
Release archives containing `mws` come with built-in Windows service support. If you prefer to manage the service using [NSSM](https://nssm.cc), download the standard version instead.
</Note>
Once extracted the archive, run `./gogs web` to start the server. Use `./gogs web --help` to see all available options.
Once extracted the archive, run `gogs web` to start the server. Use `gogs web --help` to see all available options.
</Tab>
<Tab title="Docker">
Two types of Docker images are provided:
@@ -71,7 +71,6 @@ If you choose to use MySQL or PostgreSQL as your database backend, you need to f
|Source| Description | Note|
|------|------------------------------------------|-----|
|Packager.io ([link](https://packager.io/gh/gogs/gogs))|Every commit of `main`|After installation, place custom configuration in `/etc/default/gogs`.|
|Arch User Repository ([link](https://aur.archlinux.org/packages/gogs/))| Stable releases | Detailed instructions available in the [Arch Linux Wiki entry](https://wiki.archlinux.org/title/Gogs). |
</Tab>
</Tabs>
+18 -18
View File
@@ -5,14 +5,14 @@ description: "The painless way to host your own Git service"
icon: "book-open"
---
<img
className="block dark:hidden"
<img
className="block dark:hidden"
src="/images/logo-light.svg"
noZoom
/>
<img
className="hidden dark:block"
<img
className="hidden dark:block"
src="/images/logo-dark.svg"
noZoom
/>
@@ -34,14 +34,14 @@ The growth of the Gogs project wasn't possible without our world-class sponsors!
<Columns cols={2}>
<a href="https://www.digitalocean.com/?refcode=5aeb02268b55&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge" target="_blank" style={{borderBottom: "none"}}>
<img
className="block dark:hidden"
<img
className="block dark:hidden"
src="/images/sponsors/digitalocean-light.png"
width={320}
noZoom
/>
<img
className="hidden dark:block"
<img
className="hidden dark:block"
src="/images/sponsors/digitalocean-dark.png"
width={320}
noZoom
@@ -50,13 +50,13 @@ The growth of the Gogs project wasn't possible without our world-class sponsors!
<a href="https://www.mintlify.com" target="_blank" style={{borderBottom: "none"}}>
<img
className="block dark:hidden"
className="block dark:hidden"
src="/images/sponsors/mintlify-light.svg"
width={320}
noZoom
/>
<img
className="hidden dark:block"
className="hidden dark:block"
src="/images/sponsors/mintlify-dark.svg"
width={320}
noZoom
@@ -64,14 +64,14 @@ The growth of the Gogs project wasn't possible without our world-class sponsors!
</a>
<a href="https://www.crowdin.com" target="_blank" style={{borderBottom: "none"}}>
<img
className="block dark:hidden"
<img
className="block dark:hidden"
src="/images/sponsors/crowdin-light.svg"
width={320}
noZoom
/>
<img
className="hidden dark:block"
<img
className="hidden dark:block"
src="/images/sponsors/crowdin-dark.svg"
width={320}
noZoom
@@ -79,14 +79,14 @@ The growth of the Gogs project wasn't possible without our world-class sponsors!
</a>
<a href="https://www.buildkite.com" target="_blank" style={{borderBottom: "none", paddingTop: "5px"}}>
<img
className="block dark:hidden"
<img
className="block dark:hidden"
src="/images/sponsors/buildkite-light.svg"
width={320}
noZoom
/>
<img
className="hidden dark:block"
<img
className="hidden dark:block"
src="/images/sponsors/buildkite-dark.svg"
width={320}
noZoom
+50 -44
View File
@@ -1,41 +1,45 @@
module gogs.io/gogs
go 1.25.0
go 1.26.0
require (
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/Masterminds/semver/v3 v3.4.0
github.com/cockroachdb/errors v1.12.0
github.com/cockroachdb/errors v1.13.0
github.com/derision-test/go-mockgen/v2 v2.1.1
github.com/editorconfig/editorconfig-core-go/v2 v2.6.4
github.com/fatih/color v1.18.0
github.com/flamego/binding v1.3.0
github.com/flamego/cache v1.5.1
github.com/flamego/captcha v1.3.0
github.com/flamego/flamego v1.12.0
github.com/flamego/session v1.3.0
github.com/flamego/validator v1.0.0
github.com/glebarez/go-sqlite v1.21.2
github.com/glebarez/sqlite v1.11.0
github.com/go-ldap/ldap/v3 v3.4.12
github.com/go-macaron/binding v1.2.0
github.com/go-macaron/cache v0.0.0-20190810181446-10f7c57e2196
github.com/go-macaron/captcha v0.2.0
github.com/go-macaron/csrf v0.0.0-20190812063352-946f6d303a4c
github.com/go-macaron/gzip v0.0.0-20160222043647-cad1c6580a07
github.com/go-macaron/i18n v0.6.0
github.com/go-macaron/session v1.0.3
github.com/go-macaron/toolbox v0.0.0-20190813233741-94defb8383c6
github.com/go-macaron/session v1.0.4
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14
github.com/gogs/git-module v1.8.6
github.com/gogs/go-gogs-client v0.0.0-20200128182646-c69cb7680fd4
github.com/gogs/git-module v1.8.7
github.com/gogs/go-libravatar v0.0.0-20191106065024-33a75213d0a0
github.com/gogs/minwinsvc v0.0.0-20170301035411-95be6356811a
github.com/google/go-github v17.0.0+incompatible
github.com/google/uuid v1.6.0
github.com/inbucket/html2text v1.0.0
github.com/issue9/identicon v1.2.1
github.com/json-iterator/go v1.1.12
github.com/microcosm-cc/bluemonday v1.0.27
github.com/msteinert/pam v1.2.0
github.com/niklasfasching/go-org v1.9.1
github.com/olekukonko/tablewriter v1.1.3
github.com/pquerna/otp v1.5.0
github.com/prometheus/client_golang v1.23.0
github.com/prometheus/client_golang v1.23.2
github.com/russross/blackfriday v1.6.0
github.com/sergi/go-diff v1.4.0
github.com/sourcegraph/run v0.12.0
github.com/stretchr/testify v1.11.1
@@ -43,18 +47,16 @@ require (
github.com/unknwon/com v1.0.1
github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6
github.com/unknwon/paginater v0.0.0-20170405233947-45e5d631308e
github.com/urfave/cli v1.22.17
github.com/yuin/goldmark v1.7.16
golang.org/x/crypto v0.47.0
golang.org/x/image v0.35.0
golang.org/x/net v0.48.0
golang.org/x/text v0.33.0
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.67.0
github.com/urfave/cli/v3 v3.6.2
github.com/wneessen/go-mail v0.7.2
golang.org/x/crypto v0.49.0
golang.org/x/image v0.38.0
golang.org/x/net v0.51.0
golang.org/x/text v0.35.0
gopkg.in/ini.v1 v1.67.1
gopkg.in/macaron.v1 v1.5.1
gorm.io/driver/mysql v1.5.2
gorm.io/driver/postgres v1.6.0
gorm.io/driver/sqlserver v1.4.1
gorm.io/gorm v1.25.12
unknwon.dev/clog/v2 v2.2.0
xorm.io/builder v0.3.6
@@ -64,57 +66,62 @@ require (
require (
bitbucket.org/creachadair/shell v0.0.7 // indirect
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
charm.land/lipgloss/v2 v2.0.1 // indirect
charm.land/log/v2 v2.0.0 // indirect
filippo.io/edwards25519 v1.1.1 // indirect
github.com/Azure/go-ntlmssp v0.1.1 // indirect
github.com/alecthomas/participle/v2 v2.1.4 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clipperhouse/displaywidth v0.6.2 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.4.2 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 // indirect
github.com/charmbracelet/x/ansi v0.11.6 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/denisenkom/go-mssqldb v0.12.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/djherbis/buffer v1.2.0 // indirect
github.com/djherbis/nio/v3 v3.0.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/getsentry/sentry-go v0.27.0 // indirect
github.com/getsentry/sentry-go v0.46.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-macaron/inject v0.0.0-20200308113650-138e5925c53b // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/google/go-querystring v1.0.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/itchyny/gojq v0.12.11 // indirect
github.com/itchyny/timefmt-go v0.1.5 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.6.0 // indirect
github.com/jackc/pgx/v5 v5.9.2 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/lib/pq v1.10.2 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mattn/go-sqlite3 v1.14.24 // indirect
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 // indirect
github.com/microsoft/go-mssqldb v0.17.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
@@ -123,22 +130,24 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/redis/go-redis/v9 v9.5.5 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.bobheadxi.dev/streamline v1.2.1 // indirect
go.opentelemetry.io/otel v1.11.0 // indirect
go.opentelemetry.io/otel/trace v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/mod v0.31.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e // indirect
gopkg.in/redis.v2 v2.3.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
@@ -147,6 +156,3 @@ require (
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.39.0 // indirect
)
// +heroku goVersion go1.25
// +heroku install ./cmd/gogs
+115 -119
View File
@@ -1,27 +1,32 @@
bitbucket.org/creachadair/shell v0.0.7 h1:Z96pB6DkSb7F3Y3BBnJeOZH2gazyMTWlvecSD4vDqfk=
bitbucket.org/creachadair/shell v0.0.7/go.mod h1:oqtXSSvSYr4624lnnabXHaBsYW6RD80caLi2b3hJk0U=
charm.land/lipgloss/v2 v2.0.1 h1:6Xzrn49+Py1Um5q/wZG1gWgER2+7dUyZ9XMEufqPSys=
charm.land/lipgloss/v2 v2.0.1/go.mod h1:KjPle2Qd3YmvP1KL5OMHiHysGcNwq6u83MUjYkFvEkM=
charm.land/log/v2 v2.0.0 h1:SY3Cey7ipx86/MBXQHwsguOT6X1exT94mmJRdzTNs+s=
charm.land/log/v2 v2.0.0/go.mod h1:c3cZSRqm20qUVVAR1WmS/7ab8bgha3C6G7DjPcaVZz0=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e/go.mod h1:uJEsN4LQpeGYRCjuPXPZBClU7N5pWzGuyF4uqLpE/e0=
gitea.com/lunny/nodb v0.0.0-20200923032308-3238c4655727/go.mod h1:h0OwsgcpJLSYtHcM5+Xciw9OEeuxi6ty4HDiO8C7aIY=
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0/go.mod h1:+6sju8gk8FRmSajX3Oz4G5Gm7P+mbqE9FVaXXFYTkCM=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4=
github.com/Azure/go-ntlmssp v0.1.1 h1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw=
github.com/Azure/go-ntlmssp v0.1.1/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/participle/v2 v2.1.4 h1:W/H79S8Sat/krZ3el6sQMvMaahJ+XcM9WSI2naI7w2U=
github.com/alecthomas/participle/v2 v2.1.4/go.mod h1:8tqVbpTX20Ru4NfYQgZf4mP18eXPTBViyMWiArNEgGI=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
@@ -34,23 +39,36 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 h1:U/lr3Dgy4WK+hNk4tyD+nuGjpVLPEHuJSFXMw11/HPA=
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY=
github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8=
github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 h1:OqDqxQZliC7C8adA7KjelW3OjtAxREfeHkNcd66wpeI=
github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318/go.mod h1:Y6kE2GzHfkyQQVCSL9r2hwokSrIlHGzZG+71+wDYSZI=
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo=
github.com/clipperhouse/displaywidth v0.6.2/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/cockroachdb/errors v1.12.0 h1:d7oCs6vuIMUQRVbi6jWWWEJZahLCfJpnJSVobd1/sUo=
github.com/cockroachdb/errors v1.12.0/go.mod h1:SvzfYNNBshAVbZ8wzNc/UPK3w1vf0dKDUP41ucAIf7g=
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cockroachdb/errors v1.13.0 h1:BoCcJeiP9hpBJDETkX19qi8Tb8So37srSsp3stTaDMQ=
github.com/cockroachdb/errors v1.13.0/go.mod h1:bjxt/4E5+OyuAnacpTIU9rn2mzPu1VlthvHP+xpROq0=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
@@ -61,16 +79,13 @@ github.com/couchbase/gomemcached v0.1.1/go.mod h1:mxliKQxOv84gQ0bJWbI+w9Wxdpt9Hj
github.com/couchbase/goutils v0.0.0-20190315194238-f9d42b11473b/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/couchbaselabs/go-couchbase v0.0.0-20190708161019-23e7ca2ce2b7/go.mod h1:mby/05p8HE5yHEAKiIH/555NoblMs7PtW6NrYshDruc=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 h1:YcpmyvADGYw5LqMnHqSkyIELsHCGF6PkrmM31V8rF7o=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/denisenkom/go-mssqldb v0.12.0 h1:VtrkII767ttSPNRfFekePK3sctr+joXgO58stqQbtUA=
github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU=
github.com/derision-test/go-mockgen/v2 v2.1.1 h1:MXG9rzyvsrDBfa1a1GatvHjCrbmEug3hVt0rSOXipCw=
github.com/derision-test/go-mockgen/v2 v2.1.1/go.mod h1:cDK2Y9IF5roTJgugWV23IvlOJsllhDN5zxRDN+g4cZo=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
@@ -80,8 +95,6 @@ github.com/djherbis/buffer v1.2.0 h1:PH5Dd2ss0C7CRRhQCZ2u7MssF+No9ide8Ye71nPHcrQ
github.com/djherbis/buffer v1.2.0/go.mod h1:fjnebbZjCUpPinBRD+TDwXSOeNQ7fPQWLfGQqiAiUyE=
github.com/djherbis/nio/v3 v3.0.1 h1:6wxhnuppteMa6RHA4L81Dq7ThkZH8SwnDzXDYy95vB4=
github.com/djherbis/nio/v3 v3.0.1/go.mod h1:Ng4h80pbZFMla1yKzm61cF0tqqilXZYrogmWgZxOcmg=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
@@ -93,13 +106,25 @@ github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaB
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/flamego/binding v1.3.0 h1:CPbnSuP0SxT50JR7lK2khTjcQi1oOECqRK7kbOYw91U=
github.com/flamego/binding v1.3.0/go.mod h1:xgm6FEpEKKkF8CQilK2X3MJ5kTjOTnYdz/ooFctDTdc=
github.com/flamego/cache v1.5.1 h1:2B4QhLFV7je0oUMCVKsAGAT+OyDHlXhozOoUffm+O3s=
github.com/flamego/cache v1.5.1/go.mod h1:cTWYm/Ls35KKHo8vwcKgTlJUNXswEhzFWqVCTFzj24s=
github.com/flamego/captcha v1.3.0 h1:CyQivqkiO4zT0nJY2vO0ySdOi85Z7EyESGMXvNQmi5U=
github.com/flamego/captcha v1.3.0/go.mod h1:fCjE5o1cJXQkVJ2aYk7ISIBohfbNy1WxI2A3Ervzyp8=
github.com/flamego/flamego v1.12.0 h1:BS0iY6RytweVvu5j40fQJ53X2ZcUVeuQ8ZSigVkDB9A=
github.com/flamego/flamego v1.12.0/go.mod h1:MM4kNGS7SvJtwUZYb2oGySR+ncdtIvtJHsl8OhH1Ngo=
github.com/flamego/session v1.3.0 h1:mj+fyNnJeM9aNXx2CGKppH5VFFUVHNEkhjObJIVH9hY=
github.com/flamego/session v1.3.0/go.mod h1:x4oNtRuWDnaA2uRylTm3kShbCI3lTWM+dUHuJyeeiZE=
github.com/flamego/validator v1.0.0 h1:ixuWHVgiVGp4pVGtUn/0d6HBjZJbbXfJHDNkxW+rZoY=
github.com/flamego/validator v1.0.0/go.mod h1:POYn0/5iW4sdamdPAYPrzqN6DFC4YaczY0gYY+Pyx5E=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/getsentry/sentry-go v0.46.0 h1:mbdDaarbUdOt9X+dx6kDdntkShLEX3/+KyOsVDTPDj0=
github.com/getsentry/sentry-go v0.46.0/go.mod h1:evVbw2qotNUdYG8KxXbAdjOQWWvWIwKxpjdZZIvcIPw=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
@@ -112,6 +137,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4=
github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -121,8 +148,6 @@ github.com/go-macaron/binding v1.2.0 h1:/A8x8ZVQNTzFO43ch8czTqhc4VzOEPXYU/ELjIyh
github.com/go-macaron/binding v1.2.0/go.mod h1:8pXMCyR9UPsXV02PYGLI+t2Xep/v2OgVuuLTNtCG03c=
github.com/go-macaron/cache v0.0.0-20190810181446-10f7c57e2196 h1:fqWZxyMLF6RVGmjvsZ9FijiU9UlAjuE6nu9RfNBZ+iE=
github.com/go-macaron/cache v0.0.0-20190810181446-10f7c57e2196/go.mod h1:O6fSdaYZbGh4clVMGMGO5k2KbMO0Cz8YdBnPrD0I8dM=
github.com/go-macaron/captcha v0.2.0 h1:d38eYDDF8tdqoM0hJbk+Jb7WQGWlwYNnQwRqLRmSk1Y=
github.com/go-macaron/captcha v0.2.0/go.mod h1:lmhlZnu9cTRGNQEkSh1qZi2IK3HJH4Z1MXkg6ARQKZA=
github.com/go-macaron/csrf v0.0.0-20190812063352-946f6d303a4c h1:kFFz1OpaH3+efG7RA33z+D0piwpA/a3x/Zn2d8z9rfw=
github.com/go-macaron/csrf v0.0.0-20190812063352-946f6d303a4c/go.mod h1:FX53Xq0NNlUj0E5in5J8Dq5nrbdK3ZyDIy6y5VWOiUo=
github.com/go-macaron/gzip v0.0.0-20160222043647-cad1c6580a07 h1:YSIA98PevNf1NtCa/J6cz7gjzpz99WVAOa9Eg0klKps=
@@ -133,15 +158,16 @@ github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191/go.mod h1:VFI2o2
github.com/go-macaron/inject v0.0.0-20200308113650-138e5925c53b h1:/aWj44HoEycE4MDi2HZf4t+XI7hKwZRltZf4ih5tB2c=
github.com/go-macaron/inject v0.0.0-20200308113650-138e5925c53b/go.mod h1:VFI2o2q9kYsC4o7VP1HrEVosiZZTd+MVT3YZx4gqvJw=
github.com/go-macaron/session v0.0.0-20190805070824-1a3cdc6f5659/go.mod h1:tLd0QEudXocQckwcpCq5pCuTCuYc24I0bRJDuRe9OuQ=
github.com/go-macaron/session v1.0.3 h1:YnSfcm24a4HHRnZzBU30FGvoo4kR6vYbTeyTlA1dya4=
github.com/go-macaron/session v1.0.3/go.mod h1:NKoSrKpBFGEgeDtdLr/mnGaxa2LZVOg8/LwZKwPgQr0=
github.com/go-macaron/toolbox v0.0.0-20190813233741-94defb8383c6 h1:x/v1iUWlqXTKVg17ulB0qCgcM2s+eysAbr/dseKLLss=
github.com/go-macaron/toolbox v0.0.0-20190813233741-94defb8383c6/go.mod h1:YFNJ/JT4yLnpuIXTFef30SZkxGHUczjGZGFaZpPcdn0=
github.com/go-macaron/session v1.0.4 h1:fIvtOwdYBsqlb+icre1LvWB7YKnosfoSpaqT1nybh8E=
github.com/go-macaron/session v1.0.4/go.mod h1:NKoSrKpBFGEgeDtdLr/mnGaxa2LZVOg8/LwZKwPgQr0=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
@@ -154,23 +180,14 @@ github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561 h1:aBzukfDxQlCTVS0NBU
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 h1:yXtpJr/LV6PFu4nTLgfjQdcMdzjbqqXMEnHfq0Or6p8=
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14/go.mod h1:jPoNZLWDAqA5N3G5amEoiNbhVrmM+ZQEcnQvNQ2KaZk=
github.com/gogs/git-module v1.8.6 h1:4Io9vWZYQyIjdIPxfKgeYZXnDKNgydc6OZTxII5xCH4=
github.com/gogs/git-module v1.8.6/go.mod h1:IiMSJqi8XH62Kjqjt5Rw8IawSo+DHfM2dDjkSzWLjhs=
github.com/gogs/go-gogs-client v0.0.0-20200128182646-c69cb7680fd4 h1:C7NryI/RQhsIWwC2bHN601P1wJKeuQ6U/UCOYTn3Cic=
github.com/gogs/go-gogs-client v0.0.0-20200128182646-c69cb7680fd4/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
github.com/gogs/git-module v1.8.7 h1:GDyfzB1Z8ytld3LajTfUE4PuIcGcuCHpWB6j8/oD7Tk=
github.com/gogs/git-module v1.8.7/go.mod h1:IiMSJqi8XH62Kjqjt5Rw8IawSo+DHfM2dDjkSzWLjhs=
github.com/gogs/go-libravatar v0.0.0-20191106065024-33a75213d0a0 h1:K02vod+sn3M1OOkdqi2tPxN2+xESK4qyITVQ3JkGEv4=
github.com/gogs/go-libravatar v0.0.0-20191106065024-33a75213d0a0/go.mod h1:Zas3BtO88pk1cwUfEYlvnl/CRwh0ybDxRWSwRjG8I3w=
github.com/gogs/minwinsvc v0.0.0-20170301035411-95be6356811a h1:8DZwxETOVWIinYxDK+i6L+rMb7eGATGaakD6ZucfHVk=
github.com/gogs/minwinsvc v0.0.0-20170301035411-95be6356811a/go.mod h1:TUIZ+29jodWQ8Gk6Pvtg4E09aMsc3C/VLZiVYfUhWQU=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@@ -200,14 +217,11 @@ github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4r
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
@@ -245,8 +259,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
@@ -263,11 +277,8 @@ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZ
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
@@ -289,10 +300,14 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ=
github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af/go.mod h1:Cqz6pqow14VObJ7peltM+2n3PWOz7yTrfUuGbVFkzN0=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -313,17 +328,10 @@ github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 h1:YocNLcTBdEd
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
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 v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE=
github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/msteinert/pam v1.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE=
github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -366,9 +374,6 @@ github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrap
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -380,27 +385,31 @@ github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/redis/go-redis/v9 v9.5.5 h1:51VEyMF8eOO+NUHFm8fpg+IOc1xFuFOhxs3R+kPu1FM=
github.com/redis/go-redis/v9 v9.5.5/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
@@ -438,7 +447,6 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
@@ -451,12 +459,14 @@ github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 h1:sRrkJEHtNoaSvyXMbR
github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ=
github.com/unknwon/paginater v0.0.0-20170405233947-45e5d631308e h1:Qf3QQl/zmEbWDajFEiisbKN83hLY+eq2MhbA0I1/two=
github.com/unknwon/paginater v0.0.0-20170405233947-45e5d631308e/go.mod h1:TBwoao3Q4Eb/cp+dHbXDfRTrZSsj/k7kLr2j1oWRWC0=
github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
github.com/urfave/cli/v3 v3.6.2 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8=
github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/wneessen/go-mail v0.7.2 h1:xxPnhZ6IZLSgxShebmZ6DPKh1b6OJcoHfzy7UjOkzS8=
github.com/wneessen/go-mail v0.7.2/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.bobheadxi.dev/streamline v1.2.1 h1:IqKSA1TbeuDqCzYNAwtlh8sqf3tsQus8XgJdkCWFT8c=
@@ -470,6 +480,8 @@ go.opentelemetry.io/otel/trace v1.11.0 h1:20U/Vj42SX+mASlXLmSGBg6jpI1jQtv682lZtT
go.opentelemetry.io/otel/trace v1.11.0/go.mod h1:nyYjis9jy0gytE9LXGU+/m1sHTKbRY0fX0hulNNDP1U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -479,22 +491,21 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I=
golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk=
golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE=
golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -509,14 +520,11 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -526,8 +534,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -548,28 +556,24 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -583,8 +587,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -608,11 +612,9 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e h1:wGA78yza6bu/mWcc4QfBuIEHEtc06xdiU0X8sY36yUU=
gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e/go.mod h1:xsQCaysVCudhrYTfzYWe577fCe7Ceci+6qjO2Rdc0Z4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -621,12 +623,10 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k=
gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
gopkg.in/macaron.v1 v1.3.4/go.mod h1:/RoHTdC8ALpyJ3+QR36mKjwnT1F1dyYtsGM9Ate6ZFI=
gopkg.in/macaron.v1 v1.3.5/go.mod h1:uMZCFccv9yr5TipIalVOyAyZQuOH3OkmXvgcWwhJuP4=
gopkg.in/macaron.v1 v1.4.0/go.mod h1:uMZCFccv9yr5TipIalVOyAyZQuOH3OkmXvgcWwhJuP4=
@@ -639,7 +639,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
@@ -652,9 +651,6 @@ gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs=
gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8=
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0=
gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig=
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
+2 -2
View File
@@ -5,7 +5,7 @@ import (
"gopkg.in/macaron.v1"
"gogs.io/gogs/internal/authutil"
"gogs.io/gogs/internal/authx"
"gogs.io/gogs/internal/conf"
)
@@ -20,7 +20,7 @@ func MetricsFilter() macaron.Handler {
return
}
username, password := authutil.DecodeBasic(r.Header)
username, password := authx.DecodeBasic(r.Header)
if username != conf.Prometheus.BasicAuthUsername || password != conf.Prometheus.BasicAuthPassword {
w.WriteHeader(http.StatusForbidden)
return
+3 -3
View File
@@ -5,7 +5,7 @@ import (
"github.com/cockroachdb/errors"
"gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/errx"
)
type Type int
@@ -34,10 +34,10 @@ func Name(typ Type) string {
}[typ]
}
var _ errutil.NotFound = (*ErrBadCredentials)(nil)
var _ errx.NotFound = (*ErrBadCredentials)(nil)
type ErrBadCredentials struct {
Args errutil.Args
Args errx.Args
}
// IsErrBadCredentials returns true if the underlying error has the type
+15 -2
View File
@@ -2,12 +2,19 @@ package smtp
import (
"crypto/tls"
"fmt"
"net"
"net/smtp"
"strconv"
"time"
"github.com/cockroachdb/errors"
)
// dialTimeout bounds how long the SMTP authentication flow waits on the
// underlying TCP connect. Without it, an unreachable or misspelled host hangs
// the sign-in request until the OS-level connect timeout (minutes).
const dialTimeout = 10 * time.Second
// Config contains configuration for SMTP authentication.
//
// ⚠️ WARNING: Change to the field name must preserve the INI key name for backward compatibility.
@@ -21,10 +28,16 @@ type Config struct {
}
func (c *Config) doAuth(auth smtp.Auth) error {
client, err := smtp.Dial(fmt.Sprintf("%s:%d", c.Host, c.Port))
addr := net.JoinHostPort(c.Host, strconv.Itoa(c.Port))
conn, err := net.DialTimeout("tcp", addr, dialTimeout)
if err != nil {
return err
}
client, err := smtp.NewClient(conn, c.Host)
if err != nil {
_ = conn.Close()
return err
}
defer client.Close()
if err = client.Hello("gogs"); err != nil {
@@ -1,4 +1,4 @@
package authutil
package authx
import (
"encoding/base64"
@@ -1,4 +1,4 @@
package authutil
package authx
import (
"net/http"
+7 -7
View File
@@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"gogs.io/gogs/internal/testutil"
"gogs.io/gogs/internal/testx"
)
func TestIsProdMode(t *testing.T) {
@@ -36,7 +36,7 @@ func TestIsProdMode(t *testing.T) {
}
func TestWorkDirHelper(_ *testing.T) {
if !testutil.WantHelperProcess() {
if !testx.WantHelperProcess() {
return
}
@@ -53,7 +53,7 @@ func TestWorkDir(t *testing.T) {
}
for _, test := range tests {
t.Run("", func(t *testing.T) {
out, err := testutil.Exec("TestWorkDirHelper", test.env)
out, err := testx.Exec("TestWorkDirHelper", test.env)
if err != nil {
t.Fatal(err)
}
@@ -64,7 +64,7 @@ func TestWorkDir(t *testing.T) {
}
func TestCustomDirHelper(_ *testing.T) {
if !testutil.WantHelperProcess() {
if !testx.WantHelperProcess() {
return
}
@@ -81,7 +81,7 @@ func TestCustomDir(t *testing.T) {
}
for _, test := range tests {
t.Run("", func(t *testing.T) {
out, err := testutil.Exec("TestCustomDirHelper", test.env)
out, err := testx.Exec("TestCustomDirHelper", test.env)
if err != nil {
t.Fatal(err)
}
@@ -92,7 +92,7 @@ func TestCustomDir(t *testing.T) {
}
func TestHomeDirHelper(_ *testing.T) {
if !testutil.WantHelperProcess() {
if !testx.WantHelperProcess() {
return
}
@@ -111,7 +111,7 @@ func TestHomeDir(t *testing.T) {
}
for _, test := range tests {
t.Run("", func(t *testing.T) {
out, err := testutil.Exec("TestHomeDirHelper", test.envs...)
out, err := testx.Exec("TestHomeDirHelper", test.envs...)
if err != nil {
t.Fatal(err)
}
+35 -6
View File
@@ -1,6 +1,7 @@
package conf
import (
"net"
"net/mail"
"net/url"
"os"
@@ -10,7 +11,7 @@ import (
"time"
"github.com/cockroachdb/errors"
_ "github.com/go-macaron/cache/memcache"
"github.com/fatih/color"
_ "github.com/go-macaron/cache/redis"
_ "github.com/go-macaron/session/redis"
"github.com/gogs/go-libravatar"
@@ -18,11 +19,18 @@ import (
log "unknwon.dev/clog/v2"
"gogs.io/gogs/conf"
"gogs.io/gogs/internal/osutil"
"gogs.io/gogs/internal/semverutil"
"gogs.io/gogs/internal/osx"
"gogs.io/gogs/internal/semverx"
)
func init() {
// fatih/color disables ANSI codes when stdout is not a TTY, which is the
// case under process managers like moon. Honor TTY_FORCE so callers can
// opt back in without a real terminal.
if os.Getenv("TTY_FORCE") != "" {
color.NoColor = false
}
// Initialize the primary logger until logging service is up.
err := log.NewConsole()
if err != nil {
@@ -66,7 +74,7 @@ func Init(customConf string) error {
}
CustomConf = customConf
if osutil.IsFile(customConf) {
if osx.IsFile(customConf) {
if err = File.Append(customConf); err != nil {
return errors.Wrapf(err, "append %q", customConf)
}
@@ -141,7 +149,7 @@ func Init(customConf string) error {
return errors.Wrap(err, "get OpenSSH version")
}
if IsWindowsRuntime() || semverutil.Compare(sshVersion, "<", "5.1") {
if IsWindowsRuntime() || semverx.Compare(sshVersion, "<", "5.1") {
if !HookMode {
log.Warn(`SSH minimum key size check is forced to be disabled because server is not eligible:
1. Windows server
@@ -221,6 +229,26 @@ func Init(customConf string) error {
if err = File.Section("auth").MapTo(&Auth); err != nil {
return errors.Wrap(err, "mapping [auth] section")
}
// Reset before re-parsing so repeated Init calls (e.g. via the web installer)
// do not carry over CIDRs from a previous configuration.
Auth.TrustedProxyCIDRs = nil
for _, raw := range Auth.TrustedProxyIPs {
// Allow bare IPs as a convenience by promoting them to single-host CIDRs.
if !strings.Contains(raw, "/") {
if ip := net.ParseIP(raw); ip != nil {
if ip.To4() != nil {
raw += "/32"
} else {
raw += "/128"
}
}
}
_, cidr, err := net.ParseCIDR(raw)
if err != nil {
return errors.Wrapf(err, "parse trusted proxy CIDR %q", raw)
}
Auth.TrustedProxyCIDRs = append(Auth.TrustedProxyCIDRs, cidr)
}
// *************************
// ----- User settings -----
@@ -346,6 +374,7 @@ func Init(customConf string) error {
return errors.Wrap(err, "mapping [lfs] section")
}
LFS.ObjectsPath = ensureAbs(LFS.ObjectsPath)
LFS.ObjectsTempPath = ensureAbs(LFS.ObjectsTempPath)
handleDeprecated()
if !HookMode {
@@ -382,7 +411,7 @@ func Init(customConf string) error {
return errors.Wrap(err, "mapping [other] section")
}
HasRobotsTxt = osutil.IsFile(filepath.Join(CustomDir(), "robots.txt"))
HasRobotsTxt = osx.IsFile(filepath.Join(CustomDir(), "robots.txt"))
return nil
}
+2 -2
View File
@@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/assert"
"gopkg.in/ini.v1"
"gogs.io/gogs/internal/testutil"
"gogs.io/gogs/internal/testx"
)
func TestInit(t *testing.T) {
@@ -55,5 +55,5 @@ func TestInit(t *testing.T) {
t.Fatal(err)
}
testutil.AssertGolden(t, filepath.Join("testdata", "TestInit.golden.ini"), testutil.Update("TestInit"), buf.String())
testx.AssertGolden(t, filepath.Join("testdata", "TestInit.golden.ini"), testx.Update("TestInit"), buf.String())
}
+18 -16
View File
@@ -2,6 +2,7 @@ package conf
import (
"fmt"
"net"
"net/url"
"os"
"time"
@@ -39,8 +40,6 @@ var (
InstallLock bool
SecretKey string
LoginRememberDays int
CookieRememberName string
CookieUsername string
CookieSecure bool
EnableLoginStatusCookie bool
LoginStatusCookieName string
@@ -56,7 +55,6 @@ var (
User string
Password string
DisableHELO bool `ini:"DISABLE_HELO"`
HELOHostname string `ini:"HELO_HOSTNAME"`
SkipVerify bool
@@ -87,13 +85,6 @@ var (
CSRFCookieName string `ini:"CSRF_COOKIE_NAME"`
}
// Cache settings
Cache struct {
Adapter string
Interval int
Host string
}
// HTTP settings
HTTP struct {
AccessControlAllowOrigin string
@@ -222,7 +213,6 @@ var (
// Other settings
Other struct {
ShowFooterBranding bool
ShowFooterTemplateLoadTime bool
}
@@ -230,6 +220,14 @@ var (
HasRobotsTxt bool
)
type CacheOptions struct {
Adapter string
Interval int
Host string
}
var Cache CacheOptions
type AppOpts struct {
// ⚠️ WARNING: Should only be set by the main package (i.e. "cmd/gogs/main.go").
Version string `ini:"-"`
@@ -253,7 +251,11 @@ type AuthOpts struct {
EnableReverseProxyAuthentication bool
EnableReverseProxyAutoRegistration bool
ReverseProxyAuthenticationHeader string
CustomLogoutURL string `ini:"CUSTOM_LOGOUT_URL"`
TrustedProxyIPs []string `ini:"TRUSTED_PROXY_IPS"`
CustomLogoutURL string `ini:"CUSTOM_LOGOUT_URL"`
// Derived from other static values
TrustedProxyCIDRs []*net.IPNet `ini:"-"` // Parsed CIDR form of TrustedProxyIPs.
}
// Authentication settings
@@ -264,7 +266,7 @@ type ServerOpts struct {
Domain string
Protocol string
HTTPAddr string `ini:"HTTP_ADDR"`
HTTPPort string `ini:"HTTP_PORT"`
HTTPPort int `ini:"HTTP_PORT"`
CertFile string
KeyFile string
TLSMinVersion string `ini:"TLS_MIN_VERSION"`
@@ -361,8 +363,9 @@ type DatabaseOpts struct {
var Database DatabaseOpts
type LFSOpts struct {
Storage string
ObjectsPath string
Storage string
ObjectsPath string
ObjectsTempPath string
}
// LFS settings
@@ -510,7 +513,6 @@ var (
UseSQLite3 bool
UseMySQL bool
UsePostgreSQL bool
UseMSSQL bool
)
// UsersAvatarPathPrefix is the path prefix to user avatars.
-1
View File
@@ -48,7 +48,6 @@ func TestCheckInvalidOptions(t *testing.T) {
_, _ = cfg.Section("server").NewKey("LANDING_PAGE", "true")
_, _ = cfg.Section("database").NewKey("DB_TYPE", "true")
_, _ = cfg.Section("database").NewKey("PASSWD", "true")
_, _ = cfg.Section("other").NewKey("SHOW_FOOTER_BRANDING", "true")
_, _ = cfg.Section("other").NewKey("SHOW_FOOTER_TEMPLATE_LOAD_TIME", "true")
_, _ = cfg.Section("email").NewKey("ENABLED", "true")
_, _ = cfg.Section("server").NewKey("NONEXISTENT_OPTION", "true")
+1 -3
View File
@@ -74,8 +74,6 @@ MAX_IDLE_CONNS=30
INSTALL_LOCK=false
SECRET_KEY=`!#@FDEWREWR&*(`
LOGIN_REMEMBER_DAYS=7
COOKIE_REMEMBER_NAME=gogs_incredible
COOKIE_USERNAME=gogs_awesome
COOKIE_SECURE=false
ENABLE_LOGIN_STATUS_COOKIE=false
LOGIN_STATUS_COOKIE_NAME=login_status
@@ -88,7 +86,6 @@ HOST=smtp.mailgun.org:587
FROM=noreply@gogs.localhost
USER=noreply@gogs.localhost
PASSWORD=87654321
DISABLE_HELO=false
HELO_HOSTNAME=
SKIP_VERIFY=false
USE_CERTIFICATE=false
@@ -107,6 +104,7 @@ ENABLE_REGISTRATION_CAPTCHA=true
ENABLE_REVERSE_PROXY_AUTHENTICATION=false
ENABLE_REVERSE_PROXY_AUTO_REGISTRATION=false
REVERSE_PROXY_AUTHENTICATION_HEADER=X-FORWARDED-FOR
TRUSTED_PROXY_IPS=127.0.0.0/8,::1/128
CUSTOM_LOGOUT_URL=
[user]
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"github.com/cockroachdb/errors"
"gogs.io/gogs/internal/osutil"
"gogs.io/gogs/internal/osx"
"gogs.io/gogs/internal/process"
)
@@ -44,6 +44,6 @@ func CheckRunUser(runUser string) (string, bool) {
return "", true
}
currentUser := osutil.CurrentUsername()
currentUser := osx.CurrentUsername()
return currentUser, runUser == currentUser
}
+2 -2
View File
@@ -11,7 +11,7 @@ import (
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/errx"
)
type APIContext struct {
@@ -62,7 +62,7 @@ func (c *APIContext) Errorf(err error, format string, args ...any) {
// is about not found. It responses with 404 status code for not found error,
// or error context description for logging purpose of 500 server error.
func (c *APIContext) NotFoundOrError(err error, msg string) {
if errutil.IsNotFound(err) {
if errx.IsNotFound(err) {
c.NotFound()
return
}
+58 -24
View File
@@ -2,6 +2,7 @@ package context
import (
"context"
"net"
"net/http"
"net/url"
"strings"
@@ -70,24 +71,22 @@ func Toggle(options *ToggleOptions) macaron.Handler {
return
}
if isWebPath(c.Req.URL.Path) {
c.ServeWeb()
return
}
c.SetCookie("redirect_to", url.QueryEscape(conf.Server.Subpath+c.Req.RequestURI), 0, conf.Server.Subpath)
c.RedirectSubpath("/user/login")
c.RedirectSubpath("/user/sign-in")
return
} else if !c.User.IsActive && conf.Auth.RequireEmailConfirmation {
c.Title("auth.active_your_account")
c.Success("user/auth/activate")
// Inactive users get bounced to the React activation page, which
// is responsible for offering a resend and showing status.
c.RedirectSubpath("/user/activate")
return
}
}
// Redirect to log in page if auto-signin info is provided and has not signed in.
if !options.SignOutRequired && !c.IsLogged && !isAPIPath(c.Req.URL.Path) &&
len(c.GetCookie(conf.Security.CookieUsername)) > 0 {
c.SetCookie("redirect_to", url.QueryEscape(conf.Server.Subpath+c.Req.RequestURI), 0, conf.Server.Subpath)
c.RedirectSubpath("/user/login")
return
}
if options.AdminRequired {
if !c.User.IsAdmin {
c.Status(http.StatusForbidden)
@@ -102,6 +101,21 @@ func isAPIPath(url string) bool {
return strings.HasPrefix(url, "/api/")
}
func isWebPath(p string) bool {
p = strings.TrimPrefix(p, conf.Server.Subpath)
switch {
case p == "/user/sign-in",
p == "/user/mfa",
strings.HasPrefix(p, "/assets/"),
strings.HasPrefix(p, "/src/"),
strings.HasPrefix(p, "/node_modules/"),
strings.HasPrefix(p, "/@"),
strings.HasPrefix(p, "/img/"):
return true
}
return false
}
type AuthStore interface {
// GetAccessTokenBySHA1 returns the access token with given SHA1. It returns
// database.ErrAccessTokenNotExist when not found.
@@ -146,18 +160,12 @@ func authenticatedUserID(store AuthStore, c *macaron.Context, sess session.Store
// Check access token.
if isAPIPath(c.Req.URL.Path) {
tokenSHA := c.Query("token")
if len(tokenSHA) <= 0 {
tokenSHA = c.Query("access_token")
}
if tokenSHA == "" {
// Well, check with header again.
auHead := c.Req.Header.Get("Authorization")
if len(auHead) > 0 {
auths := strings.Fields(auHead)
if len(auths) == 2 && auths[0] == "token" {
tokenSHA = auths[1]
}
var tokenSHA string
auHead := c.Req.Header.Get("Authorization")
if auHead != "" {
auths := strings.Fields(auHead)
if len(auths) == 2 && auths[0] == "token" {
tokenSHA = auths[1]
}
}
@@ -204,7 +212,7 @@ func authenticatedUser(store AuthStore, ctx *macaron.Context, sess session.Store
uid, isTokenAuth := authenticatedUserID(store, ctx, sess)
if uid <= 0 {
if conf.Auth.EnableReverseProxyAuthentication {
if conf.Auth.EnableReverseProxyAuthentication && isRequestFromTrustedProxy(ctx.Req.Request) {
webAuthUser := ctx.Req.Header.Get(conf.Auth.ReverseProxyAuthenticationHeader)
if len(webAuthUser) > 0 {
user, err := store.GetUserByUsername(ctx.Req.Context(), webAuthUser)
@@ -263,6 +271,32 @@ func authenticatedUser(store AuthStore, ctx *macaron.Context, sess session.Store
return u, false, isTokenAuth
}
// isRequestFromTrustedProxy reports whether the request's immediate remote
// address falls within one of the configured trusted proxy CIDR ranges. The
// reverse proxy authentication header is only honored for such requests so an
// attacker reaching Gogs directly cannot forge it.
func isRequestFromTrustedProxy(req *http.Request) bool {
host, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
return false
}
ip := net.ParseIP(host)
if ip == nil {
return false
}
// Normalize IPv4-mapped IPv6 (e.g. "::ffff:127.0.0.1" on dual-stack listeners)
// to its IPv4 form so it matches IPv4 CIDRs like 127.0.0.0/8.
if v4 := ip.To4(); v4 != nil {
ip = v4
}
for _, cidr := range conf.Auth.TrustedProxyCIDRs {
if cidr.Contains(ip) {
return true
}
}
return false
}
// AuthenticateByToken attempts to authenticate a user by the given access
// token. It returns database.ErrAccessTokenNotExist when the access token does not
// exist.
+48
View File
@@ -0,0 +1,48 @@
package context
import (
"net"
"net/http"
"testing"
"github.com/stretchr/testify/require"
"gogs.io/gogs/internal/conf"
)
func TestIsRequestFromTrustedProxy(t *testing.T) {
mustCIDR := func(s string) *net.IPNet {
_, n, err := net.ParseCIDR(s)
require.NoError(t, err)
return n
}
original := conf.Auth.TrustedProxyCIDRs
t.Cleanup(func() { conf.Auth.TrustedProxyCIDRs = original })
conf.Auth.TrustedProxyCIDRs = []*net.IPNet{
mustCIDR("127.0.0.0/8"),
mustCIDR("::1/128"),
mustCIDR("10.1.0.0/16"),
}
tests := []struct {
name string
remoteAddr string
want bool
}{
{name: "loopback IPv4 with port", remoteAddr: "127.0.0.1:54321", want: true},
{name: "loopback IPv6 with port", remoteAddr: "[::1]:54321", want: true},
{name: "within configured CIDR", remoteAddr: "10.1.2.3:8080", want: true},
{name: "outside configured CIDR", remoteAddr: "203.0.113.5:443", want: false},
{name: "IPv4-mapped IPv6 matches IPv4 CIDR", remoteAddr: "[::ffff:127.0.0.1]:54321", want: true},
{name: "remote without port", remoteAddr: "127.0.0.1", want: false},
{name: "unparseable remote", remoteAddr: "not-an-ip", want: false},
{name: "empty remote", remoteAddr: "", want: false},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
req := &http.Request{RemoteAddr: tc.remoteAddr}
require.Equal(t, tc.want, isRequestFromTrustedProxy(req))
})
}
}
+62 -14
View File
@@ -1,6 +1,7 @@
package context
import (
stdctx "context"
"fmt"
"io"
"net/http"
@@ -16,7 +17,7 @@ import (
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/database"
"gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/errx"
"gogs.io/gogs/internal/form"
"gogs.io/gogs/internal/lazyregexp"
"gogs.io/gogs/internal/template"
@@ -38,6 +39,8 @@ type Context struct {
Repo *Repository
Org *Organization
webHandler http.Handler
}
// RawTitle sets the "Title" field in template data.
@@ -156,10 +159,54 @@ func (c *Context) RenderWithErr(msg string, status int, tpl string, f any) {
c.HTML(status, tpl)
}
// NotFound renders the 404 page.
// WebContext carries per-request inputs into the web handler so it can
// render the React shell. Fields are read by helpers like WebContextFrom.
type WebContext struct {
Lang string
SubURL string
StatusCode int
}
// WebContextKey is the request context key for WebContext values. Exported
// so callers outside this package (e.g. the web NotFound handler) can attach
// a WebContext when the request bypasses Contexter.
type WebContextKey struct{}
// WebContextFrom returns the WebContext attached to r, or a zero value with
// sensible defaults when nothing was attached.
func WebContextFrom(r *http.Request) WebContext {
wr, ok := r.Context().Value(WebContextKey{}).(WebContext)
if !ok {
return WebContext{Lang: "en-US"}
}
if wr.Lang == "" {
wr.Lang = "en-US"
}
return wr
}
// NotFound renders the React 404 page through the web handler with a 404
// status.
func (c *Context) NotFound() {
c.Title("status.page_not_found")
c.HTML(http.StatusNotFound, fmt.Sprintf("status/%d", http.StatusNotFound))
c.serveWeb(WebContext{
Lang: c.Language(),
SubURL: conf.Server.Subpath,
StatusCode: http.StatusNotFound,
})
}
// ServeWeb delegates the current request to the web handler. The web frontend
// decides what to render based on the request path.
func (c *Context) ServeWeb() {
c.serveWeb(WebContext{
Lang: c.Language(),
SubURL: conf.Server.Subpath,
})
}
func (c *Context) serveWeb(wr WebContext) {
ctx := stdctx.WithValue(c.Req.Context(), WebContextKey{}, wr)
c.webHandler.ServeHTTP(c.Resp, c.Req.WithContext(ctx))
}
// Error renders the 500 page.
@@ -182,7 +229,7 @@ func (c *Context) Errorf(err error, format string, args ...any) {
// NotFoundOrError responses with 404 page for not found error and 500 page otherwise.
func (c *Context) NotFoundOrError(err error, msg string) {
if errutil.IsNotFound(err) {
if errx.IsNotFound(err) {
c.NotFound()
return
}
@@ -221,16 +268,18 @@ func (c *Context) ServeContent(name string, r io.ReadSeeker, params ...any) {
// https://github.com/go-macaron/csrf/blob/5d38f39de352972063d1ef026fc477283841bb9b/csrf.go#L148.
var csrfTokenExcludePattern = lazyregexp.New(`[^a-zA-Z0-9-_].*`)
// Contexter initializes a classic context for a request.
func Contexter(store Store) macaron.Handler {
// Contexter initializes a classic context for a request. webHandler
// receives 404 responses so the React frontend can render its own 404 page.
func Contexter(store Store, webHandler http.Handler) macaron.Handler {
return func(ctx *macaron.Context, l i18n.Locale, cache cache.Cache, sess session.Store, f *session.Flash, x csrf.CSRF) {
c := &Context{
Context: ctx,
Cache: cache,
csrf: x,
Flash: f,
Session: sess,
Link: conf.Server.Subpath + strings.TrimSuffix(ctx.Req.URL.Path, "/"),
Context: ctx,
Cache: cache,
csrf: x,
Flash: f,
Session: sess,
Link: conf.Server.Subpath + strings.TrimSuffix(ctx.Req.URL.Path, "/"),
webHandler: webHandler,
Repo: &Repository{
PullRequest: &PullRequest{},
},
@@ -279,7 +328,6 @@ func Contexter(store Store) macaron.Handler {
log.Trace("CSRF Token: %v", c.Data["CSRFToken"])
c.Data["ShowRegistrationButton"] = !conf.Auth.DisableRegistration
c.Data["ShowFooterBranding"] = conf.Other.ShowFooterBranding
c.renderNoticeBanner()
+2 -2
View File
@@ -10,7 +10,7 @@ import (
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/database"
"gogs.io/gogs/internal/repoutil"
"gogs.io/gogs/internal/repox"
)
// ServeGoGet does quick responses for appropriate go-get meta with status OK
@@ -53,7 +53,7 @@ func ServeGoGet() macaron.Handler {
`,
map[string]string{
"GoGetImport": path.Join(conf.Server.URL.Host, conf.Server.Subpath, ownerName, repoName),
"CloneLink": repoutil.HTTPSCloneURL(ownerName, repoName),
"CloneLink": repox.HTTPSCloneURL(ownerName, repoName),
"GoDocDirectory": prefix + "{/dir}",
"GoDocFile": prefix + "{/dir}/{file}#L{line}",
"InsecureFlag": insecureFlag,
+3 -3
View File
@@ -8,7 +8,7 @@ import (
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/markup"
"gogs.io/gogs/internal/osutil"
"gogs.io/gogs/internal/osx"
"gogs.io/gogs/internal/tool"
)
@@ -16,7 +16,7 @@ import (
// on all pages.
func (c *Context) renderNoticeBanner() {
fpath := filepath.Join(conf.CustomDir(), "notice", "banner.md")
if !osutil.Exist(fpath) {
if !osx.Exist(fpath) {
return
}
@@ -54,5 +54,5 @@ func (c *Context) renderNoticeBanner() {
return
}
c.Data["ServerNotice"] = string(markup.SanitizeBytes(markup.RawMarkdown(buf, "")))
c.Data["ServerNotice"] = string(markup.RawMarkdown(buf, ""))
}
+2 -2
View File
@@ -14,7 +14,7 @@ import (
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/database"
"gogs.io/gogs/internal/repoutil"
"gogs.io/gogs/internal/repox"
)
type PullRequest struct {
@@ -40,7 +40,7 @@ type Repository struct {
TreePath string
CommitID string
RepoLink string
CloneLink repoutil.CloneLink
CloneLink repox.CloneLink
CommitsCount int64
Mirror *database.Mirror
@@ -1,4 +1,4 @@
package cryptoutil
package cryptox
import (
"crypto/aes"
@@ -1,4 +1,4 @@
package cryptoutil
package cryptox
import (
"crypto/rand"
@@ -1,4 +1,4 @@
package cryptoutil
package cryptox
import (
"crypto/md5"
@@ -1,4 +1,4 @@
package cryptoutil
package cryptox
import (
"testing"
@@ -1,4 +1,4 @@
package cryptoutil
package cryptox
import (
"crypto/sha1"
@@ -1,4 +1,4 @@
package cryptoutil
package cryptox
import (
"testing"
+11 -11
View File
@@ -9,8 +9,8 @@ import (
"github.com/google/uuid"
"gorm.io/gorm"
"gogs.io/gogs/internal/cryptoutil"
"gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/cryptox"
"gogs.io/gogs/internal/errx"
)
// AccessToken is a personal access token.
@@ -58,7 +58,7 @@ func newAccessTokensStore(db *gorm.DB) *AccessTokensStore {
}
type ErrAccessTokenAlreadyExist struct {
args errutil.Args
args errx.Args
}
func IsErrAccessTokenAlreadyExist(err error) bool {
@@ -75,13 +75,13 @@ func (err ErrAccessTokenAlreadyExist) Error() string {
func (s *AccessTokensStore) Create(ctx context.Context, userID int64, name string) (*AccessToken, error) {
err := s.db.WithContext(ctx).Where("uid = ? AND name = ?", userID, name).First(new(AccessToken)).Error
if err == nil {
return nil, ErrAccessTokenAlreadyExist{args: errutil.Args{"userID": userID, "name": name}}
return nil, ErrAccessTokenAlreadyExist{args: errx.Args{"userID": userID, "name": name}}
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
token := cryptoutil.SHA1(uuid.New().String())
sha256 := cryptoutil.SHA256(token)
token := cryptox.SHA1(uuid.New().String())
sha256 := cryptox.SHA256(token)
accessToken := &AccessToken{
UserID: userID,
@@ -106,10 +106,10 @@ func (s *AccessTokensStore) DeleteByID(ctx context.Context, userID, id int64) er
return s.db.WithContext(ctx).Where("id = ? AND uid = ?", id, userID).Delete(new(AccessToken)).Error
}
var _ errutil.NotFound = (*ErrAccessTokenNotExist)(nil)
var _ errx.NotFound = (*ErrAccessTokenNotExist)(nil)
type ErrAccessTokenNotExist struct {
args errutil.Args
args errx.Args
}
// IsErrAccessTokenNotExist returns true if the underlying error has the type
@@ -131,14 +131,14 @@ func (ErrAccessTokenNotExist) NotFound() bool {
func (s *AccessTokensStore) GetBySHA1(ctx context.Context, sha1 string) (*AccessToken, error) {
// No need to waste a query for an empty SHA1.
if sha1 == "" {
return nil, ErrAccessTokenNotExist{args: errutil.Args{"sha": sha1}}
return nil, ErrAccessTokenNotExist{args: errx.Args{"sha": sha1}}
}
sha256 := cryptoutil.SHA256(sha1)
sha256 := cryptox.SHA256(sha1)
token := new(AccessToken)
err := s.db.WithContext(ctx).Where("sha256 = ?", sha256).First(token).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrAccessTokenNotExist{args: errutil.Args{"sha": sha1}}
return nil, ErrAccessTokenNotExist{args: errx.Args{"sha": sha1}}
} else if err != nil {
return nil, err
}
+4 -4
View File
@@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/require"
"gorm.io/gorm"
"gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/errx"
)
func TestAccessToken_BeforeCreate(t *testing.T) {
@@ -138,7 +138,7 @@ func accessTokensCreate(t *testing.T, ctx context.Context, s *AccessTokensStore)
// Try create second access token with same name should fail
_, err = s.Create(ctx, token.UserID, token.Name)
wantErr := ErrAccessTokenAlreadyExist{
args: errutil.Args{
args: errx.Args{
"userID": token.UserID,
"name": token.Name,
},
@@ -166,7 +166,7 @@ func accessTokensDeleteByID(t *testing.T, ctx context.Context, s *AccessTokensSt
// We should get token not found error
_, err = s.GetBySHA1(ctx, token.Sha1)
wantErr := ErrAccessTokenNotExist{
args: errutil.Args{
args: errx.Args{
"sha": token.Sha1,
},
}
@@ -185,7 +185,7 @@ func accessTokensGetBySHA(t *testing.T, ctx context.Context, s *AccessTokensStor
// Try to get a non-existent token
_, err = s.GetBySHA1(ctx, "bad_sha")
wantErr := ErrAccessTokenNotExist{
args: errutil.Args{
args: errx.Args{
"sha": "bad_sha",
},
}
+30 -30
View File
@@ -2,6 +2,7 @@ package database
import (
"context"
"encoding/json"
"fmt"
"path"
"strconv"
@@ -11,16 +12,15 @@ import (
"github.com/cockroachdb/errors"
"github.com/gogs/git-module"
api "github.com/gogs/go-gogs-client"
jsoniter "github.com/json-iterator/go"
"gorm.io/gorm"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/lazyregexp"
"gogs.io/gogs/internal/repoutil"
"gogs.io/gogs/internal/strutil"
"gogs.io/gogs/internal/testutil"
"gogs.io/gogs/internal/repox"
apiv1types "gogs.io/gogs/internal/route/api/v1/types"
"gogs.io/gogs/internal/strx"
"gogs.io/gogs/internal/testx"
"gogs.io/gogs/internal/tool"
)
@@ -218,19 +218,19 @@ func (s *ActionsStore) MirrorSyncPush(ctx context.Context, opts MirrorSyncPushOp
apiCommits, err := opts.Commits.APIFormat(ctx,
newUsersStore(s.db),
repoutil.RepositoryPath(opts.Owner.Name, opts.Repo.Name),
repoutil.HTMLURL(opts.Owner.Name, opts.Repo.Name),
repox.RepositoryPath(opts.Owner.Name, opts.Repo.Name),
repox.HTMLURL(opts.Owner.Name, opts.Repo.Name),
)
if err != nil {
return errors.Wrap(err, "convert commits to API format")
}
opts.Commits.CompareURL = repoutil.CompareCommitsPath(opts.Owner.Name, opts.Repo.Name, opts.OldCommitID, opts.NewCommitID)
opts.Commits.CompareURL = repox.CompareCommitsPath(opts.Owner.Name, opts.Repo.Name, opts.OldCommitID, opts.NewCommitID)
apiPusher := opts.Owner.APIFormat()
err = PrepareWebhooks(
opts.Repo,
HookEventTypePush,
&api.PushPayload{
&apiv1types.WebhookPushPayload{
Ref: opts.RefName,
Before: opts.OldCommitID,
After: opts.NewCommitID,
@@ -245,7 +245,7 @@ func (s *ActionsStore) MirrorSyncPush(ctx context.Context, opts MirrorSyncPushOp
return errors.Wrap(err, "prepare webhooks")
}
data, err := jsoniter.Marshal(opts.Commits)
data, err := json.Marshal(opts.Commits)
if err != nil {
return errors.Wrap(err, "marshal JSON")
}
@@ -476,7 +476,7 @@ func (s *ActionsStore) CommitRepo(ctx context.Context, opts CommitRepoOptions) e
// If not the first commit, set the compare URL.
if !isNewRef && !isDelRef {
opts.Commits.CompareURL = repoutil.CompareCommitsPath(opts.Owner.Name, opts.Repo.Name, opts.OldCommitID, opts.NewCommitID)
opts.Commits.CompareURL = repox.CompareCommitsPath(opts.Owner.Name, opts.Repo.Name, opts.OldCommitID, opts.NewCommitID)
}
refName := git.RefShortName(opts.RefFullName)
@@ -496,10 +496,10 @@ func (s *ActionsStore) CommitRepo(ctx context.Context, opts CommitRepoOptions) e
err = PrepareWebhooks(
opts.Repo,
HookEventTypeDelete,
&api.DeletePayload{
&apiv1types.WebhookDeletePayload{
Ref: refName,
RefType: "branch",
PusherType: api.PUSHER_TYPE_USER,
PusherType: apiv1types.WebhookPusherTypeUser,
Repo: apiRepo,
Sender: apiPusher,
},
@@ -529,7 +529,7 @@ func (s *ActionsStore) CommitRepo(ctx context.Context, opts CommitRepoOptions) e
opts.Commits.Commits = opts.Commits.Commits[:conf.UI.FeedMaxCommitNum]
}
data, err := jsoniter.Marshal(opts.Commits)
data, err := json.Marshal(opts.Commits)
if err != nil {
return errors.Wrap(err, "marshal JSON")
}
@@ -540,7 +540,7 @@ func (s *ActionsStore) CommitRepo(ctx context.Context, opts CommitRepoOptions) e
err = PrepareWebhooks(
opts.Repo,
HookEventTypeCreate,
&api.CreatePayload{
&apiv1types.WebhookCreatePayload{
Ref: refName,
RefType: "branch",
DefaultBranch: opts.Repo.DefaultBranch,
@@ -563,8 +563,8 @@ func (s *ActionsStore) CommitRepo(ctx context.Context, opts CommitRepoOptions) e
commits, err := opts.Commits.APIFormat(ctx,
newUsersStore(s.db),
repoutil.RepositoryPath(opts.Owner.Name, opts.Repo.Name),
repoutil.HTMLURL(opts.Owner.Name, opts.Repo.Name),
repox.RepositoryPath(opts.Owner.Name, opts.Repo.Name),
repox.HTMLURL(opts.Owner.Name, opts.Repo.Name),
)
if err != nil {
return errors.Wrap(err, "convert commits to API format")
@@ -573,7 +573,7 @@ func (s *ActionsStore) CommitRepo(ctx context.Context, opts CommitRepoOptions) e
err = PrepareWebhooks(
opts.Repo,
HookEventTypePush,
&api.PushPayload{
&apiv1types.WebhookPushPayload{
Ref: opts.RefFullName,
Before: opts.OldCommitID,
After: opts.NewCommitID,
@@ -635,10 +635,10 @@ func (s *ActionsStore) PushTag(ctx context.Context, opts PushTagOptions) error {
err = PrepareWebhooks(
opts.Repo,
HookEventTypeDelete,
&api.DeletePayload{
&apiv1types.WebhookDeletePayload{
Ref: refName,
RefType: "tag",
PusherType: api.PUSHER_TYPE_USER,
PusherType: apiv1types.WebhookPusherTypeUser,
Repo: apiRepo,
Sender: apiPusher,
},
@@ -658,7 +658,7 @@ func (s *ActionsStore) PushTag(ctx context.Context, opts PushTagOptions) error {
err = PrepareWebhooks(
opts.Repo,
HookEventTypeCreate,
&api.CreatePayload{
&apiv1types.WebhookCreatePayload{
Ref: refName,
RefType: "tag",
Sha: opts.NewCommitID,
@@ -751,7 +751,7 @@ func (a *Action) GetActUserName() string {
}
func (a *Action) ShortActUserName() string {
return strutil.Ellipsis(a.ActUserName, 20)
return strx.Ellipsis(a.ActUserName, 20)
}
func (a *Action) GetRepoUserName() string {
@@ -759,7 +759,7 @@ func (a *Action) GetRepoUserName() string {
}
func (a *Action) ShortRepoUserName() string {
return strutil.Ellipsis(a.RepoUserName, 20)
return strx.Ellipsis(a.RepoUserName, 20)
}
func (a *Action) GetRepoName() string {
@@ -767,7 +767,7 @@ func (a *Action) GetRepoName() string {
}
func (a *Action) ShortRepoName() string {
return strutil.Ellipsis(a.RepoName, 33)
return strx.Ellipsis(a.RepoName, 33)
}
func (a *Action) GetRepoPath() string {
@@ -848,7 +848,7 @@ func NewPushCommits() *PushCommits {
}
}
func (pcs *PushCommits) APIFormat(ctx context.Context, usersStore *UsersStore, repoPath, repoURL string) ([]*api.PayloadCommit, error) {
func (pcs *PushCommits) APIFormat(ctx context.Context, usersStore *UsersStore, repoPath, repoURL string) ([]*apiv1types.WebhookPayloadCommit, error) {
// NOTE: We cache query results in case there are many commits in a single push.
usernameByEmail := make(map[string]string)
getUsernameByEmail := func(email string) (string, error) {
@@ -870,7 +870,7 @@ func (pcs *PushCommits) APIFormat(ctx context.Context, usersStore *UsersStore, r
return user.Name, nil
}
commits := make([]*api.PayloadCommit, len(pcs.Commits))
commits := make([]*apiv1types.WebhookPayloadCommit, len(pcs.Commits))
for i, commit := range pcs.Commits {
authorUsername, err := getUsernameByEmail(commit.AuthorEmail)
if err != nil {
@@ -883,23 +883,23 @@ func (pcs *PushCommits) APIFormat(ctx context.Context, usersStore *UsersStore, r
}
nameStatus := &git.NameStatus{}
if !testutil.InTest {
if !testx.InTest {
nameStatus, err = git.ShowNameStatus(repoPath, commit.Sha1)
if err != nil {
return nil, errors.Wrapf(err, "show name status [commit_sha1: %s]", commit.Sha1)
}
}
commits[i] = &api.PayloadCommit{
commits[i] = &apiv1types.WebhookPayloadCommit{
ID: commit.Sha1,
Message: commit.Message,
URL: fmt.Sprintf("%s/commit/%s", repoURL, commit.Sha1),
Author: &api.PayloadUser{
Author: &apiv1types.WebhookPayloadUser{
Name: commit.AuthorName,
Email: commit.AuthorEmail,
UserName: authorUsername,
},
Committer: &api.PayloadUser{
Committer: &apiv1types.WebhookPayloadUser{
Name: commit.CommitterName,
Email: commit.CommitterEmail,
UserName: committerUsername,
+2 -2
View File
@@ -13,7 +13,7 @@ import (
"xorm.io/xorm"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/errx"
)
// Attachment represent a attachment of issue/comment/release.
@@ -81,7 +81,7 @@ func NewAttachment(name string, buf []byte, file multipart.File) (_ *Attachment,
return attach, nil
}
var _ errutil.NotFound = (*ErrAttachmentNotExist)(nil)
var _ errx.NotFound = (*ErrAttachmentNotExist)(nil)
type ErrAttachmentNotExist struct {
args map[string]any
+9 -9
View File
@@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"os"
@@ -13,7 +14,6 @@ import (
"sync"
"github.com/cockroachdb/errors"
jsoniter "github.com/json-iterator/go"
"gorm.io/gorm"
"gorm.io/gorm/schema"
log "unknwon.dev/clog/v2"
@@ -21,7 +21,7 @@ import (
"xorm.io/xorm"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/osutil"
"gogs.io/gogs/internal/osx"
)
// getTableType returns the type name of a table definition without package name,
@@ -99,7 +99,7 @@ func dumpTable(ctx context.Context, db *gorm.DB, table any, w io.Writer) error {
e.CreatedAt = e.CreatedAt.UTC()
}
err = jsoniter.NewEncoder(w).Encode(elem)
err = json.NewEncoder(w).Encode(elem)
if err != nil {
return errors.Wrap(err, "encode JSON")
}
@@ -129,7 +129,7 @@ func dumpLegacyTables(ctx context.Context, dirPath string, verbose bool) error {
}
if err = x.Context(ctx).Asc("id").Iterate(table, func(idx int, bean any) (err error) {
return jsoniter.NewEncoder(f).Encode(bean)
return json.NewEncoder(f).Encode(bean)
}); err != nil {
_ = f.Close()
return errors.Newf("dump table '%s': %v", tableName, err)
@@ -156,7 +156,7 @@ func ImportDatabase(ctx context.Context, db *gorm.DB, dirPath string, verbose bo
tableName := strings.TrimPrefix(fmt.Sprintf("%T", table), "*database.")
err := func() error {
tableFile := filepath.Join(dirPath, tableName+".json")
if !osutil.IsFile(tableFile) {
if !osx.IsFile(tableFile) {
log.Info("Skipped table %q", tableName)
return nil
}
@@ -207,7 +207,7 @@ func importTable(ctx context.Context, db *gorm.DB, table any, r io.Reader) error
cleaned := bytes.ReplaceAll(scanner.Bytes(), []byte("\\u0000"), []byte(""))
elem := reflect.New(reflect.TypeOf(table).Elem()).Interface()
err = jsoniter.Unmarshal(cleaned, elem)
err = json.Unmarshal(cleaned, elem)
if err != nil {
return errors.Wrap(err, "unmarshal JSON to struct")
}
@@ -247,7 +247,7 @@ func importLegacyTables(ctx context.Context, dirPath string, verbose bool) error
tableName := strings.TrimPrefix(fmt.Sprintf("%T", table), "*database.")
tableFile := filepath.Join(dirPath, tableName+".json")
if !osutil.IsFile(tableFile) {
if !osx.IsFile(tableFile) {
continue
}
@@ -269,7 +269,7 @@ func importLegacyTables(ctx context.Context, dirPath string, verbose bool) error
_, isInsertProcessor := table.(xorm.BeforeInsertProcessor)
scanner := bufio.NewScanner(f)
for scanner.Scan() {
if err = jsoniter.Unmarshal(scanner.Bytes(), table); err != nil {
if err = json.Unmarshal(scanner.Bytes(), table); err != nil {
return errors.Newf("unmarshal to struct: %v", err)
}
@@ -283,7 +283,7 @@ func importLegacyTables(ctx context.Context, dirPath string, verbose bool) error
DeadlineUnix int64
ClosedDateUnix int64
}
if err = jsoniter.Unmarshal(scanner.Bytes(), &meta); err != nil {
if err = json.Unmarshal(scanner.Bytes(), &meta); err != nil {
log.Error("Failed to unmarshal to map: %v", err)
}
+14 -14
View File
@@ -15,10 +15,10 @@ import (
"gogs.io/gogs/internal/auth"
"gogs.io/gogs/internal/auth/github"
"gogs.io/gogs/internal/auth/pam"
"gogs.io/gogs/internal/cryptoutil"
"gogs.io/gogs/internal/cryptox"
"gogs.io/gogs/internal/dbtest"
"gogs.io/gogs/internal/lfsutil"
"gogs.io/gogs/internal/testutil"
"gogs.io/gogs/internal/lfsx"
"gogs.io/gogs/internal/testx"
)
func TestDumpAndImport(t *testing.T) {
@@ -59,30 +59,30 @@ func setupDBToDump(t *testing.T, db *gorm.DB) {
&AccessToken{
UserID: 1,
Name: "test1",
Sha1: cryptoutil.SHA1("2910d03d-c0b5-4f71-bad5-c4086e4efae3"),
SHA256: cryptoutil.SHA256(cryptoutil.SHA1("2910d03d-c0b5-4f71-bad5-c4086e4efae3")),
Sha1: cryptox.SHA1("2910d03d-c0b5-4f71-bad5-c4086e4efae3"),
SHA256: cryptox.SHA256(cryptox.SHA1("2910d03d-c0b5-4f71-bad5-c4086e4efae3")),
CreatedUnix: 1588568886,
UpdatedUnix: 1588572486, // 1 hour later
},
&AccessToken{
UserID: 1,
Name: "test2",
Sha1: cryptoutil.SHA1("84117e17-7e67-4024-bd04-1c23e6e809d4"),
SHA256: cryptoutil.SHA256(cryptoutil.SHA1("84117e17-7e67-4024-bd04-1c23e6e809d4")),
Sha1: cryptox.SHA1("84117e17-7e67-4024-bd04-1c23e6e809d4"),
SHA256: cryptox.SHA256(cryptox.SHA1("84117e17-7e67-4024-bd04-1c23e6e809d4")),
CreatedUnix: 1588568886,
},
&AccessToken{
UserID: 2,
Name: "test1",
Sha1: cryptoutil.SHA1("da2775ce-73dd-47ba-b9d2-bbcc346585c4"),
SHA256: cryptoutil.SHA256(cryptoutil.SHA1("da2775ce-73dd-47ba-b9d2-bbcc346585c4")),
Sha1: cryptox.SHA1("da2775ce-73dd-47ba-b9d2-bbcc346585c4"),
SHA256: cryptox.SHA256(cryptox.SHA1("da2775ce-73dd-47ba-b9d2-bbcc346585c4")),
CreatedUnix: 1588568886,
},
&AccessToken{
UserID: 2,
Name: "test2",
Sha1: cryptoutil.SHA256(cryptoutil.SHA1("1b2dccd1-a262-470f-bb8c-7fc73192e9bb"))[:40],
SHA256: cryptoutil.SHA256(cryptoutil.SHA1("1b2dccd1-a262-470f-bb8c-7fc73192e9bb")),
Sha1: cryptox.SHA256(cryptox.SHA1("1b2dccd1-a262-470f-bb8c-7fc73192e9bb"))[:40],
SHA256: cryptox.SHA256(cryptox.SHA1("1b2dccd1-a262-470f-bb8c-7fc73192e9bb")),
CreatedUnix: 1588568886,
},
@@ -156,14 +156,14 @@ func setupDBToDump(t *testing.T, db *gorm.DB) {
RepoID: 1,
OID: "ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f",
Size: 100,
Storage: lfsutil.StorageLocal,
Storage: lfsx.StorageLocal,
CreatedAt: time.Unix(1588568886, 0).UTC(),
},
&LFSObject{
RepoID: 2,
OID: "ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f",
Size: 100,
Storage: lfsutil.StorageLocal,
Storage: lfsx.StorageLocal,
CreatedAt: time.Unix(1588568886, 0).UTC(),
},
@@ -213,7 +213,7 @@ func dumpTables(t *testing.T, db *gorm.DB) {
}
golden := filepath.Join("testdata", "backup", tableName+".golden.json")
testutil.AssertGolden(t, golden, testutil.Update("TestDumpAndImport"), buf.String())
testx.AssertGolden(t, golden, testx.Update("TestDumpAndImport"), buf.String())
}
}
+13 -14
View File
@@ -11,10 +11,9 @@ import (
log "unknwon.dev/clog/v2"
"xorm.io/xorm"
api "github.com/gogs/go-gogs-client"
"gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/errx"
"gogs.io/gogs/internal/markup"
apiv1types "gogs.io/gogs/internal/route/api/v1/types"
)
// CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
@@ -136,8 +135,8 @@ func (c *Comment) HTMLURL() string {
// This method assumes following fields have been assigned with valid values:
// Required - Poster, Issue
func (c *Comment) APIFormat() *api.Comment {
return &api.Comment{
func (c *Comment) APIFormat() *apiv1types.IssueComment {
return &apiv1types.IssueComment{
ID: c.ID,
HTMLURL: c.HTMLURL(),
Poster: c.Poster.APIFormat(),
@@ -347,8 +346,8 @@ func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content stri
}
comment.Issue = issue
if err = PrepareWebhooks(repo, HookEventTypeIssueComment, &api.IssueCommentPayload{
Action: api.HOOK_ISSUE_COMMENT_CREATED,
if err = PrepareWebhooks(repo, HookEventTypeIssueComment, &apiv1types.WebhookIssueCommentPayload{
Action: apiv1types.WebhookIssueCommentCreated,
Issue: issue.APIFormat(),
Comment: comment.APIFormat(),
Repository: repo.APIFormatLegacy(nil),
@@ -389,7 +388,7 @@ func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commi
return err
}
var _ errutil.NotFound = (*ErrCommentNotExist)(nil)
var _ errx.NotFound = (*ErrCommentNotExist)(nil)
type ErrCommentNotExist struct {
args map[string]any
@@ -483,12 +482,12 @@ func UpdateComment(doer *User, c *Comment, oldContent string) (err error) {
if err = c.Issue.LoadAttributes(); err != nil {
log.Error("Issue.LoadAttributes [issue_id: %d]: %v", c.IssueID, err)
} else if err = PrepareWebhooks(c.Issue.Repo, HookEventTypeIssueComment, &api.IssueCommentPayload{
Action: api.HOOK_ISSUE_COMMENT_EDITED,
} else if err = PrepareWebhooks(c.Issue.Repo, HookEventTypeIssueComment, &apiv1types.WebhookIssueCommentPayload{
Action: apiv1types.WebhookIssueCommentEdited,
Issue: c.Issue.APIFormat(),
Comment: c.APIFormat(),
Changes: &api.ChangesPayload{
Body: &api.ChangesFromPayload{
Changes: &apiv1types.WebhookChangesPayload{
Body: &apiv1types.WebhookChangesFromPayload{
From: oldContent,
},
},
@@ -538,8 +537,8 @@ func DeleteCommentByID(doer *User, id int64) error {
if err = comment.Issue.LoadAttributes(); err != nil {
log.Error("Issue.LoadAttributes [issue_id: %d]: %v", comment.IssueID, err)
} else if err = PrepareWebhooks(comment.Issue.Repo, HookEventTypeIssueComment, &api.IssueCommentPayload{
Action: api.HOOK_ISSUE_COMMENT_DELETED,
} else if err = PrepareWebhooks(comment.Issue.Repo, HookEventTypeIssueComment, &apiv1types.WebhookIssueCommentPayload{
Action: apiv1types.WebhookIssueCommentDeleted,
Issue: comment.Issue.APIFormat(),
Comment: comment.APIFormat(),
Repository: comment.Issue.Repo.APIFormatLegacy(nil),
+3 -5
View File
@@ -13,7 +13,7 @@ import (
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/dbutil"
"gogs.io/gogs/internal/dbx"
)
func newLogWriter() (logger.Writer, error) {
@@ -30,7 +30,7 @@ func newLogWriter() (logger.Writer, error) {
if err != nil {
return nil, errors.Wrap(err, `create "gorm.log"`)
}
return &dbutil.Logger{Writer: w}, nil
return &dbx.Logger{Writer: w}, nil
}
// Tables is the list of struct-to-table mappings.
@@ -59,7 +59,7 @@ func NewConnection(w logger.Writer) (*gorm.DB, error) {
LogLevel: level,
})
db, err := dbutil.OpenDB(
db, err := dbx.OpenDB(
conf.Database,
&gorm.Config{
SkipDefaultTransaction: true,
@@ -91,8 +91,6 @@ func NewConnection(w logger.Writer) (*gorm.DB, error) {
db = db.Set("gorm:table_options", "ENGINE=InnoDB").Session(&gorm.Session{})
case "sqlite3":
conf.UseSQLite3 = true
case "mssql":
conf.UseMSSQL = true
default:
panic("unreachable")
}
+49 -50
View File
@@ -11,11 +11,10 @@ import (
log "unknwon.dev/clog/v2"
"xorm.io/xorm"
api "github.com/gogs/go-gogs-client"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/errx"
"gogs.io/gogs/internal/markup"
apiv1types "gogs.io/gogs/internal/route/api/v1/types"
"gogs.io/gogs/internal/tool"
)
@@ -83,7 +82,7 @@ func getUserByID(e Engine, id int64) (*User, error) {
if err != nil {
return nil, err
} else if !has {
return nil, ErrUserNotExist{args: errutil.Args{"userID": id}}
return nil, ErrUserNotExist{args: errx.Args{"userID": id}}
}
// TODO(unknwon): Rely on AfterFind hook to sanitize user full name.
@@ -172,23 +171,23 @@ func (issue *Issue) HTMLURL() string {
}
// State returns string representation of issue status.
func (issue *Issue) State() api.StateType {
func (issue *Issue) State() apiv1types.IssueStateType {
if issue.IsClosed {
return api.STATE_CLOSED
return apiv1types.IssueStateClosed
}
return api.STATE_OPEN
return apiv1types.IssueStateOpen
}
// This method assumes some fields assigned with values:
// Required - Poster, Labels,
// Optional - Milestone, Assignee, PullRequest
func (issue *Issue) APIFormat() *api.Issue {
apiLabels := make([]*api.Label, len(issue.Labels))
func (issue *Issue) APIFormat() *apiv1types.Issue {
apiLabels := make([]*apiv1types.IssueLabel, len(issue.Labels))
for i := range issue.Labels {
apiLabels[i] = issue.Labels[i].APIFormat()
}
apiIssue := &api.Issue{
apiIssue := &apiv1types.Issue{
ID: issue.ID,
Index: issue.Index,
Poster: issue.Poster.APIFormat(),
@@ -208,7 +207,7 @@ func (issue *Issue) APIFormat() *api.Issue {
apiIssue.Assignee = issue.Assignee.APIFormat()
}
if issue.IsPull {
apiIssue.PullRequest = &api.PullRequestMeta{
apiIssue.PullRequest = &apiv1types.PullRequestMeta{
HasMerged: issue.PullRequest.HasMerged,
}
if issue.PullRequest.HasMerged {
@@ -246,16 +245,16 @@ func (issue *Issue) sendLabelUpdatedWebhook(doer *User) {
log.Error("LoadIssue: %v", err)
return
}
err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &api.PullRequestPayload{
Action: api.HOOK_ISSUE_LABEL_UPDATED,
err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &apiv1types.WebhookPullRequestPayload{
Action: apiv1types.WebhookIssueLabelUpdated,
Index: issue.Index,
PullRequest: issue.PullRequest.APIFormat(),
Repository: issue.Repo.APIFormatLegacy(nil),
Sender: doer.APIFormat(),
})
} else {
err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &api.IssuesPayload{
Action: api.HOOK_ISSUE_LABEL_UPDATED,
err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &apiv1types.WebhookIssuesPayload{
Action: apiv1types.WebhookIssueLabelUpdated,
Index: issue.Index,
Issue: issue.APIFormat(),
Repository: issue.Repo.APIFormatLegacy(nil),
@@ -359,16 +358,16 @@ func (issue *Issue) ClearLabels(doer *User) (err error) {
log.Error("LoadIssue: %v", err)
return err
}
err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &api.PullRequestPayload{
Action: api.HOOK_ISSUE_LABEL_CLEARED,
err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &apiv1types.WebhookPullRequestPayload{
Action: apiv1types.WebhookIssueLabelCleared,
Index: issue.Index,
PullRequest: issue.PullRequest.APIFormat(),
Repository: issue.Repo.APIFormatLegacy(nil),
Sender: doer.APIFormat(),
})
} else {
err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &api.IssuesPayload{
Action: api.HOOK_ISSUE_LABEL_CLEARED,
err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &apiv1types.WebhookIssuesPayload{
Action: apiv1types.WebhookIssueLabelCleared,
Index: issue.Index,
Issue: issue.APIFormat(),
Repository: issue.Repo.APIFormatLegacy(nil),
@@ -487,29 +486,29 @@ func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (e
if issue.IsPull {
// Merge pull request calls issue.changeStatus so we need to handle separately.
issue.PullRequest.Issue = issue
apiPullRequest := &api.PullRequestPayload{
apiPullRequest := &apiv1types.WebhookPullRequestPayload{
Index: issue.Index,
PullRequest: issue.PullRequest.APIFormat(),
Repository: repo.APIFormatLegacy(nil),
Sender: doer.APIFormat(),
}
if isClosed {
apiPullRequest.Action = api.HOOK_ISSUE_CLOSED
apiPullRequest.Action = apiv1types.WebhookIssueClosed
} else {
apiPullRequest.Action = api.HOOK_ISSUE_REOPENED
apiPullRequest.Action = apiv1types.WebhookIssueReopened
}
err = PrepareWebhooks(repo, HookEventTypePullRequest, apiPullRequest)
} else {
apiIssues := &api.IssuesPayload{
apiIssues := &apiv1types.WebhookIssuesPayload{
Index: issue.Index,
Issue: issue.APIFormat(),
Repository: repo.APIFormatLegacy(nil),
Sender: doer.APIFormat(),
}
if isClosed {
apiIssues.Action = api.HOOK_ISSUE_CLOSED
apiIssues.Action = apiv1types.WebhookIssueClosed
} else {
apiIssues.Action = api.HOOK_ISSUE_REOPENED
apiIssues.Action = apiv1types.WebhookIssueReopened
}
err = PrepareWebhooks(repo, HookEventTypeIssues, apiIssues)
}
@@ -529,12 +528,12 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) {
if issue.IsPull {
issue.PullRequest.Issue = issue
err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &api.PullRequestPayload{
Action: api.HOOK_ISSUE_EDITED,
err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &apiv1types.WebhookPullRequestPayload{
Action: apiv1types.WebhookIssueEdited,
Index: issue.Index,
PullRequest: issue.PullRequest.APIFormat(),
Changes: &api.ChangesPayload{
Title: &api.ChangesFromPayload{
Changes: &apiv1types.WebhookChangesPayload{
Title: &apiv1types.WebhookChangesFromPayload{
From: oldTitle,
},
},
@@ -542,12 +541,12 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) {
Sender: doer.APIFormat(),
})
} else {
err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &api.IssuesPayload{
Action: api.HOOK_ISSUE_EDITED,
err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &apiv1types.WebhookIssuesPayload{
Action: apiv1types.WebhookIssueEdited,
Index: issue.Index,
Issue: issue.APIFormat(),
Changes: &api.ChangesPayload{
Title: &api.ChangesFromPayload{
Changes: &apiv1types.WebhookChangesPayload{
Title: &apiv1types.WebhookChangesFromPayload{
From: oldTitle,
},
},
@@ -571,12 +570,12 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
if issue.IsPull {
issue.PullRequest.Issue = issue
err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &api.PullRequestPayload{
Action: api.HOOK_ISSUE_EDITED,
err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &apiv1types.WebhookPullRequestPayload{
Action: apiv1types.WebhookIssueEdited,
Index: issue.Index,
PullRequest: issue.PullRequest.APIFormat(),
Changes: &api.ChangesPayload{
Body: &api.ChangesFromPayload{
Changes: &apiv1types.WebhookChangesPayload{
Body: &apiv1types.WebhookChangesFromPayload{
From: oldContent,
},
},
@@ -584,12 +583,12 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
Sender: doer.APIFormat(),
})
} else {
err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &api.IssuesPayload{
Action: api.HOOK_ISSUE_EDITED,
err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &apiv1types.WebhookIssuesPayload{
Action: apiv1types.WebhookIssueEdited,
Index: issue.Index,
Issue: issue.APIFormat(),
Changes: &api.ChangesPayload{
Body: &api.ChangesFromPayload{
Changes: &apiv1types.WebhookChangesPayload{
Body: &apiv1types.WebhookChangesFromPayload{
From: oldContent,
},
},
@@ -620,29 +619,29 @@ func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) {
isRemoveAssignee := err != nil
if issue.IsPull {
issue.PullRequest.Issue = issue
apiPullRequest := &api.PullRequestPayload{
apiPullRequest := &apiv1types.WebhookPullRequestPayload{
Index: issue.Index,
PullRequest: issue.PullRequest.APIFormat(),
Repository: issue.Repo.APIFormatLegacy(nil),
Sender: doer.APIFormat(),
}
if isRemoveAssignee {
apiPullRequest.Action = api.HOOK_ISSUE_UNASSIGNED
apiPullRequest.Action = apiv1types.WebhookIssueUnassigned
} else {
apiPullRequest.Action = api.HOOK_ISSUE_ASSIGNED
apiPullRequest.Action = apiv1types.WebhookIssueAssigned
}
err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, apiPullRequest)
} else {
apiIssues := &api.IssuesPayload{
apiIssues := &apiv1types.WebhookIssuesPayload{
Index: issue.Index,
Issue: issue.APIFormat(),
Repository: issue.Repo.APIFormatLegacy(nil),
Sender: doer.APIFormat(),
}
if isRemoveAssignee {
apiIssues.Action = api.HOOK_ISSUE_UNASSIGNED
apiIssues.Action = apiv1types.WebhookIssueUnassigned
} else {
apiIssues.Action = api.HOOK_ISSUE_ASSIGNED
apiIssues.Action = apiv1types.WebhookIssueAssigned
}
err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, apiIssues)
}
@@ -789,8 +788,8 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
log.Error("MailParticipants: %v", err)
}
if err = PrepareWebhooks(repo, HookEventTypeIssues, &api.IssuesPayload{
Action: api.HOOK_ISSUE_OPENED,
if err = PrepareWebhooks(repo, HookEventTypeIssues, &apiv1types.WebhookIssuesPayload{
Action: apiv1types.WebhookIssueOpened,
Index: issue.Index,
Issue: issue.APIFormat(),
Repository: repo.APIFormatLegacy(nil),
@@ -802,7 +801,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
return nil
}
var _ errutil.NotFound = (*ErrIssueNotExist)(nil)
var _ errx.NotFound = (*ErrIssueNotExist)(nil)
type ErrIssueNotExist struct {
args map[string]any
+5 -5
View File
@@ -9,10 +9,10 @@ import (
"xorm.io/xorm"
"github.com/cockroachdb/errors"
api "github.com/gogs/go-gogs-client"
"gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/errx"
"gogs.io/gogs/internal/lazyregexp"
apiv1types "gogs.io/gogs/internal/route/api/v1/types"
"gogs.io/gogs/internal/tool"
)
@@ -62,8 +62,8 @@ type Label struct {
IsChecked bool `xorm:"-" json:"-" gorm:"-"`
}
func (l *Label) APIFormat() *api.Label {
return &api.Label{
func (l *Label) APIFormat() *apiv1types.IssueLabel {
return &apiv1types.IssueLabel{
ID: l.ID,
Name: l.Name,
Color: strings.TrimLeft(l.Color, "#"),
@@ -101,7 +101,7 @@ func NewLabels(labels ...*Label) error {
return err
}
var _ errutil.NotFound = (*ErrLabelNotExist)(nil)
var _ errx.NotFound = (*ErrLabelNotExist)(nil)
type ErrLabelNotExist struct {
args map[string]any
+12 -8
View File
@@ -10,8 +10,8 @@ import (
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/email"
"gogs.io/gogs/internal/markup"
"gogs.io/gogs/internal/strutil"
"gogs.io/gogs/internal/userutil"
"gogs.io/gogs/internal/strx"
"gogs.io/gogs/internal/userx"
)
func (issue *Issue) MailSubject() string {
@@ -36,7 +36,7 @@ func (mu mailerUser) Email() string {
}
func (mu mailerUser) GenerateEmailActivateCode(email string) string {
return userutil.GenerateActivateCode(
return userx.GenerateActivateCode(
mu.user.ID,
email,
mu.user.Name,
@@ -138,7 +138,7 @@ func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string)
for i := range participants {
if participants[i].ID == doer.ID {
continue
} else if strutil.ContainsFold(names, participants[i].Name) {
} else if strx.ContainsFold(names, participants[i].Name) {
continue
}
@@ -146,18 +146,20 @@ func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string)
names = append(names, participants[i].Name)
}
if issue.Assignee != nil && issue.Assignee.ID != doer.ID {
if !strutil.ContainsFold(names, issue.Assignee.Name) {
if !strx.ContainsFold(names, issue.Assignee.Name) {
tos = append(tos, issue.Assignee.Email)
names = append(names, issue.Assignee.Name)
}
}
email.SendIssueCommentMail(NewMailerIssue(issue), NewMailerRepo(issue.Repo), NewMailerUser(doer), tos)
if err = email.SendIssueCommentMail(NewMailerIssue(issue), NewMailerRepo(issue.Repo), NewMailerUser(doer), tos); err != nil {
return errors.Wrap(err, "send issue comment mail")
}
// Mail mentioned people and exclude watchers.
names = append(names, doer.Name)
toUsernames := make([]string, 0, len(mentions)) // list of user names.
for i := range mentions {
if strutil.ContainsFold(names, mentions[i]) {
if strx.ContainsFold(names, mentions[i]) {
continue
}
@@ -168,7 +170,9 @@ func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string)
if err != nil {
return errors.Wrap(err, "get mailable emails by usernames")
}
email.SendIssueMentionMail(NewMailerIssue(issue), NewMailerRepo(issue.Repo), NewMailerUser(doer), tos)
if err = email.SendIssueMentionMail(NewMailerIssue(issue), NewMailerRepo(issue.Repo), NewMailerUser(doer), tos); err != nil {
return errors.Wrap(err, "send issue mention mail")
}
return nil
}
+12 -12
View File
@@ -8,17 +8,17 @@ import (
"github.com/cockroachdb/errors"
"gorm.io/gorm"
"gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/lfsutil"
"gogs.io/gogs/internal/errx"
"gogs.io/gogs/internal/lfsx"
)
// LFSObject is the relation between an LFS object and a repository.
type LFSObject struct {
RepoID int64 `gorm:"primaryKey;auto_increment:false"`
OID lfsutil.OID `gorm:"primaryKey;column:oid"`
Size int64 `gorm:"not null"`
Storage lfsutil.Storage `gorm:"not null"`
CreatedAt time.Time `gorm:"not null"`
RepoID int64 `gorm:"primaryKey;auto_increment:false"`
OID lfsx.OID `gorm:"primaryKey;column:oid"`
Size int64 `gorm:"not null"`
Storage lfsx.Storage `gorm:"not null"`
CreatedAt time.Time `gorm:"not null"`
}
// LFSStore is the storage layer for LFS objects.
@@ -31,7 +31,7 @@ func newLFSStore(db *gorm.DB) *LFSStore {
}
// CreateObject creates an LFS object record in database.
func (s *LFSStore) CreateObject(ctx context.Context, repoID int64, oid lfsutil.OID, size int64, storage lfsutil.Storage) error {
func (s *LFSStore) CreateObject(ctx context.Context, repoID int64, oid lfsx.OID, size int64, storage lfsx.Storage) error {
object := &LFSObject{
RepoID: repoID,
OID: oid,
@@ -42,7 +42,7 @@ func (s *LFSStore) CreateObject(ctx context.Context, repoID int64, oid lfsutil.O
}
type ErrLFSObjectNotExist struct {
args errutil.Args
args errx.Args
}
func IsErrLFSObjectNotExist(err error) bool {
@@ -59,12 +59,12 @@ func (ErrLFSObjectNotExist) NotFound() bool {
// GetObjectByOID returns the LFS object with given OID. It returns
// ErrLFSObjectNotExist when not found.
func (s *LFSStore) GetObjectByOID(ctx context.Context, repoID int64, oid lfsutil.OID) (*LFSObject, error) {
func (s *LFSStore) GetObjectByOID(ctx context.Context, repoID int64, oid lfsx.OID) (*LFSObject, error) {
object := new(LFSObject)
err := s.db.WithContext(ctx).Where("repo_id = ? AND oid = ?", repoID, oid).First(object).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrLFSObjectNotExist{args: errutil.Args{"repoID": repoID, "oid": oid}}
return nil, ErrLFSObjectNotExist{args: errx.Args{"repoID": repoID, "oid": oid}}
}
return nil, err
}
@@ -73,7 +73,7 @@ func (s *LFSStore) GetObjectByOID(ctx context.Context, repoID int64, oid lfsutil
// GetObjectsByOIDs returns LFS objects found within "oids". The returned list
// could have fewer elements if some oids were not found.
func (s *LFSStore) GetObjectsByOIDs(ctx context.Context, repoID int64, oids ...lfsutil.OID) ([]*LFSObject, error) {
func (s *LFSStore) GetObjectsByOIDs(ctx context.Context, repoID int64, oids ...lfsx.OID) ([]*LFSObject, error) {
if len(oids) == 0 {
return []*LFSObject{}, nil
}
+12 -12
View File
@@ -8,8 +8,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/lfsutil"
"gogs.io/gogs/internal/errx"
"gogs.io/gogs/internal/lfsx"
)
func TestLFS(t *testing.T) {
@@ -47,8 +47,8 @@ func TestLFS(t *testing.T) {
func lfsCreateObject(t *testing.T, ctx context.Context, s *LFSStore) {
// Create first LFS object
repoID := int64(1)
oid := lfsutil.OID("ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f")
err := s.CreateObject(ctx, repoID, oid, 12, lfsutil.StorageLocal)
oid := lfsx.OID("ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f")
err := s.CreateObject(ctx, repoID, oid, 12, lfsx.StorageLocal)
require.NoError(t, err)
// Get it back and check the CreatedAt field
@@ -57,15 +57,15 @@ func lfsCreateObject(t *testing.T, ctx context.Context, s *LFSStore) {
assert.Equal(t, s.db.NowFunc().Format(time.RFC3339), object.CreatedAt.UTC().Format(time.RFC3339))
// Try to create second LFS object with same oid should fail
err = s.CreateObject(ctx, repoID, oid, 12, lfsutil.StorageLocal)
err = s.CreateObject(ctx, repoID, oid, 12, lfsx.StorageLocal)
assert.Error(t, err)
}
func lfsGetObjectByOID(t *testing.T, ctx context.Context, s *LFSStore) {
// Create a LFS object
repoID := int64(1)
oid := lfsutil.OID("ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f")
err := s.CreateObject(ctx, repoID, oid, 12, lfsutil.StorageLocal)
oid := lfsx.OID("ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f")
err := s.CreateObject(ctx, repoID, oid, 12, lfsx.StorageLocal)
require.NoError(t, err)
// We should be able to get it back
@@ -74,18 +74,18 @@ func lfsGetObjectByOID(t *testing.T, ctx context.Context, s *LFSStore) {
// Try to get a non-existent object
_, err = s.GetObjectByOID(ctx, repoID, "bad_oid")
expErr := ErrLFSObjectNotExist{args: errutil.Args{"repoID": repoID, "oid": lfsutil.OID("bad_oid")}}
expErr := ErrLFSObjectNotExist{args: errx.Args{"repoID": repoID, "oid": lfsx.OID("bad_oid")}}
assert.Equal(t, expErr, err)
}
func lfsGetObjectsByOIDs(t *testing.T, ctx context.Context, s *LFSStore) {
// Create two LFS objects
repoID := int64(1)
oid1 := lfsutil.OID("ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f")
oid2 := lfsutil.OID("ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64g")
err := s.CreateObject(ctx, repoID, oid1, 12, lfsutil.StorageLocal)
oid1 := lfsx.OID("ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f")
oid2 := lfsx.OID("ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64g")
err := s.CreateObject(ctx, repoID, oid1, 12, lfsx.StorageLocal)
require.NoError(t, err)
err = s.CreateObject(ctx, repoID, oid2, 12, lfsutil.StorageLocal)
err = s.CreateObject(ctx, repoID, oid2, 12, lfsx.StorageLocal)
require.NoError(t, err)
// We should be able to get them back and ignore non-existent ones
+6 -6
View File
@@ -16,8 +16,8 @@ import (
"gogs.io/gogs/internal/auth/ldap"
"gogs.io/gogs/internal/auth/pam"
"gogs.io/gogs/internal/auth/smtp"
"gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/osutil"
"gogs.io/gogs/internal/errx"
"gogs.io/gogs/internal/osx"
)
// loginSourceFilesStore is the in-memory interface for login source files stored on file system.
@@ -41,10 +41,10 @@ type loginSourceFiles struct {
clock func() time.Time
}
var _ errutil.NotFound = (*ErrLoginSourceNotExist)(nil)
var _ errx.NotFound = (*ErrLoginSourceNotExist)(nil)
type ErrLoginSourceNotExist struct {
args errutil.Args
args errx.Args
}
func IsErrLoginSourceNotExist(err error) bool {
@@ -69,7 +69,7 @@ func (s *loginSourceFiles) GetByID(id int64) (*LoginSource, error) {
}
}
return nil, ErrLoginSourceNotExist{args: errutil.Args{"id": id}}
return nil, ErrLoginSourceNotExist{args: errx.Args{"id": id}}
}
func (s *loginSourceFiles) Len() int {
@@ -109,7 +109,7 @@ func (s *loginSourceFiles) Update(source *LoginSource) {
// loadLoginSourceFiles loads login sources from file system.
func loadLoginSourceFiles(authdPath string, clock func() time.Time) (loginSourceFilesStore, error) {
if !osutil.IsDir(authdPath) {
if !osx.IsDir(authdPath) {
return &loginSourceFiles{clock: clock}, nil
}
+2 -2
View File
@@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/errx"
)
func TestLoginSourceFiles_GetByID(t *testing.T) {
@@ -19,7 +19,7 @@ func TestLoginSourceFiles_GetByID(t *testing.T) {
t.Run("source does not exist", func(t *testing.T) {
_, err := store.GetByID(1)
wantErr := ErrLoginSourceNotExist{args: errutil.Args{"id": int64(1)}}
wantErr := ErrLoginSourceNotExist{args: errx.Args{"id": int64(1)}}
assert.Equal(t, wantErr, err)
})
+16 -14
View File
@@ -2,12 +2,12 @@ package database
import (
"context"
"encoding/json"
"fmt"
"strconv"
"time"
"github.com/cockroachdb/errors"
jsoniter "github.com/json-iterator/go"
"gorm.io/gorm"
"gogs.io/gogs/internal/auth"
@@ -15,7 +15,7 @@ import (
"gogs.io/gogs/internal/auth/ldap"
"gogs.io/gogs/internal/auth/pam"
"gogs.io/gogs/internal/auth/smtp"
"gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/errx"
)
// LoginSource represents an external way for authorizing users.
@@ -41,7 +41,8 @@ func (s *LoginSource) BeforeSave(_ *gorm.DB) (err error) {
if s.Provider == nil {
return nil
}
s.Config, err = jsoniter.MarshalToString(s.Provider.Config())
data, err := json.Marshal(s.Provider.Config())
s.Config = string(data)
return err
}
@@ -72,7 +73,7 @@ func (s *LoginSource) AfterFind(_ *gorm.DB) error {
switch s.Type {
case auth.LDAP:
var cfg ldap.Config
err := jsoniter.UnmarshalFromString(s.Config, &cfg)
err := json.Unmarshal([]byte(s.Config), &cfg)
if err != nil {
return err
}
@@ -80,7 +81,7 @@ func (s *LoginSource) AfterFind(_ *gorm.DB) error {
case auth.DLDAP:
var cfg ldap.Config
err := jsoniter.UnmarshalFromString(s.Config, &cfg)
err := json.Unmarshal([]byte(s.Config), &cfg)
if err != nil {
return err
}
@@ -88,7 +89,7 @@ func (s *LoginSource) AfterFind(_ *gorm.DB) error {
case auth.SMTP:
var cfg smtp.Config
err := jsoniter.UnmarshalFromString(s.Config, &cfg)
err := json.Unmarshal([]byte(s.Config), &cfg)
if err != nil {
return err
}
@@ -96,7 +97,7 @@ func (s *LoginSource) AfterFind(_ *gorm.DB) error {
case auth.PAM:
var cfg pam.Config
err := jsoniter.UnmarshalFromString(s.Config, &cfg)
err := json.Unmarshal([]byte(s.Config), &cfg)
if err != nil {
return err
}
@@ -104,7 +105,7 @@ func (s *LoginSource) AfterFind(_ *gorm.DB) error {
case auth.GitHub:
var cfg github.Config
err := jsoniter.UnmarshalFromString(s.Config, &cfg)
err := json.Unmarshal([]byte(s.Config), &cfg)
if err != nil {
return err
}
@@ -112,7 +113,7 @@ func (s *LoginSource) AfterFind(_ *gorm.DB) error {
case auth.Mock:
var cfg mockProviderConfig
err := jsoniter.UnmarshalFromString(s.Config, &cfg)
err := json.Unmarshal([]byte(s.Config), &cfg)
if err != nil {
return err
}
@@ -188,7 +189,7 @@ type CreateLoginSourceOptions struct {
}
type ErrLoginSourceAlreadyExist struct {
args errutil.Args
args errx.Args
}
func IsErrLoginSourceAlreadyExist(err error) bool {
@@ -204,7 +205,7 @@ func (err ErrLoginSourceAlreadyExist) Error() string {
func (s *LoginSourcesStore) Create(ctx context.Context, opts CreateLoginSourceOptions) (*LoginSource, error) {
err := s.db.WithContext(ctx).Where("name = ?", opts.Name).First(new(LoginSource)).Error
if err == nil {
return nil, ErrLoginSourceAlreadyExist{args: errutil.Args{"name": opts.Name}}
return nil, ErrLoginSourceAlreadyExist{args: errx.Args{"name": opts.Name}}
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
@@ -215,7 +216,8 @@ func (s *LoginSourcesStore) Create(ctx context.Context, opts CreateLoginSourceOp
IsActived: opts.Activated,
IsDefault: opts.Default,
}
source.Config, err = jsoniter.MarshalToString(opts.Config)
data, err := json.Marshal(opts.Config)
source.Config = string(data)
if err != nil {
return nil, err
}
@@ -230,7 +232,7 @@ func (s *LoginSourcesStore) Count(ctx context.Context) int64 {
}
type ErrLoginSourceInUse struct {
args errutil.Args
args errx.Args
}
func IsErrLoginSourceInUse(err error) bool {
@@ -249,7 +251,7 @@ func (s *LoginSourcesStore) DeleteByID(ctx context.Context, id int64) error {
if err != nil {
return err
} else if count > 0 {
return ErrLoginSourceInUse{args: errutil.Args{"id": id}}
return ErrLoginSourceInUse{args: errx.Args{"id": id}}
}
return s.db.WithContext(ctx).Where("id = ?", id).Delete(new(LoginSource)).Error
+6 -6
View File
@@ -15,7 +15,7 @@ import (
"gogs.io/gogs/internal/auth/ldap"
"gogs.io/gogs/internal/auth/pam"
"gogs.io/gogs/internal/auth/smtp"
"gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/errx"
)
func TestLoginSource_BeforeSave(t *testing.T) {
@@ -211,7 +211,7 @@ func loginSourcesCreate(t *testing.T, ctx context.Context, s *LoginSourcesStore)
// Try to create second login source with same name should fail.
_, err = s.Create(ctx, CreateLoginSourceOptions{Name: source.Name})
wantErr := ErrLoginSourceAlreadyExist{args: errutil.Args{"name": source.Name}}
wantErr := ErrLoginSourceAlreadyExist{args: errx.Args{"name": source.Name}}
assert.Equal(t, wantErr, err)
}
@@ -270,13 +270,13 @@ func loginSourcesDeleteByID(t *testing.T, ctx context.Context, s *LoginSourcesSt
// Delete the login source will result in error
err = s.DeleteByID(ctx, source.ID)
wantErr := ErrLoginSourceInUse{args: errutil.Args{"id": source.ID}}
wantErr := ErrLoginSourceInUse{args: errx.Args{"id": source.ID}}
assert.Equal(t, wantErr, err)
})
mock := NewMockLoginSourceFilesStore()
mock.GetByIDFunc.SetDefaultHook(func(id int64) (*LoginSource, error) {
return nil, ErrLoginSourceNotExist{args: errutil.Args{"id": id}}
return nil, ErrLoginSourceNotExist{args: errx.Args{"id": id}}
})
setMockLoginSourceFilesStore(t, s, mock)
@@ -308,7 +308,7 @@ func loginSourcesDeleteByID(t *testing.T, ctx context.Context, s *LoginSourcesSt
// We should get token not found error
_, err = s.GetByID(ctx, source.ID)
wantErr := ErrLoginSourceNotExist{args: errutil.Args{"id": source.ID}}
wantErr := ErrLoginSourceNotExist{args: errx.Args{"id": source.ID}}
assert.Equal(t, wantErr, err)
}
@@ -316,7 +316,7 @@ func loginSourcesGetByID(t *testing.T, ctx context.Context, s *LoginSourcesStore
mock := NewMockLoginSourceFilesStore()
mock.GetByIDFunc.SetDefaultHook(func(id int64) (*LoginSource, error) {
if id != 101 {
return nil, ErrLoginSourceNotExist{args: errutil.Args{"id": id}}
return nil, ErrLoginSourceNotExist{args: errx.Args{"id": id}}
}
return &LoginSource{ID: id}, nil
})
+2 -2
View File
@@ -12,7 +12,7 @@ import (
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/dbtest"
"gogs.io/gogs/internal/testutil"
"gogs.io/gogs/internal/testx"
)
func TestMain(m *testing.M) {
@@ -22,7 +22,7 @@ func TestMain(m *testing.M) {
if !testing.Verbose() {
// Remove the primary logger and register a noop logger.
log.Remove(log.DefaultConsoleName)
err := log.New("noop", testutil.InitNoopLogger)
err := log.New("noop", testx.InitNoopLogger)
if err != nil {
fmt.Println(err)
os.Exit(1)
+2 -2
View File
@@ -9,7 +9,7 @@ import (
"gorm.io/gorm/logger"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/testutil"
"gogs.io/gogs/internal/testx"
)
func TestMain(m *testing.M) {
@@ -19,7 +19,7 @@ func TestMain(m *testing.M) {
if !testing.Verbose() {
// Remove the primary logger and register a noop logger.
log.Remove(log.DefaultConsoleName)
err := log.New("noop", testutil.InitNoopLogger)
err := log.New("noop", testx.InitNoopLogger)
if err != nil {
fmt.Println(err)
os.Exit(1)
+3 -3
View File
@@ -102,11 +102,11 @@ You can migrate your older database using a previous release, then you can upgra
Please save following instructions to somewhere and start working:
- If you were using below 0.6.0 (e.g. 0.5.x), download last supported archive from following link:
https://gogs.io/gogs/releases/tag/v0.7.33
https://github.com/gogs/gogs/releases/tag/v0.7.33
- If you were using below 0.7.0 (e.g. 0.6.x), download last supported archive from following link:
https://gogs.io/gogs/releases/tag/v0.9.141
https://github.com/gogs/gogs/releases/tag/v0.9.141
- If you were using below 0.11.55 (e.g. 0.9.141), download last supported archive from following link:
https://gogs.io/gogs/releases/tag/v0.12.0
https://github.com/gogs/gogs/releases/tag/v0.12.0
Once finished downloading:
+2 -2
View File
@@ -4,7 +4,7 @@ import (
"github.com/cockroachdb/errors"
"gorm.io/gorm"
"gogs.io/gogs/internal/cryptoutil"
"gogs.io/gogs/internal/cryptox"
)
func migrateAccessTokenToSHA256(db *gorm.DB) error {
@@ -33,7 +33,7 @@ func migrateAccessTokenToSHA256(db *gorm.DB) error {
}
for _, t := range accessTokens {
sha256 := cryptoutil.SHA256(t.Sha1)
sha256 := cryptox.SHA256(t.Sha1)
err = tx.Model(&accessToken{}).Where("id = ?", t.ID).Update("sha256", sha256).Error
if err != nil {
return errors.Wrap(err, "update")
+13 -13
View File
@@ -8,10 +8,10 @@ import (
"xorm.io/xorm"
"github.com/cockroachdb/errors"
api "github.com/gogs/go-gogs-client"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/errx"
apiv1types "gogs.io/gogs/internal/route/api/v1/types"
)
// Milestone represents a milestone of repository.
@@ -72,19 +72,19 @@ func (m *Milestone) AfterSet(colName string, _ xorm.Cell) {
}
// State returns string representation of milestone status.
func (m *Milestone) State() api.StateType {
func (m *Milestone) State() apiv1types.IssueStateType {
if m.IsClosed {
return api.STATE_CLOSED
return apiv1types.IssueStateClosed
}
return api.STATE_OPEN
return apiv1types.IssueStateOpen
}
func (m *Milestone) ChangeStatus(isClosed bool) error {
return ChangeMilestoneStatus(m, isClosed)
}
func (m *Milestone) APIFormat() *api.Milestone {
apiMilestone := &api.Milestone{
func (m *Milestone) APIFormat() *apiv1types.IssueMilestone {
apiMilestone := &apiv1types.IssueMilestone{
ID: m.ID,
State: m.State(),
Title: m.Name,
@@ -128,7 +128,7 @@ func NewMilestone(m *Milestone) (err error) {
return sess.Commit()
}
var _ errutil.NotFound = (*ErrMilestoneNotExist)(nil)
var _ errx.NotFound = (*ErrMilestoneNotExist)(nil)
type ErrMilestoneNotExist struct {
args map[string]any
@@ -343,11 +343,11 @@ func ChangeMilestoneAssign(doer *User, issue *Issue, oldMilestoneID int64) (err
return errors.Newf("commit: %v", err)
}
var hookAction api.HookIssueAction
var hookAction apiv1types.WebhookIssueAction
if issue.MilestoneID > 0 {
hookAction = api.HOOK_ISSUE_MILESTONED
hookAction = apiv1types.WebhookIssueMilestoned
} else {
hookAction = api.HOOK_ISSUE_DEMILESTONED
hookAction = apiv1types.WebhookIssueDemilestoned
}
if issue.IsPull {
@@ -356,7 +356,7 @@ func ChangeMilestoneAssign(doer *User, issue *Issue, oldMilestoneID int64) (err
log.Error("LoadIssue: %v", err)
return err
}
err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &api.PullRequestPayload{
err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &apiv1types.WebhookPullRequestPayload{
Action: hookAction,
Index: issue.Index,
PullRequest: issue.PullRequest.APIFormat(),
@@ -364,7 +364,7 @@ func ChangeMilestoneAssign(doer *User, issue *Issue, oldMilestoneID int64) (err
Sender: doer.APIFormat(),
})
} else {
err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &api.IssuesPayload{
err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &apiv1types.WebhookIssuesPayload{
Action: hookAction,
Index: issue.Index,
Issue: issue.APIFormat(),

Some files were not shown because too many files have changed in this diff Show More