Compare commits

..

55 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
76f3497e3b Fix embedded Postgres configuration and initialization order
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-02-06 01:48:00 +00:00
copilot-swe-agent[bot]
74d4d9a262 Fix trailing whitespace in code review feedback
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-02-06 01:32:05 +00:00
deepsource-autofix[bot]
f5daceef55 style: format code with Go fmt and Gofumpt
This commit fixes the style issues introduced in 4616bd6 according to the output
from Go fmt and Gofumpt.

Details: https://github.com/gogs/gogs/pull/8150
2026-02-06 01:29:34 +00:00
copilot-swe-agent[bot]
4616bd64dd Add embedded PostgreSQL support via --embedded-postgres flag
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-02-06 01:29:19 +00:00
copilot-swe-agent[bot]
39b27c3503 Initial plan 2026-02-06 01:18:32 +00:00
Joe Chen
ed5d02e036 chore: free up "docs/" subdir for Mintlify
[skip ci]
2026-02-01 23:15:47 -05:00
dependabot[bot]
5874791a57 mod: bump golang.org/x/crypto from 0.45.0 to 0.47.0 (#8141)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-01 09:20:24 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
17ad3d3425 pkgr: fix up install main package path (#8146) 2026-02-01 09:12:12 -05:00
dependabot[bot]
9b2a967e45 mod: bump github.com/editorconfig/editorconfig-core-go/v2 from 2.6.3 to 2.6.4 (#8143)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-01 09:01:09 -05:00
dependabot[bot]
e80635a449 mod: bump github.com/go-ldap/ldap/v3 from 3.4.11 to 3.4.12 (#8144)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-01 09:00:50 -05:00
Joe Chen
47bccf292d pkgr: fix up install path
[skip ci]
2026-02-01 08:53:52 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
38def73489 Update security policy for version support and advisories
[skip ci]
2026-02-01 08:16:22 -05:00
Copilot
6cf6422b88 Remove codecov config and upload in CI (#8145)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-02-01 08:10:55 -05:00
dependabot[bot]
8d8d66ec1d mod: bump github.com/olekukonko/tablewriter from 1.1.0 to 1.1.3 (#8140)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-01 07:45:08 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
7ea2c4093f refactor: move main package from root to cmd/gogs (#8139)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 23:28:48 -05:00
dependabot[bot]
7ebfb202e4 mod: bump github.com/olekukonko/tablewriter from 0.0.5 to 1.1.0 (#8039)
Co-authored-by: Joe Chen <jc@unknwon.io>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 23:11:08 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
7b8c560f15 ci(go): use tparse for test output in non-Windows jobs (#8138)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 22:58:41 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
a636dcf678 fix(release): remove "v" prefix from archive names (#8137)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 22:42:45 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
3dcb74be39 release: cut CHANGELOG entries for 0.14.1
[skip ci]
2026-01-31 22:23:09 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
7ad425025e fix(ssh): git clone via built-in SSH server hangs (#8135)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 22:20:43 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
85abee4b9b chore: update release templates (#8134)
[skip ci]
2026-01-31 20:08:38 -05:00
Joe Chen
997c3c5eab chore: take care of legacy code 2026-01-31 20:07:49 -05:00
Joe Chen
a5ddb2665e chore: bump dev version to 0.15
[skip ci]
2026-01-31 20:04:55 -05:00
Joe Chen
b68e6886c6 release: cut CHANGELOG entries for 0.14.0
[skip ci]
2026-01-31 16:29:15 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
ac7ba9c8a7 locale: sync from Crowdin (#8131) 2026-01-31 16:07:47 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
dd862ee058 ci(docker): auto-tag minor version for stable releases (#8129)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 15:50:42 -05:00
Joe Chen
f94042ce6f chore: update release templates
[skip ci]
2026-01-31 15:17:59 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
628216d588 security: require authentication for attachment uploads (#8128)
https://github.com/gogs/gogs/security/advisories/GHSA-fc3h-92p8-h36f

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 14:40:39 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
7306b955a9 ci: use external unknwon/send-email-on-failure action (#8127) 2026-01-31 13:29:30 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
fc6d1e2055 ci(release): add email notification on failure for main branch (#8126)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 13:13:51 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
3b01892d85 fix(docker): correct binary path in Dockerfiles(#8125)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 13:02:25 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
7b7e38c880 security: prevent deletion of protected and default branches via web UI (#8124)
https://github.com/gogs/gogs/security/advisories/GHSA-2c6v-8r3v-gh6p

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 12:51:07 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
bb68c0a042 security: fix cross-repository label modification vulnerability (#8123)
https://github.com/gogs/gogs/security/advisories/GHSA-cv22-72px-f4gh

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 12:28:30 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
68271e6af0 chore: update vulnerability reporting guidelines
[skip ci]
2026-01-31 12:01:23 -05:00
Copilot
4f5b00f8c4 Build artifacts for every commit on main (#8122)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Joe Chen <jc@unknwon.io>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 11:58:45 -05:00
Joe Chen
5d3ffd132b chore: bunch of minor tidy-ups
[skip ci]
2026-01-31 09:59:32 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
ee65aa89ca ci: add cross-compilation workflow for releases (#8121)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 09:40:32 -05:00
dependabot[bot]
a1a97de76f mod: bump modernc.org/sqlite from 1.38.2 to 1.39.0 (#8038)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-30 23:19:19 -05:00
Matthias Jobst
9963268267 docker: ignore proxy for healthcheck command (#7532)
Co-authored-by: Joe Chen <jc@unknwon.io>
2026-01-30 23:12:49 -05:00
dependabot[bot]
49a45290ae mod: bump gopkg.in/macaron.v1 from 1.5.0 to 1.5.1 (#8035)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: ᴊᴏᴇ ᴄʜᴇɴ <jc@unknwon.io>
2026-01-30 23:10:14 -05:00
Yaroslav Halchenko
3cc8e7aa6d Fix typos throughout the codebase (#7514) 2026-01-30 23:03:05 -05:00
Rajat Jain
9f1499f3ab Support comparing tags in addition to branches (#6493)
Co-authored-by: Joe Chen <jc@unknwon.io>
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-30 22:45:03 -05:00
Jeff Li
77dba1b5ea repo: fix 500 error on watchers and stargazers pages using MSSQL (#6386)
Co-authored-by: Joe Chen <jc@unknwon.io>
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-30 22:24:43 -05:00
Georg Wicke-Arndt
f70f29fdb0 Show file name in browser tab title when viewing files (#5896) 2026-01-30 22:11:10 -05:00
Joe Chen
ed6109d35d chore: update AGENTS.md
[skip ci]
2026-01-30 22:01:02 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
54e08ba678 docker: add image versions instructions
[skip ci]
2026-01-30 21:52:31 -05:00
Sino
87c8faaf08 Standardize HTTP status codes (#7851)
Co-authored-by: Joe Chen <jc@unknwon.io>
2026-01-30 09:27:59 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
1b226ca48d repo: improve authz for resources (#8119)
https://github.com/gogs/gogs/security/advisories/GHSA-jj5m-h57j-5gv7
2026-01-29 20:56:09 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
e3bb4165dc database: impersonate pure-Go SQLite driver as the old "sqlite3" (#8118) 2026-01-29 14:49:36 -05:00
pikomonde
df3d945a2c config: validate and print warnings for invalid options (#7705)
Co-authored-by: Joe Chen <jc@unknwon.io>
2026-01-28 11:36:03 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
ae41bab5f2 repo: always list tree entries with verbatim (#8116)
Co-authored-by: Ali <alicse3@gmail.com>
2026-01-28 10:11:30 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
2316b09eaf database: fully switch over to pure-Go SQLite driver (#8115) 2026-01-27 22:45:50 -05:00
Copilot
3477bbac0e Add ED25519 test coverage and refactor SSH key parsing tests (#8107)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 14:04:12 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
bb3cab921b chore: update release template (#8110)
[skip ci]
2026-01-24 23:07:27 -05:00
Copilot
1cdeef2ce8 Replace tool.IsMaliciousPath with pathutil.Clean and move IsSameSite to urlutil (#8106) 2026-01-23 21:13:27 -05:00
139 changed files with 4104 additions and 3246 deletions

View File

@@ -1,6 +1,7 @@
--- ---
name: "Dev: Release a minor version" name: "Dev: Release a minor version"
about: ONLY USED BY MAINTAINERS. about: ONLY USED BY MAINTAINERS.
assignees: "unknwon"
title: "Release [VERSION]" title: "Release [VERSION]"
labels: 📸 release labels: 📸 release
--- ---
@@ -13,40 +14,33 @@ On the `main` branch:
- [ ] Close stale issues with the label [status: needs feedback](https://github.com/gogs/gogs/issues?q=is%3Aissue+is%3Aopen+label%3A%22status%3A+needs+feedback%22). - [ ] Close stale issues with the label [status: needs feedback](https://github.com/gogs/gogs/issues?q=is%3Aissue+is%3Aopen+label%3A%22status%3A+needs+feedback%22).
- [ ] [Sync locales from Crowdin](https://github.com/gogs/gogs/blob/main/docs/dev/import_locale.md). - [ ] [Sync locales from Crowdin](https://github.com/gogs/gogs/blob/main/docs/dev/import_locale.md).
- [ ] [Update CHANGELOG](https://github.com/gogs/gogs/commit/540134d4436d8da82247dd2cabe9312ca2f5b1f1) to include entries for the current minor release. - [ ] [Update CHANGELOG](https://github.com/gogs/gogs/commit/f1102a7a7c545ec221d2906f02fa19170d96f96d) to include entries for the current minor release.
- [ ] Cut a new release branch `release/<MAJOR>.<MINOR>`, e.g. `release/0.12`. - Do not forget adding entries for GHSA patches.
- [ ] Cut a new release branch `release/<MAJOR>.<MINOR>`, e.g. `release/0.14`.
## During release ## During release
On the release branch: On the release branch:
- [ ] [Update the hard-coded version](https://github.com/gogs/gogs/commit/f17e7d5a2c36c52a1121d2315f3d75dcd8053b89) to the current release, e.g. `0.12.0+dev` -> `0.12.0`. - [ ] [Update the hard-coded version](https://github.com/gogs/gogs/commit/f0e3cd90f8d7695960eeef2e4e54b2e717302f6c) to the current release, e.g. `0.14.0+dev` -> `0.14.0`.
- [ ] Wait for GitHub Actions to complete and no failed jobs. - [ ] Wait for GitHub Actions to complete and no failed jobs.
- [ ] Publish new RC releases (e.g. `v0.12.0-rc.1`, `v0.12.0-rc.2`) to ensure Docker workflow succeeds. **Make sure the tag is created on the release branch**. - [ ] Publish new RC releases (e.g. `v0.14.0-rc.1`, `v0.14.0-rc.2`) ⚠️ **on the release branch** ⚠️ and ensure Docker and release workflows both succeed.
- [ ] Pull down the Docker image and [run through application setup](https://github.com/gogs/gogs/blob/main/docker/README.md) to make sure nothing blows up.
- [ ] Download one of the release archives and run through application setup to make sure nothing blows up.
- [ ] Publish a new [GitHub release](https://github.com/gogs/gogs/releases) ⚠️ **on the release branch** ⚠️ with entries from [CHANGELOG](https://github.com/gogs/gogs/blob/main/CHANGELOG.md) for the current minor release.
- [ ] [Wait for new image tags for the current release](https://github.com/gogs/gogs/actions/workflows/docker.yml?query=event%3Arelease) to be created automatically on both [Docker Hub](https://hub.docker.com/r/gogs/gogs/tags) and [GitHub Container registry](https://github.com/gogs/gogs/pkgs/container/gogs).
- Pull down the Docker image and [run through application setup](https://github.com/gogs/gogs/blob/main/docker/README.md) to make sure nothing blows up. - Pull down the Docker image and [run through application setup](https://github.com/gogs/gogs/blob/main/docker/README.md) to make sure nothing blows up.
- [ ] Publish a new [GitHub release](https://github.com/gogs/gogs/releases) with entries from [CHANGELOG](https://github.com/gogs/gogs/blob/main/CHANGELOG.md) for the current minor release. **Make sure the tag is created on the release branch**. - [ ] Download all release archives and [generate SHA256 checksum](https://github.com/gogs/gogs/blob/main/docs/dev/release/sha256.sh) for all binaries to the file `checksum_sha256.txt`.
- [ ] [Wait for a new image tag for the current release](https://github.com/gogs/gogs/actions/workflows/docker.yml?query=event%3Arelease) to be created automatically on both [Docker Hub](https://hub.docker.com/r/gogs/gogs/tags) and [GitHub Container registry](https://github.com/gogs/gogs/pkgs/container/gogs). - [ ] Upload all archives and `checksum_sha256.txt` to https://dl.gogs.io.
- [ ] [Push a new Docker image tag](https://github.com/gogs/gogs/blob/main/docs/dev/release/release_new_version.md#update-docker-image-tag) as `<MAJOR>.<MINOR>` to both [Docker Hub](https://hub.docker.com/r/gogs/gogs/tags) and [GitHub Container registry](https://github.com/gogs/gogs/pkgs/container/gogs), e.g.:
- [ ] [Compile and pack binaries](https://github.com/gogs/gogs/blob/main/docs/dev/release/release_new_version.md#compile-and-pack-binaries) (all prefixed with `gogs_<MAJOR>.<MINOR>.<PATCH>_`, e.g. `gogs_0.12.0_`):
- [ ] macOS: `darwin_amd64.zip`, `darwin_arm64.zip`
- [ ] Linux: `linux_386.tar.gz`, `linux_386.zip`, `linux_amd64.tar.gz`, `linux_amd64.zip`
- [ ] ARM: `linux_armv7.tar.gz`, `linux_armv7.zip`, `linux_armv8.tar.gz`, `linux_armv8.zip`
- [ ] Windows: `windows_amd64.zip`, `windows_amd64_mws.zip`
- [ ] [Generate SHA256 checksum](https://github.com/gogs/gogs/blob/main/docs/dev/release/sha256.sh) for all binaries to the file `checksum_sha256.txt`.
- [ ] Upload all binaries and `checksum_sha256.txt` to:
- [ ] GitHub release
- [ ] https://dl.gogs.io
- [ ] Update content of [Install from binary](https://gogs.io/docs/installation/install_from_binary).
## After release ## After release
On the `main` branch: On the `main` branch:
- [ ] Publish [GitHub security advisories](https://github.com/gogs/gogs/security) for security patches included in the release.
- [ ] Update the repository mirror on [Gitee](https://gitee.com/unknwon/gogs). - [ ] Update the repository mirror on [Gitee](https://gitee.com/unknwon/gogs).
- [ ] Create a new release announcement in [Discussions](https://github.com/gogs/gogs/discussions/categories/announcements). - [ ] Create a new release announcement in [Discussions](https://github.com/gogs/gogs/discussions/categories/announcements).
- [ ] Send a tweet on the [official Twitter account](https://twitter.com/GogsHQ) for the minor release. - [ ] Send a tweet on the [official Twitter account](https://twitter.com/GogsHQ) for the minor release.
- [ ] Publish a new release article on [OSChina](http://my.oschina.net/Obahua/admin/releases). - [ ] Close the milestone for the minor release.
- [ ] Close the minor milestone. - [ ] [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`.
- [ ] [Bump the hard-coded version](https://github.com/gogs/gogs/commit/a98968436cd5841cf691bb0b80c54c81470d1676) to the new develop version, e.g. `0.12.0+dev` -> `0.13.0+dev`.
- [ ] Run `task legacy` to identify deprecated code that is aimed to be removed in current develop version. - [ ] Run `task legacy` 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.

View File

@@ -1,6 +1,7 @@
--- ---
name: "Dev: Release a patch version" name: "Dev: Release a patch version"
about: ONLY USED BY MAINTAINERS. about: ONLY USED BY MAINTAINERS.
assignees: "unknwon"
title: "Release [VERSION]" title: "Release [VERSION]"
labels: 📸 release labels: 📸 release
--- ---
@@ -13,7 +14,7 @@ On the release branch:
- [ ] Make sure all commits are cherry-picked from the `main` branch by checking the patch milestone. - [ ] 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 `task build` 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/e6c5633f580399c8f4dfc07166a63a01c6c70346) to include entries for the current patch release. - [ ] [Update CHANGELOG on the `main` branch](https://github.com/gogs/gogs/commit/f1102a7a7c545ec221d2906f02fa19170d96f96d) to include entries for the current patch release.
## During release ## During release
@@ -21,28 +22,18 @@ On the release branch:
- [ ] [Update the hard-coded version](https://github.com/gogs/gogs/commit/f0e3cd90f8d7695960eeef2e4e54b2e717302f6c) to the current release, e.g. `0.12.0` -> `0.12.1`. - [ ] [Update the hard-coded version](https://github.com/gogs/gogs/commit/f0e3cd90f8d7695960eeef2e4e54b2e717302f6c) to the current release, e.g. `0.12.0` -> `0.12.1`.
- [ ] Wait for GitHub Actions to complete and no failed jobs. - [ ] Wait for GitHub Actions to complete and no failed jobs.
- [ ] Publish new RC releases in [GitHub release](https://github.com/gogs/gogs/releases) (e.g. `v0.12.0-rc.1`, `v0.12.0-rc.2`) to ensure Docker workflow succeeds. - [ ] Publish new RC releases in [GitHub release](https://github.com/gogs/gogs/releases) (e.g. `v0.12.0-rc.1`, `v0.12.0-rc.2`) ⚠️ **on the release branch** ⚠️ and ensure Docker workflow succeeds.
- ⚠️ **Make sure the tag is created on the release branch**. - [ ] Pull down the Docker image and [run through application setup](https://github.com/gogs/gogs/blob/main/docker/README.md) to make sure nothing blows up.
- Pull down the Docker image and [run through application setup](https://github.com/gogs/gogs/blob/main/docker/README.md) to make sure nothing blows up. - [ ] Download one of the release archives and run through application setup to make sure nothing blows up.
- [ ] Publish a new [GitHub release](https://github.com/gogs/gogs/releases) with entries from [CHANGELOG](https://github.com/gogs/gogs/blob/main/CHANGELOG.md) for the current patch release and all previous releases with same minor version. - [ ] Publish a new [GitHub release](https://github.com/gogs/gogs/releases) ⚠️ **on the release branch** ⚠️ with entries from [CHANGELOG](https://github.com/gogs/gogs/blob/main/CHANGELOG.md) for the current patch release and all previous releases with same minor version.
- ⚠️ **Make sure the tag is created on the release branch**.
- [ ] Update all previous GitHub releases with same minor version with the warning: - [ ] Update all previous GitHub releases with same minor version with the warning:
``` ```
** Heads up! There is a new patch release [0.12.1](https://github.com/gogs/gogs/releases/tag/v0.12.1) available, we recommend directly installing or upgrading to that version.** ** Heads up! There is a new patch release [0.12.1](https://github.com/gogs/gogs/releases/tag/v0.12.1) available, we recommend directly installing or upgrading to that version.**
``` ```
- [ ] [Wait for a new image tag for the current release](https://github.com/gogs/gogs/actions/workflows/docker.yml?query=event%3Arelease) to be created automatically on both [Docker Hub](https://hub.docker.com/r/gogs/gogs/tags) and [GitHub Container registry](https://github.com/gogs/gogs/pkgs/container/gogs). - [ ] [Wait for new image tags for the current release](https://github.com/gogs/gogs/actions/workflows/docker.yml?query=event%3Arelease) to be created automatically on both [Docker Hub](https://hub.docker.com/r/gogs/gogs/tags) and [GitHub Container registry](https://github.com/gogs/gogs/pkgs/container/gogs).
- Pull down the Docker image and [run through application setup](https://github.com/gogs/gogs/blob/main/docker/README.md) to make sure nothing blows up. - Pull down the Docker image and [run through application setup](https://github.com/gogs/gogs/blob/main/docker/README.md) to make sure nothing blows up.
- [ ] [Update Docker image tag](https://www.notion.so/jcunknwon/Cheatsheet-and-playbooks-c3b053da42114411bd27285cd065b2a6?source=copy_link#1654f105c63f80958d96cd72e2f5df69) for the minor release `<MAJOR>.<MINOR>` on both [Docker Hub](https://hub.docker.com/r/gogs/gogs/tags) and [GitHub Container registry](https://github.com/gogs/gogs/pkgs/container/gogs). - [ ] Download all release archives and [generate SHA256 checksum](https://github.com/gogs/gogs/blob/main/docs/dev/release/sha256.sh) for all binaries to the file `checksum_sha256.txt`.
- [ ] [Compile and pack binaries](https://www.notion.so/jcunknwon/Cheatsheet-and-playbooks-c3b053da42114411bd27285cd065b2a6?source=copy_link#1654f105c63f803f8bfcc117395d9747) (all prefixed with `gogs_<MAJOR>.<MINOR>.<PATCH>_`, e.g. `gogs_0.12.0_`): - [ ] Upload all archives and `checksum_sha256.txt` to https://dl.gogs.io.
- [ ] macOS: `darwin_arm64.zip`, `darwin_amd64.zip`
- [ ] Linux: `linux_amd64.tar.gz`, `linux_amd64.zip`
- [ ] ARM: `linux_armv8.tar.gz`, `linux_armv8.zip`
- [ ] Windows: `windows_amd64.zip`, `windows_amd64_mws.zip`
- [ ] [Generate SHA256 checksum](https://www.notion.so/jcunknwon/Cheatsheet-and-playbooks-c3b053da42114411bd27285cd065b2a6?source=copy_link#1654f105c63f80d4a74ad8821a403f52) for all binaries to the file `checksum_sha256.txt`.
- [ ] Upload all binaries and `checksum_sha256.txt` to:
- [ ] GitHub release
- [ ] https://dl.gogs.io
- [ ] Update content of [Install from binary](https://gogs.io/docs/installation/install_from_binary).
## After release ## After release
@@ -54,5 +45,5 @@ On the `main` branch:
``` ```
- [ ] Create a new release announcement in [Discussions](https://github.com/gogs/gogs/discussions/categories/announcements). - [ ] Create a new release announcement in [Discussions](https://github.com/gogs/gogs/discussions/categories/announcements).
- [ ] Send a tweet on the [official Twitter account](https://twitter.com/GogsHQ) for the patch release. - [ ] Send a tweet on the [official Twitter account](https://twitter.com/GogsHQ) for the patch release.
- [ ] Close the patch milestone. - [ ] Close the milestone for the patch release.
- [ ] **After 14 days**, publish [GitHub security advisories](https://github.com/gogs/gogs/security) for security patches included in the release. - [ ] **After 14 days**, publish [GitHub security advisories](https://github.com/gogs/gogs/security) for security patches included in the release.

View File

@@ -19,18 +19,8 @@ jobs:
# --include-untagged-manifests: Deletes unreferenced manifests to maximize space # --include-untagged-manifests: Deletes unreferenced manifests to maximize space
doctl registry garbage-collection start --force --include-untagged-manifests doctl registry garbage-collection start --force --include-untagged-manifests
- name: Send email on failure - name: Send email on failure
uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0 uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
if: ${{ failure() }} if: ${{ failure() }}
with: with:
server_address: smtp.mailgun.org smtp_username: ${{ secrets.SMTP_USERNAME }}
server_port: 465 smtp_password: ${{ secrets.SMTP_PASSWORD }}
username: ${{ secrets.SMTP_USERNAME }}
password: ${{ secrets.SMTP_PASSWORD }}
subject: GitHub Actions (${{ github.repository }}) job result
to: github-actions-8ce6454@unknwon.io
from: GitHub Actions (${{ github.repository }})
reply_to: noreply@unknwon.io
body: |
The job "${{ github.job }}" of ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }} completed with "${{ job.status }}".
View the job run at: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}

View File

@@ -68,21 +68,11 @@ jobs:
image-ref: gogs/gogs:latest image-ref: gogs/gogs:latest
exit-code: '1' exit-code: '1'
- name: Send email on failure - name: Send email on failure
uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0 uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
if: ${{ failure() }} if: ${{ failure() }}
with: with:
server_address: smtp.mailgun.org smtp_username: ${{ secrets.SMTP_USERNAME }}
server_port: 465 smtp_password: ${{ secrets.SMTP_PASSWORD }}
username: ${{ secrets.SMTP_USERNAME }}
password: ${{ secrets.SMTP_PASSWORD }}
subject: GitHub Actions (${{ github.repository }}) job result
to: github-actions-8ce6454@unknwon.io
from: GitHub Actions (${{ github.repository }})
reply_to: noreply@unknwon.io
body: |
The job "${{ github.job }}" of ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }} completed with "${{ job.status }}".
View the job run at: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
buildx-next: buildx-next:
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'gogs/gogs' }} if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'gogs/gogs' }}
@@ -145,21 +135,11 @@ jobs:
image-ref: gogs/gogs:next-latest image-ref: gogs/gogs:next-latest
exit-code: '1' exit-code: '1'
- name: Send email on failure - name: Send email on failure
uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0 uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
if: ${{ failure() }} if: ${{ failure() }}
with: with:
server_address: smtp.mailgun.org smtp_username: ${{ secrets.SMTP_USERNAME }}
server_port: 465 smtp_password: ${{ secrets.SMTP_PASSWORD }}
username: ${{ secrets.SMTP_USERNAME }}
password: ${{ secrets.SMTP_PASSWORD }}
subject: GitHub Actions (${{ github.repository }}) job result
to: github-actions-8ce6454@unknwon.io
from: GitHub Actions (${{ github.repository }})
reply_to: noreply@unknwon.io
body: |
The job "${{ github.job }}" of ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }} completed with "${{ job.status }}".
View the job run at: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
deploy-demo: deploy-demo:
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'gogs/gogs' }} if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'gogs/gogs' }}
@@ -181,21 +161,11 @@ jobs:
kubectl rollout restart deployment gogs-demo -n gogs kubectl rollout restart deployment gogs-demo -n gogs
kubectl rollout status deployment gogs-demo -n gogs kubectl rollout status deployment gogs-demo -n gogs
- name: Send email on failure - name: Send email on failure
uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0 uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
if: ${{ failure() }} if: ${{ failure() }}
with: with:
server_address: smtp.mailgun.org smtp_username: ${{ secrets.SMTP_USERNAME }}
server_port: 465 smtp_password: ${{ secrets.SMTP_PASSWORD }}
username: ${{ secrets.SMTP_USERNAME }}
password: ${{ secrets.SMTP_PASSWORD }}
subject: GitHub Actions (${{ github.repository }}) job result
to: github-actions-8ce6454@unknwon.io
from: GitHub Actions (${{ github.repository }})
reply_to: noreply@unknwon.io
body: |
The job "${{ github.job }}" of ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }} completed with "${{ job.status }}".
View the job run at: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
buildx-pull-request: buildx-pull-request:
if: ${{ github.event_name == 'pull_request'}} if: ${{ github.event_name == 'pull_request'}}
@@ -285,8 +255,25 @@ jobs:
contents: read contents: read
packages: write packages: write
steps: steps:
- name: Compute image tag name - name: Compute image tags
run: echo "IMAGE_TAG=$(echo $GITHUB_REF_NAME | cut -c 2-)" >> $GITHUB_ENV run: |
IMAGE_TAG=$(echo $GITHUB_REF_NAME | cut -c 2-)
echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV
TAGS="gogs/gogs:$IMAGE_TAG
ghcr.io/gogs/gogs:$IMAGE_TAG"
# Add minor version tag for stable releases (no prerelease suffix per semver).
if [[ ! "$IMAGE_TAG" =~ - ]]; then
MINOR_TAG=$(echo "$IMAGE_TAG" | cut -d. -f1,2)
TAGS="$TAGS
gogs/gogs:$MINOR_TAG
ghcr.io/gogs/gogs:$MINOR_TAG"
fi
echo "TAGS<<EOF" >> $GITHUB_ENV
echo "$TAGS" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Checkout code - name: Checkout code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up QEMU - name: Set up QEMU
@@ -320,25 +307,13 @@ jobs:
context: . context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7 platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true push: true
tags: | tags: ${{ env.TAGS }}
gogs/gogs:${{ env.IMAGE_TAG }}
ghcr.io/gogs/gogs:${{ env.IMAGE_TAG }}
- name: Send email on failure - name: Send email on failure
uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0 uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
if: ${{ failure() }} if: ${{ failure() }}
with: with:
server_address: smtp.mailgun.org smtp_username: ${{ secrets.SMTP_USERNAME }}
server_port: 465 smtp_password: ${{ secrets.SMTP_PASSWORD }}
username: ${{ secrets.SMTP_USERNAME }}
password: ${{ secrets.SMTP_PASSWORD }}
subject: GitHub Actions (${{ github.repository }}) job result
to: github-actions-8ce6454@unknwon.io
from: GitHub Actions (${{ github.repository }})
reply_to: noreply@unknwon.io
body: |
The job "${{ github.job }}" of ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }} completed with "${{ job.status }}".
View the job run at: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
# Updates to the following section needs to be synced to all release branches within their lifecycles. # Updates to the following section needs to be synced to all release branches within their lifecycles.
buildx-next-release: buildx-next-release:
@@ -349,8 +324,25 @@ jobs:
contents: read contents: read
packages: write packages: write
steps: steps:
- name: Compute image tag name - name: Compute image tags
run: echo "IMAGE_TAG=$(echo $GITHUB_REF_NAME | cut -c 2-)" >> $GITHUB_ENV run: |
IMAGE_TAG=$(echo $GITHUB_REF_NAME | cut -c 2-)
echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV
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).
if [[ ! "$IMAGE_TAG" =~ - ]]; then
MINOR_TAG=$(echo "$IMAGE_TAG" | cut -d. -f1,2)
TAGS="$TAGS
gogs/gogs:next-$MINOR_TAG
ghcr.io/gogs/gogs:next-$MINOR_TAG"
fi
echo "TAGS<<EOF" >> $GITHUB_ENV
echo "$TAGS" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Checkout code - name: Checkout code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up QEMU - name: Set up QEMU
@@ -385,25 +377,13 @@ jobs:
file: Dockerfile.next file: Dockerfile.next
platforms: linux/amd64,linux/arm64,linux/arm/v7 platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true push: true
tags: | tags: ${{ env.TAGS }}
gogs/gogs:next-${{ env.IMAGE_TAG }}
ghcr.io/gogs/gogs:next-${{ env.IMAGE_TAG }}
- name: Send email on failure - name: Send email on failure
uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0 uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
if: ${{ failure() }} if: ${{ failure() }}
with: with:
server_address: smtp.mailgun.org smtp_username: ${{ secrets.SMTP_USERNAME }}
server_port: 465 smtp_password: ${{ secrets.SMTP_PASSWORD }}
username: ${{ secrets.SMTP_USERNAME }}
password: ${{ secrets.SMTP_PASSWORD }}
subject: GitHub Actions (${{ github.repository }}) job result
to: github-actions-8ce6454@unknwon.io
from: GitHub Actions (${{ github.repository }})
reply_to: noreply@unknwon.io
body: |
The job "${{ github.job }}" of ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }} completed with "${{ job.status }}".
View the job run at: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
digitalocean-gc: digitalocean-gc:
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'gogs/gogs' }} if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'gogs/gogs' }}

View File

@@ -72,28 +72,16 @@ jobs:
with: with:
go-version: ${{ matrix.go-version }} go-version: ${{ matrix.go-version }}
- name: Run tests with coverage - name: Run tests with coverage
run: go test -shuffle=on -v -race -coverprofile=coverage -covermode=atomic ./... run: |
- name: Upload coverage report to Codecov go test -shuffle=on -v -race -coverprofile=coverage -covermode=atomic -json ./... > test-report.json
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 go install github.com/mfridman/tparse@latest
with: tparse -all -file=test-report.json
file: ./coverage
flags: unittests
- name: Send email on failure - name: Send email on failure
uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0 uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
if: ${{ failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' }} if: ${{ failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
with: with:
server_address: smtp.mailgun.org smtp_username: ${{ secrets.SMTP_USERNAME }}
server_port: 465 smtp_password: ${{ secrets.SMTP_PASSWORD }}
username: ${{ secrets.SMTP_USERNAME }}
password: ${{ secrets.SMTP_PASSWORD }}
subject: GitHub Actions (${{ github.repository }}) job result
to: github-actions-8ce6454@unknwon.io
from: GitHub Actions (${{ github.repository }})
reply_to: noreply@unknwon.io
body: |
The job "${{ github.job }}" of ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }} completed with "${{ job.status }}".
View the job run at: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
# Running tests with race detection consumes too much memory on Windows, # Running tests with race detection consumes too much memory on Windows,
# see https://github.com/golang/go/issues/46099 for details. # see https://github.com/golang/go/issues/46099 for details.
@@ -113,27 +101,12 @@ jobs:
go-version: ${{ matrix.go-version }} go-version: ${{ matrix.go-version }}
- name: Run tests with coverage - name: Run tests with coverage
run: go test -shuffle=on -v -coverprofile=coverage -covermode=atomic ./... run: go test -shuffle=on -v -coverprofile=coverage -covermode=atomic ./...
- name: Upload coverage report to Codecov
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
with:
file: ./coverage
flags: unittests
- name: Send email on failure - name: Send email on failure
uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0 uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
if: ${{ failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' }} if: ${{ failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
with: with:
server_address: smtp.mailgun.org smtp_username: ${{ secrets.SMTP_USERNAME }}
server_port: 465 smtp_password: ${{ secrets.SMTP_PASSWORD }}
username: ${{ secrets.SMTP_USERNAME }}
password: ${{ secrets.SMTP_PASSWORD }}
subject: GitHub Actions (${{ github.repository }}) job result
to: github-actions-8ce6454@unknwon.io
from: GitHub Actions (${{ github.repository }})
reply_to: noreply@unknwon.io
body: |
The job "${{ github.job }}" of ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }} completed with "${{ job.status }}".
View the job run at: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
postgres: postgres:
name: Postgres name: Postgres
@@ -162,7 +135,10 @@ jobs:
with: with:
go-version: ${{ matrix.go-version }} go-version: ${{ matrix.go-version }}
- name: Run tests with coverage - name: Run tests with coverage
run: go test -shuffle=on -v -race -coverprofile=coverage -covermode=atomic ./internal/database/... run: |
go test -shuffle=on -v -race -coverprofile=coverage -covermode=atomic -json ./internal/database/... > test-report.json
go install github.com/mfridman/tparse@latest
tparse -all -file=test-report.json
env: env:
GOGS_DATABASE_TYPE: postgres GOGS_DATABASE_TYPE: postgres
PGPORT: 5432 PGPORT: 5432
@@ -188,29 +164,13 @@ jobs:
with: with:
go-version: ${{ matrix.go-version }} go-version: ${{ matrix.go-version }}
- name: Run tests with coverage - name: Run tests with coverage
run: go test -shuffle=on -v -race -coverprofile=coverage -covermode=atomic ./internal/database/... run: |
go test -shuffle=on -v -race -coverprofile=coverage -covermode=atomic -json ./internal/database/... > test-report.json
go install github.com/mfridman/tparse@latest
tparse -all -file=test-report.json
env: env:
GOGS_DATABASE_TYPE: mysql GOGS_DATABASE_TYPE: mysql
MYSQL_USER: root MYSQL_USER: root
MYSQL_PASSWORD: root MYSQL_PASSWORD: root
MYSQL_HOST: localhost MYSQL_HOST: localhost
MYSQL_PORT: 3306 MYSQL_PORT: 3306
sqlite-go:
name: SQLite - Go
strategy:
matrix:
go-version: [ 1.25.x ]
platform: [ ubuntu-latest ]
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Install Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: ${{ matrix.go-version }}
- name: Run tests with coverage
run: go test -shuffle=on -v -race -parallel=1 -coverprofile=coverage -covermode=atomic ./internal/database/...
env:
GOGS_DATABASE_TYPE: sqlite

146
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,146 @@
name: Release
on:
release:
types: [published]
push:
branches:
- main
pull_request:
paths:
- '.github/workflows/release.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
GOPROXY: "https://proxy.golang.org"
permissions:
contents: write
jobs:
build:
name: Build ${{ matrix.goos }}/${{ matrix.goarch }}${{ matrix.suffix }}
runs-on: ubuntu-latest
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}
steps:
- name: Checkout code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: 1.25.x
- name: Determine version
id: version
run: |
if [ "${{ github.event_name }}" = "release" ]; then
echo "version=${{ github.event.release.tag_name }}" | sed 's/version=v/version=/' >> "$GITHUB_OUTPUT"
echo "release_tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT"
elif [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo "version=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
echo "release_tag=latest-commit-build" >> "$GITHUB_OUTPUT"
else
echo "version=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
echo "release_tag=release-archive-testing" >> "$GITHUB_OUTPUT"
fi
- name: Build binary
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: "0"
run: |
BINARY_NAME="gogs"
if [ "${{ matrix.goos }}" = "windows" ]; then
BINARY_NAME="gogs.exe"
fi
TAGS_FLAG=""
if [ -n "${{ matrix.tags }}" ]; then
TAGS_FLAG="-tags ${{ matrix.tags }}"
fi
go build -v \
-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)\"
" \
$TAGS_FLAG \
-trimpath -o "$BINARY_NAME" ./cmd/gogs
- name: Prepare archive contents
run: |
mkdir -p dist/gogs
BINARY_NAME="gogs"
if [ "${{ matrix.goos }}" = "windows" ]; then
BINARY_NAME="gogs.exe"
fi
cp "$BINARY_NAME" dist/gogs/
cp LICENSE README.md README_ZH.md dist/gogs/
cp -r scripts dist/gogs/
- name: Create archives
working-directory: dist
run: |
VERSION="${{ steps.version.outputs.version }}"
ARCHIVE_BASE="gogs_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}${{ matrix.suffix }}"
zip -r "${ARCHIVE_BASE}.zip" gogs
if [ "${{ matrix.goos }}" = "linux" ]; then
tar -czvf "${ARCHIVE_BASE}.tar.gz" gogs
fi
- name: Upload to release
env:
GH_TOKEN: ${{ github.token }}
run: |
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
if [ "${{ github.event_name }}" != "release" ]; then
git tag -f "$RELEASE_TAG"
git push origin "$RELEASE_TAG" --force || true
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_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
PATTERN="_${{ matrix.goos }}_${{ matrix.goarch }}${{ matrix.suffix }}\."
gh release view "$RELEASE_TAG" --json assets --jq ".assets[].name" | grep "$PATTERN" | while read -r asset; do
gh release delete-asset "$RELEASE_TAG" "$asset" --yes || true
done
gh release upload "$RELEASE_TAG" dist/gogs_*.zip --clobber
if [ "${{ matrix.goos }}" = "linux" ]; then
gh release upload "$RELEASE_TAG" dist/gogs_*.tar.gz --clobber
fi
notify-failure:
name: Notify on failure
runs-on: ubuntu-latest
needs: [build]
if: ${{ failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
steps:
- name: Send email on failure
uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
with:
smtp_username: ${{ secrets.SMTP_USERNAME }}
smtp_password: ${{ secrets.SMTP_PASSWORD }}

24
.gitignore vendored
View File

@@ -1,18 +1,16 @@
.DS_Store # Build artifacts
*.db .bin/
*.log dist/
# Runtime data
log/ log/
custom/ custom/
data/ data/
# Configuration and application files
.idea/ .idea/
*.iml .task/
public/img/avatar/
*.exe
*.exe~
/gogs
profile/
*.pem
output*
/release
.task
.envrc .envrc
# System junk
.DS_Store

View File

@@ -1,6 +1,7 @@
## Core principles ## Core principles
- When you see changes made outside your knowledge, use the current version as your new starting point. Do not blindly overwrite those changes or you suck. Even if you have to update the code, always respect the god damn pattern in the surrounding context! - Stop telling me "You're right", it just shows how incompetent you are. Do it right on your first try, fact-check and review after changes. If you are not sure, ask for help.
- When you see changes made outside your knowledge, use the current version as your new starting point. Do not blindly overwrite those changes or you suck. Even if you have to update the code, always respect the pattern in the surrounding context!
## Style and mechanics ## Style and mechanics
@@ -15,6 +16,17 @@ This applies to all texts, including but not limited to UI, documentation, code
- Use `github.com/cockroachdb/errors` for error handling. - 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. - 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.
## 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.
## Tool-use guidance ## Tool-use guidance
- Use `gh` CLI to access information on github.com that is not publicly available. - Use `gh` CLI to access information on github.com that is not publicly available.
## 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.

View File

@@ -2,26 +2,38 @@
All notable changes to Gogs are documented in this file. All notable changes to Gogs are documented in this file.
## 0.14.0+dev (`main`) ## 0.15.0+dev (`main`)
## 0.14.1
### Added ### Added
- Support comparing tags in addition to branches. [#6141](https://github.com/gogs/gogs/issues/6141)
- Show file name in browser tab title when viewing files. [#5896](https://github.com/gogs/gogs/pull/5896)
- Support using TLS for Redis session provider using `[session] PROVIDER_CONFIG = ...,tls=true`. [#7860](https://github.com/gogs/gogs/pull/7860) - Support using TLS for Redis session provider using `[session] PROVIDER_CONFIG = ...,tls=true`. [#7860](https://github.com/gogs/gogs/pull/7860)
- Support expanading values in `app.ini` from environment variables, e.g. `[database] PASSWORD = ${DATABASE_PASSWORD}`. [#8057](https://github.com/gogs/gogs/pull/8057) - Support expanading values in `app.ini` from environment variables, e.g. `[database] PASSWORD = ${DATABASE_PASSWORD}`. [#8057](https://github.com/gogs/gogs/pull/8057)
- Support custom logout URL that users get redirected to after sign out using `[auth] CUSTOM_LOGOUT_URL`. [#8089](https://github.com/gogs/gogs/pull/8089) - Support custom logout URL that users get redirected to after sign out using `[auth] CUSTOM_LOGOUT_URL`. [#8089](https://github.com/gogs/gogs/pull/8089)
- Start publishing next-generation, security-focused Docker image via `gogs/gogs:next-latest`, which will become the default image distribution (`gogs/gogs:latest`) starting 0.15.0. While not all container options support have been added in the next-generation image, the use of current legacy Docker image is deprecated, it will be published as `gogs/gogs:legacy-latest` starting 0.15.0, and be completely removed starting 0.16.0. [#8061](https://github.com/gogs/gogs/pull/8061) - Start publishing next-generation, security-focused Docker image via `gogs/gogs:next-latest`, which will become the default image distribution (`gogs/gogs:latest`) starting 0.16.0. While not all container options support have been added in the next-generation image, the use of current legacy Docker image is deprecated, it will be published as `gogs/gogs:legacy-latest` starting 0.16.0, and be completely removed no earlier than 0.17.0. [#8061](https://github.com/gogs/gogs/pull/8061)
### Changed ### Changed
- The required Go version to compile source code changed to 1.25. - The required Go version to compile source code changed to 1.25.
- The build tag `cert` has been removed, and the `gogs cert` subcommand is now always available. [#7883](https://github.com/gogs/gogs/pull/7883) - The build tag `cert` has been removed, and the `gogs cert` subcommand is now always available. [#7883](https://github.com/gogs/gogs/pull/7883)
- Switched to pure-Go SQLite driver, CGO is no longer required to compile Gogs. [#7882](https://github.com/gogs/gogs/issues/7882)
- Updated Mermaid JS to 11.9.0. [#8009](https://github.com/gogs/gogs/pull/8009) - Updated Mermaid JS to 11.9.0. [#8009](https://github.com/gogs/gogs/pull/8009)
- Halt the repository creation and leave the directory untouched if the repository root already exists. [#8091](https://github.com/gogs/gogs/pull/8091) - Halt the repository creation and leave the directory untouched if the repository root already exists. [#8091](https://github.com/gogs/gogs/pull/8091)
### Fixed ### Fixed
- _Security:_ Unauthenticated file upload. [#8128](https://github.com/gogs/gogs/pull/8128) - [GHSA-fc3h-92p8-h36f](https://github.com/gogs/gogs/security/advisories/GHSA-fc3h-92p8-h36f)
- _Security:_ Protected branch bypass in web UI. [#8124](https://github.com/gogs/gogs/pull/8124) - [GHSA-2c6v-8r3v-gh6p](https://github.com/gogs/gogs/security/advisories/GHSA-2c6v-8r3v-gh6p)
- _Security:_ Authorization bypass allows cross-repository label modification. [#8123](https://github.com/gogs/gogs/pull/8123) - [GHSA-cv22-72px-f4gh](https://github.com/gogs/gogs/security/advisories/GHSA-cv22-72px-f4gh)
- _Security:_ Cross-repository comment deletion. [#8119](https://github.com/gogs/gogs/pull/8119) - [GHSA-jj5m-h57j-5gv7](https://github.com/gogs/gogs/security/advisories/GHSA-jj5m-h57j-5gv7)
- 500 error on repository watchers and stargazers pages when using MSSQL. [#5482](https://github.com/gogs/gogs/issues/5482)
- Submodules using `ssh://` protocol and a port number are not rendered correctly. [#4941](https://github.com/gogs/gogs/issues/4941) - Submodules using `ssh://` protocol and a port number are not rendered correctly. [#4941](https://github.com/gogs/gogs/issues/4941)
- Missing link to user profile on the first commit in commits history page. [#7404](https://github.com/gogs/gogs/issues/7404) - Missing link to user profile on the first commit in commits history page. [#7404](https://github.com/gogs/gogs/issues/7404)
- Unable to delete or display files with special characters in their names. [#7596](https://github.com/gogs/gogs/issues/7596)
- Docker healthcheck fails when `HTTP_PROXY` or `HTTPS_PROXY` environment variables are set. [#7529](https://github.com/gogs/gogs/issues/7529)
## 0.13.4 ## 0.13.4

1
CLAUDE.md Symbolic link
View File

@@ -0,0 +1 @@
AGENTS.md

View File

@@ -25,20 +25,20 @@ RUN apk --no-cache --no-progress add \
tzdata \ tzdata \
rsync rsync
ENV GOGS_CUSTOM /data/gogs ENV GOGS_CUSTOM=/data/gogs
# Configure LibC Name Service # Configure LibC Name Service
COPY docker/nsswitch.conf /etc/nsswitch.conf COPY docker/nsswitch.conf /etc/nsswitch.conf
WORKDIR /app/gogs WORKDIR /app/gogs
COPY docker ./docker COPY docker ./docker
COPY --from=binarybuilder /gogs.io/gogs/gogs . COPY --from=binarybuilder /gogs.io/gogs/.bin/gogs .
RUN ./docker/build/finalize.sh RUN ./docker/build/finalize.sh
# Configure Docker Container # Configure Docker Container
VOLUME ["/data", "/backup"] VOLUME ["/data", "/backup"]
EXPOSE 22 3000 EXPOSE 22 3000
HEALTHCHECK CMD (curl -o /dev/null -sS http://localhost:3000/healthcheck) || exit 1 HEALTHCHECK CMD (curl --noproxy localhost -o /dev/null -sS http://localhost:3000/healthcheck) || exit 1
ENTRYPOINT ["/app/gogs/docker/start.sh"] ENTRYPOINT ["/app/gogs/docker/start.sh"]
CMD ["/usr/bin/s6-svscan", "/app/gogs/docker/s6/"] CMD ["/usr/bin/s6-svscan", "/app/gogs/docker/s6/"]

View File

@@ -31,7 +31,7 @@ RUN apk --no-cache --no-progress add \
ENV GOGS_CUSTOM=/data/gogs ENV GOGS_CUSTOM=/data/gogs
WORKDIR /app/gogs WORKDIR /app/gogs
COPY --from=binarybuilder /gogs.io/gogs/gogs . COPY --from=binarybuilder /gogs.io/gogs/.bin/gogs .
COPY docker-next/start.sh . COPY docker-next/start.sh .
RUN chmod +x start.sh && \ RUN chmod +x start.sh && \
mkdir -p /data && \ mkdir -p /data && \
@@ -41,7 +41,7 @@ RUN chmod +x start.sh && \
# Configure Docker Container # Configure Docker Container
VOLUME ["/data", "/backup"] VOLUME ["/data", "/backup"]
EXPOSE 22 3000 EXPOSE 22 3000
HEALTHCHECK CMD (curl -o /dev/null -sS http://localhost:3000/healthcheck) || exit 1 HEALTHCHECK CMD (curl --noproxy localhost -o /dev/null -sS http://localhost:3000/healthcheck) || exit 1
# Run as non-root user by default for better K8s security context support. # Run as non-root user by default for better K8s security context support.
USER git:git USER git:git

View File

@@ -2,9 +2,9 @@
## Supported versions ## Supported versions
Only the latest minor version releases are supported (>= 0.13) for accepting vulnerability reports and patching fixes. Only the latest minor version releases are supported (e.g., 0.14) for patching vulnerabilities. You can find the latest minor version in the [GitHub releases](https://github.com/gogs/gogs/releases) page.
Existing vulnerability reports are being tracked in [GitHub Security Advisories](https://github.com/gogs/gogs/security/advisories). Existing vulnerability reports are being tracked in [GitHub Security Advisories](https://github.com/gogs/gogs/security/advisories). Not all accepted GHSA are published.
## Vulnerability lifecycle ## Vulnerability lifecycle
@@ -14,6 +14,7 @@ Existing vulnerability reports are being tracked in [GitHub Security Advisories]
1. Report an advisory for the vulnerability. 1. Report an advisory for the vulnerability.
- Please be aware that **only advisories reported in plain English** will be reviewed. - Please be aware that **only advisories reported in plain English** will be reviewed.
- We DO NOT accept vulnerabilities cannot be reproduced on the latest `main` commit.
1. Project maintainers review the advisory: 1. Project maintainers review the advisory:
- Ask clarifying questions - Ask clarifying questions
- Make sure there was no prior advisory exists for the same vulnerability - Make sure there was no prior advisory exists for the same vulnerability

View File

@@ -10,8 +10,10 @@ tasks:
web: web:
desc: Build the binary and start the web server desc: Build the binary and start the web server
deps: [build] deps: [build]
env:
GOGS_WORK_DIR: '{{.ROOT_DIR}}'
cmds: cmds:
- ./gogs web - .bin/gogs web
build: build:
desc: Build the binary desc: Build the binary
@@ -22,7 +24,7 @@ tasks:
-X "{{.PKG_PATH}}.BuildCommit={{.BUILD_COMMIT}}" -X "{{.PKG_PATH}}.BuildCommit={{.BUILD_COMMIT}}"
' '
-tags '{{.TAGS}}' -tags '{{.TAGS}}'
-trimpath -o gogs{{.BINARY_EXT}} -trimpath -o .bin/gogs{{.BINARY_EXT}} ./cmd/gogs
vars: vars:
PKG_PATH: gogs.io/gogs/internal/conf PKG_PATH: gogs.io/gogs/internal/conf
BUILD_TIME: BUILD_TIME:
@@ -31,7 +33,7 @@ tasks:
sh: git rev-parse HEAD sh: git rev-parse HEAD
sources: sources:
- go.mod - go.mod
- gogs.go - cmd/gogs/*.go
- internal/**/*.go - internal/**/*.go
- conf/**/* - conf/**/*
- public/**/* - public/**/*
@@ -59,18 +61,6 @@ tasks:
cmds: cmds:
- find . -name "*.DS_Store" -type f -delete - find . -name "*.DS_Store" -type f -delete
release:
desc: Build the binary and pack resources to a ZIP file
deps: [build]
cmds:
- rm -rf {{.RELEASE_GOGS}}
- mkdir -p {{.RELEASE_GOGS}}
- cp -r gogs{{.BINARY_EXT}} LICENSE README.md README_ZH.md scripts {{.RELEASE_GOGS}}
- cd {{.RELEASE_ROOT}} && zip -r gogs.zip "gogs"
vars:
RELEASE_ROOT: release
RELEASE_GOGS: release/gogs
less: less:
desc: Generate CSS from LESS files desc: Generate CSS from LESS files
cmds: cmds:
@@ -99,3 +89,8 @@ tasks:
dropdb "$dbname" dropdb "$dbname"
echo "dropped $dbname" echo "dropped $dbname"
done done
lint:
desc: Run all linters
cmds:
- golangci-lint run

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"context" "context"
@@ -14,7 +14,7 @@ import (
) )
var ( var (
Admin = cli.Command{ adminCommand = cli.Command{
Name: "admin", Name: "admin",
Usage: "Perform admin operations on command line", Usage: "Perform admin operations on command line",
Description: `Allow using internal logic of Gogs without hacking into the source code Description: `Allow using internal logic of Gogs without hacking into the source code

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"context" "context"
@@ -20,7 +20,7 @@ import (
"gogs.io/gogs/internal/osutil" "gogs.io/gogs/internal/osutil"
) )
var Backup = cli.Command{ var backupCommand = cli.Command{
Name: "backup", Name: "backup",
Usage: "Backup files and database", Usage: "Backup files and database",
Description: `Backup dumps and compresses all related files and database into zip file, Description: `Backup dumps and compresses all related files and database into zip file,

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package cmd package main
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
@@ -22,7 +22,7 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
) )
var Cert = cli.Command{ var certCommand = cli.Command{
Name: "cert", Name: "cert",
Usage: "Generate self-signed certificate", Usage: "Generate self-signed certificate",
Description: `Generate a self-signed X.509 certificate for a TLS server. Description: `Generate a self-signed X.509 certificate for a TLS server.

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"time" "time"
@@ -21,7 +21,6 @@ func boolFlag(name, usage string) cli.BoolFlag {
} }
} }
//nolint:deadcode,unused
func intFlag(name string, value int, usage string) cli.IntFlag { func intFlag(name string, value int, usage string) cli.IntFlag {
return cli.IntFlag{ return cli.IntFlag{
Name: name, Name: name,
@@ -30,7 +29,6 @@ func intFlag(name string, value int, usage string) cli.IntFlag {
} }
} }
//nolint:deadcode,unused
func durationFlag(name string, value time.Duration, usage string) cli.DurationFlag { func durationFlag(name string, value time.Duration, usage string) cli.DurationFlag {
return cli.DurationFlag{ return cli.DurationFlag{
Name: name, Name: name,

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"bufio" "bufio"
@@ -24,7 +24,7 @@ import (
) )
var ( var (
Hook = cli.Command{ hookCommand = cli.Command{
Name: "hook", Name: "hook",
Usage: "Delegate commands to corresponding Git hooks", Usage: "Delegate commands to corresponding Git hooks",
Description: "All sub-commands should only be called by Git", Description: "All sub-commands should only be called by Git",

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"bufio" "bufio"
@@ -16,7 +16,7 @@ import (
) )
var ( var (
Import = cli.Command{ importCommand = cli.Command{
Name: "import", Name: "import",
Usage: "Import portable data as local Gogs data", Usage: "Import portable data as local Gogs data",
Description: `Allow user import data from other Gogs installations to local instance Description: `Allow user import data from other Gogs installations to local instance

View File

@@ -1,5 +1,3 @@
//go:build go1.18
// Gogs is a painless self-hosted Git Service. // Gogs is a painless self-hosted Git Service.
package main package main
@@ -9,12 +7,11 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
log "unknwon.dev/clog/v2" log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/cmd"
"gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/conf"
) )
func init() { func init() {
conf.App.Version = "0.14.0+dev" conf.App.Version = "0.15.0+dev"
} }
func main() { func main() {
@@ -23,14 +20,14 @@ func main() {
app.Usage = "A painless self-hosted Git service" app.Usage = "A painless self-hosted Git service"
app.Version = conf.App.Version app.Version = conf.App.Version
app.Commands = []cli.Command{ app.Commands = []cli.Command{
cmd.Web, webCommand,
cmd.Serv, servCommand,
cmd.Hook, hookCommand,
cmd.Cert, certCommand,
cmd.Admin, adminCommand,
cmd.Import, importCommand,
cmd.Backup, backupCommand,
cmd.Restore, restoreCommand,
} }
if err := app.Run(os.Args); err != nil { if err := app.Run(os.Args); err != nil {
log.Fatal("Failed to start application: %v", err) log.Fatal("Failed to start application: %v", err)

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"context" "context"
@@ -18,7 +18,7 @@ import (
"gogs.io/gogs/internal/semverutil" "gogs.io/gogs/internal/semverutil"
) )
var Restore = cli.Command{ var restoreCommand = cli.Command{
Name: "restore", Name: "restore",
Usage: "Restore files and database from backup", Usage: "Restore files and database from backup",
Description: `Restore imports all related files and database from a backup archive. Description: `Restore imports all related files and database from a backup archive.

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"context" "context"
@@ -21,7 +21,7 @@ const (
accessDeniedMessage = "Repository does not exist or you do not have access" accessDeniedMessage = "Repository does not exist or you do not have access"
) )
var Serv = cli.Command{ var servCommand = cli.Command{
Name: "serv", Name: "serv",
Usage: "This command should only be called by SSH shell", Usage: "This command should only be called by SSH shell",
Description: `Serv provide access auth for repositories`, Description: `Serv provide access auth for repositories`,

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"crypto/tls" "crypto/tls"
@@ -30,6 +30,7 @@ import (
"gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/context" "gogs.io/gogs/internal/context"
"gogs.io/gogs/internal/database" "gogs.io/gogs/internal/database"
"gogs.io/gogs/internal/embeddedpg"
"gogs.io/gogs/internal/form" "gogs.io/gogs/internal/form"
"gogs.io/gogs/internal/osutil" "gogs.io/gogs/internal/osutil"
"gogs.io/gogs/internal/route" "gogs.io/gogs/internal/route"
@@ -45,7 +46,7 @@ import (
"gogs.io/gogs/templates" "gogs.io/gogs/templates"
) )
var Web = cli.Command{ var webCommand = cli.Command{
Name: "web", Name: "web",
Usage: "Start web server", Usage: "Start web server",
Description: `Gogs web server is the only thing you need to run, Description: `Gogs web server is the only thing you need to run,
@@ -54,6 +55,7 @@ and it takes care of all the other things for you`,
Flags: []cli.Flag{ Flags: []cli.Flag{
stringFlag("port, p", "3000", "Temporary port number to prevent conflict"), stringFlag("port, p", "3000", "Temporary port number to prevent conflict"),
stringFlag("config, c", "", "Custom configuration file path"), stringFlag("config, c", "", "Custom configuration file path"),
boolFlag("embedded-postgres", "Use embedded PostgreSQL database"),
}, },
} }
@@ -160,7 +162,29 @@ func newMacaron() *macaron.Macaron {
} }
func runWeb(c *cli.Context) error { func runWeb(c *cli.Context) error {
err := route.GlobalInit(c.String("config")) // Initialize configuration first to get WorkDir
err := conf.Init(c.String("config"))
if err != nil {
log.Fatal("Failed to initialize configuration: %v", err)
}
conf.InitLogging(false)
var localPg *embeddedpg.LocalPostgres
if c.Bool("embedded-postgres") {
localPg = embeddedpg.Initialize(conf.WorkDir())
if err := localPg.Launch(); err != nil {
log.Fatal("Failed to launch embedded postgres: %v", err)
}
defer func() {
if err := localPg.Shutdown(); err != nil {
log.Error("Failed to shutdown embedded postgres: %v", err)
}
}()
localPg.ConfigureGlobalDatabase()
}
err = route.GlobalInit(c.String("config"))
if err != nil { if err != nil {
log.Fatal("Failed to initialize application: %v", err) log.Fatal("Failed to initialize application: %v", err)
} }
@@ -329,9 +353,12 @@ func runWeb(c *cli.Context) error {
return return
} }
}) })
}, ignSignIn)
m.Group("", func() {
m.Post("/issues/attachments", repo.UploadIssueAttachment) m.Post("/issues/attachments", repo.UploadIssueAttachment)
m.Post("/releases/attachments", repo.UploadReleaseAttachment) m.Post("/releases/attachments", repo.UploadReleaseAttachment)
}, ignSignIn) }, reqSignIn)
m.Group("/:username", func() { m.Group("/:username", func() {
m.Post("/action/:action", user.Action) m.Post("/action/:action", user.Action)
@@ -476,7 +503,7 @@ func runWeb(c *cli.Context) error {
m.Get("/milestones", repo.Milestones) m.Get("/milestones", repo.Milestones)
}, ignSignIn, context.RepoAssignment(true)) }, ignSignIn, context.RepoAssignment(true))
m.Group("/:username/:reponame", func() { m.Group("/:username/:reponame", func() {
// FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest. // FIXME: should use different URLs but mostly same logic for comments of issue and pull request.
// So they can apply their own enable/disable logic on routers. // So they can apply their own enable/disable logic on routers.
m.Group("/issues", func() { m.Group("/issues", func() {
m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue). m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue).
@@ -501,7 +528,7 @@ func runWeb(c *cli.Context) error {
}, ignSignIn, context.RepoAssignment(false, true)) }, ignSignIn, context.RepoAssignment(false, true))
m.Group("/:username/:reponame", func() { m.Group("/:username/:reponame", func() {
// FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest. // FIXME: should use different URLs but mostly same logic for comments of issue and pull request.
// So they can apply their own enable/disable logic on routers. // So they can apply their own enable/disable logic on routers.
m.Group("/issues", func() { m.Group("/issues", func() {
m.Group("/:index", func() { m.Group("/:index", func() {

View File

@@ -1,16 +0,0 @@
coverage:
range: "60...95"
status:
project:
default:
threshold: 1%
informational: true
patch:
default:
only_pulls: true
informational: true
comment:
layout: 'diff'
github_checks: false

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Застинали клонове
branches.all=Всички клонове branches.all=Всички клонове
branches.updated_by=Актуализирани %[1]s от %[2]s branches.updated_by=Актуализирани %[1]s от %[2]s
branches.change_default_branch=Промяна на клон по подразбиране branches.change_default_branch=Промяна на клон по подразбиране
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Нов файл editor.new_file=Нов файл
editor.upload_file=Качи файл editor.upload_file=Качи файл
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'. config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Zastaralé větve
branches.all=Všechny větve branches.all=Všechny větve
branches.updated_by=%[2]s změnil %[1]s branches.updated_by=%[2]s změnil %[1]s
branches.change_default_branch=Změnit výchozí větev branches.change_default_branch=Změnit výchozí větev
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Nový soubor editor.new_file=Nový soubor
editor.upload_file=Nahrát soubor editor.upload_file=Nahrát soubor
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'. config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Alte Branches
branches.all=Alle Branches branches.all=Alle Branches
branches.updated_by=Aktualisiert %[1]s von %[2]s branches.updated_by=Aktualisiert %[1]s von %[2]s
branches.change_default_branch=Ändere Standard-Branch branches.change_default_branch=Ändere Standard-Branch
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Neue Datei editor.new_file=Neue Datei
editor.upload_file=Datei hochladen editor.upload_file=Datei hochladen
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Fehler beim Senden der Test-E-Mail an '%s': %v
config.email.test_mail_sent=Test-E-Mail wurde an '%s ' gesendet. config.email.test_mail_sent=Test-E-Mail wurde an '%s ' gesendet.
config.auth_config=Authentifizierungskonfiguration config.auth_config=Authentifizierungskonfiguration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Aktivierungscode Lebensdauer config.auth.activate_code_lives=Aktivierungscode Lebensdauer
config.auth.reset_password_code_lives=Gültigkeitsdauer Zurücksetzungs-Code config.auth.reset_password_code_lives=Gültigkeitsdauer Zurücksetzungs-Code
config.auth.require_email_confirm=E-Mail-Bestätigung erforderlich config.auth.require_email_confirm=E-Mail-Bestätigung erforderlich

View File

@@ -501,6 +501,8 @@ branches.stale_branches=Stale Branches
branches.all=All Branches branches.all=All Branches
branches.updated_by=Updated %[1]s by %[2]s branches.updated_by=Updated %[1]s by %[2]s
branches.change_default_branch=Change Default Branch branches.change_default_branch=Change Default Branch
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=New file editor.new_file=New file
editor.upload_file=Upload file editor.upload_file=Upload file
@@ -1346,6 +1348,7 @@ config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives

View File

@@ -494,6 +494,8 @@ branches.stale_branches = Stale Branches
branches.all = All Branches branches.all = All Branches
branches.updated_by = Updated %[1]s by %[2]s branches.updated_by = Updated %[1]s by %[2]s
branches.change_default_branch = Change Default Branch branches.change_default_branch = Change Default Branch
branches.default_deletion_not_allowed = Cannot delete the default branch.
branches.protected_deletion_not_allowed = Cannot delete a protected branch.
editor.new_file = New file editor.new_file = New file
editor.upload_file = Upload file editor.upload_file = Upload file

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Ramas Viejas
branches.all=Todas las Ramas branches.all=Todas las Ramas
branches.updated_by=%[1]s actualizado por %[2]s branches.updated_by=%[1]s actualizado por %[2]s
branches.change_default_branch=Cambiar la Rama por Defecto branches.change_default_branch=Cambiar la Rama por Defecto
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Nuevo archivo editor.new_file=Nuevo archivo
editor.upload_file=Subir archivo editor.upload_file=Subir archivo
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Error al enviar correo electrónico de prueba a '%
config.email.test_mail_sent=Test email has been sent to '%s'. config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation config.auth.require_email_confirm=Require email confirmation

View File

@@ -496,6 +496,8 @@ branches.stale_branches=شاخه های قدیمی
branches.all=همه شاخه branches.all=همه شاخه
branches.updated_by=%[1]s به روزشده توسط %[2]s branches.updated_by=%[1]s به روزشده توسط %[2]s
branches.change_default_branch=تغییر شاخه ی پیش فرض branches.change_default_branch=تغییر شاخه ی پیش فرض
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=پرونده جدید editor.new_file=پرونده جدید
editor.upload_file=بارگذاری پرونده editor.upload_file=بارگذاری پرونده
@@ -1276,6 +1278,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'. config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Passivoituneet haarat
branches.all=Kaikki haarat branches.all=Kaikki haarat
branches.updated_by=Päivitetty %[1]s %[2]s branches.updated_by=Päivitetty %[1]s %[2]s
branches.change_default_branch=Muuta oletushaaraa branches.change_default_branch=Muuta oletushaaraa
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Uusi tiedosto editor.new_file=Uusi tiedosto
editor.upload_file=Liitä tiedosto editor.upload_file=Liitä tiedosto
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Testisähköpostin lähettäminen vastaanottajalle
config.email.test_mail_sent=Testi sähköposti on lähetetty vastaanottajalle '%s'. config.email.test_mail_sent=Testi sähköposti on lähetetty vastaanottajalle '%s'.
config.auth_config=Todennuksen asetukset config.auth_config=Todennuksen asetukset
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Aktiivinen koodi elämät ennen vanhenemista config.auth.activate_code_lives=Aktiivinen koodi elämät ennen vanhenemista
config.auth.reset_password_code_lives=Nollaa salasana koodi elämät config.auth.reset_password_code_lives=Nollaa salasana koodi elämät
config.auth.require_email_confirm=Vaadi sähköpostivahvistus config.auth.require_email_confirm=Vaadi sähköpostivahvistus

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Branches stagnantes
branches.all=Toutes les Branches branches.all=Toutes les Branches
branches.updated_by=Mise à jour %[1]s par %[2]s branches.updated_by=Mise à jour %[1]s par %[2]s
branches.change_default_branch=Changer la Branche par Défaut branches.change_default_branch=Changer la Branche par Défaut
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Nouveau fichier editor.new_file=Nouveau fichier
editor.upload_file=Téléverser un fichier editor.upload_file=Téléverser un fichier
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Impossible d'envoyer un e-mail de test à '%s' :
config.email.test_mail_sent=Un e-mail de test à été envoyé à '%s'. config.email.test_mail_sent=Un e-mail de test à été envoyé à '%s'.
config.auth_config=Configuration de l'authentification config.auth_config=Configuration de l'authentification
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activer les vies sur les codes config.auth.activate_code_lives=Activer les vies sur les codes
config.auth.reset_password_code_lives=Vies sur les codes de réinitialisation des mots de passes config.auth.reset_password_code_lives=Vies sur les codes de réinitialisation des mots de passes
config.auth.require_email_confirm=Nécessite une confirmation par e-mail config.auth.require_email_confirm=Nécessite une confirmation par e-mail

View File

@@ -495,6 +495,8 @@ branches.stale_branches=Stale Branches
branches.all=All Branches branches.all=All Branches
branches.updated_by=Updated %[1]s by %[2]s branches.updated_by=Updated %[1]s by %[2]s
branches.change_default_branch=Change Default Branch branches.change_default_branch=Change Default Branch
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Novo arquivo editor.new_file=Novo arquivo
editor.upload_file=Subir arquivo editor.upload_file=Subir arquivo
@@ -1275,6 +1277,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'. config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Elavult ágak
branches.all=Minden ág branches.all=Minden ág
branches.updated_by=Frissítve ekkor: %[1]s %[2]s által branches.updated_by=Frissítve ekkor: %[1]s %[2]s által
branches.change_default_branch=Alapértelmezett ág megváltoztatása branches.change_default_branch=Alapértelmezett ág megváltoztatása
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Új fájl editor.new_file=Új fájl
editor.upload_file=Fájl feltöltése editor.upload_file=Fájl feltöltése
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Nem sikerült kiküldeni a teszt e-mailt '%s'-nek:
config.email.test_mail_sent=Teszt e-mail kiküldve '%s'-nek. config.email.test_mail_sent=Teszt e-mail kiküldve '%s'-nek.
config.auth_config=Hitelesítési beállítások config.auth_config=Hitelesítési beállítások
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Jelszó visszaállítási kód élettartama config.auth.reset_password_code_lives=Jelszó visszaállítási kód élettartama
config.auth.require_email_confirm=E-mail megerősítés szükségessé tétele config.auth.require_email_confirm=E-mail megerősítés szükségessé tétele

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Cabang Basi
branches.all=Semua Cabang branches.all=Semua Cabang
branches.updated_by=Diperbarui %[1]s oleh %[2]s branches.updated_by=Diperbarui %[1]s oleh %[2]s
branches.change_default_branch=Ubah Cabang Default branches.change_default_branch=Ubah Cabang Default
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Berkas baru editor.new_file=Berkas baru
editor.upload_file=Unggah Berkas editor.upload_file=Unggah Berkas
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Gagal mengirim surel uji ke '%s': %v
config.email.test_mail_sent=Surel uji telah dikirim ke '%s'. config.email.test_mail_sent=Surel uji telah dikirim ke '%s'.
config.auth_config=Konfigurasi otentikasi config.auth_config=Konfigurasi otentikasi
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Perlu konfirmasi surel config.auth.require_email_confirm=Perlu konfirmasi surel

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Stale Branches
branches.all=Tutti i rami (branch) branches.all=Tutti i rami (branch)
branches.updated_by=Updated %[1]s by %[2]s branches.updated_by=Updated %[1]s by %[2]s
branches.change_default_branch=Cambia branch di default branches.change_default_branch=Cambia branch di default
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Nuovo file editor.new_file=Nuovo file
editor.upload_file=Carica File editor.upload_file=Carica File
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'. config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=古いブランチ
branches.all=すべてのブランチ branches.all=すべてのブランチ
branches.updated_by=%[1]s が %[2]s によって更新されました branches.updated_by=%[1]s が %[2]s によって更新されました
branches.change_default_branch=デフォルトブランチの変更 branches.change_default_branch=デフォルトブランチの変更
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=新規ファイル editor.new_file=新規ファイル
editor.upload_file=ファイルをアップロード editor.upload_file=ファイルをアップロード
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'. config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation config.auth.require_email_confirm=Require email confirmation

View File

@@ -495,6 +495,8 @@ branches.stale_branches=오래된 브랜치
branches.all=모든 브랜치 branches.all=모든 브랜치
branches.updated_by=%[2]s이 %[1]s를 업데이트 branches.updated_by=%[2]s이 %[1]s를 업데이트
branches.change_default_branch=기본 브랜치 변경 branches.change_default_branch=기본 브랜치 변경
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=파일 생성 editor.new_file=파일 생성
editor.upload_file=파일 업로드 editor.upload_file=파일 업로드
@@ -1276,6 +1278,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent='%s'로 테스트 이메일을 보냈습니다. config.email.test_mail_sent='%s'로 테스트 이메일을 보냈습니다.
config.auth_config=Authentication configuration config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=이메일 인증 필요 config.auth.require_email_confirm=이메일 인증 필요

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Pamests atzars
branches.all=Visi atzari branches.all=Visi atzari
branches.updated_by=%[2]s atjaunoja %[1]s branches.updated_by=%[2]s atjaunoja %[1]s
branches.change_default_branch=Mainīt noklusēto atzaru branches.change_default_branch=Mainīt noklusēto atzaru
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Jauns fails editor.new_file=Jauns fails
editor.upload_file=Augšupielādēt failu editor.upload_file=Augšupielādēt failu
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'. config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Хуучирсан салаанууд
branches.all=Бүх салаанууд branches.all=Бүх салаанууд
branches.updated_by=Шинэчлэгдсэн %[1]s by %[2]s branches.updated_by=Шинэчлэгдсэн %[1]s by %[2]s
branches.change_default_branch=Анхны салаа өөрчлөх branches.change_default_branch=Анхны салаа өөрчлөх
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Шинэ файл editor.new_file=Шинэ файл
editor.upload_file=Файл хуулах editor.upload_file=Файл хуулах
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Туршилтын имэйлийг '%s': %v рү
config.email.test_mail_sent=Туршилтын имэйл '%s' рүү илгээгдлээ. config.email.test_mail_sent=Туршилтын имэйл '%s' рүү илгээгдлээ.
config.auth_config=Authentication тохиргоо config.auth_config=Authentication тохиргоо
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Кодын ашиглалтыг идэвхжүүлэх config.auth.activate_code_lives=Кодын ашиглалтыг идэвхжүүлэх
config.auth.reset_password_code_lives=Нууц үгийн кодыг шинэчлэх config.auth.reset_password_code_lives=Нууц үгийн кодыг шинэчлэх
config.auth.require_email_confirm=Имэйлээр баталгаажуулахыг шаардана config.auth.require_email_confirm=Имэйлээр баталгаажуулахыг шаардана

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Stale Branches
branches.all=All Branches branches.all=All Branches
branches.updated_by=Updated %[1]s by %[2]s branches.updated_by=Updated %[1]s by %[2]s
branches.change_default_branch=Change Default Branch branches.change_default_branch=Change Default Branch
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Nieuw bestand editor.new_file=Nieuw bestand
editor.upload_file=Bestand uploaden editor.upload_file=Bestand uploaden
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'. config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Stare gałęzie
branches.all=Wszystkie gałęzie branches.all=Wszystkie gałęzie
branches.updated_by=Zaktualizowano %[1]s przez %[2]s branches.updated_by=Zaktualizowano %[1]s przez %[2]s
branches.change_default_branch=Zmiana domyślnej gałęzi branches.change_default_branch=Zmiana domyślnej gałęzi
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Nowy plik editor.new_file=Nowy plik
editor.upload_file=Załaduj plik editor.upload_file=Załaduj plik
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Nie udało się wysłać wiadomości testowej do '
config.email.test_mail_sent=Wiadomość testowa została wysłana do '%s'. config.email.test_mail_sent=Wiadomość testowa została wysłana do '%s'.
config.auth_config=Konfiguracja uwierzytelniania config.auth_config=Konfiguracja uwierzytelniania
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Wymagaj potwierdzenia adresu e-mail config.auth.require_email_confirm=Wymagaj potwierdzenia adresu e-mail

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Branches obsoletos
branches.all=Todos os branches branches.all=Todos os branches
branches.updated_by=Atualizado %[1]s por %[2]s branches.updated_by=Atualizado %[1]s por %[2]s
branches.change_default_branch=Modificar branch padrão branches.change_default_branch=Modificar branch padrão
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Novo arquivo editor.new_file=Novo arquivo
editor.upload_file=Enviar arquivo editor.upload_file=Enviar arquivo
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'. config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation config.auth.require_email_confirm=Require email confirmation

View File

@@ -446,7 +446,7 @@ migrate.clone_address_desc=Isto pode ser um URL de HTTP/HTTPS/GIT.
migrate.clone_address_desc_import_local=Você também pode migrar um repositório pelo caminho do servidor local. migrate.clone_address_desc_import_local=Você também pode migrar um repositório pelo caminho do servidor local.
migrate.permission_denied=Não está autorizado a importar repositórios locais. migrate.permission_denied=Não está autorizado a importar repositórios locais.
migrate.invalid_local_path=Caminho local inválido, o caminho não existe ou não é um directório. migrate.invalid_local_path=Caminho local inválido, o caminho não existe ou não é um directório.
migrate.clone_address_resolved_to_blocked_local_address=Clone address resolved to a local network address that is implicitly blocked. migrate.clone_address_resolved_to_blocked_local_address=Clonar endereço resolvido para um endereço de rede local implicitamente bloqueado.
migrate.failed=Migração falhada: %v migrate.failed=Migração falhada: %v
mirror_from=mirror de mirror_from=mirror de
@@ -494,6 +494,8 @@ branches.stale_branches=Ramificações Obsoletas
branches.all=Todas as Ramificações branches.all=Todas as Ramificações
branches.updated_by=Atualizado %[1]s por %[2]s branches.updated_by=Atualizado %[1]s por %[2]s
branches.change_default_branch=Mudar Ramificação Predefinida branches.change_default_branch=Mudar Ramificação Predefinida
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Novo Ficheiro editor.new_file=Novo Ficheiro
editor.upload_file=Enviar ficheiro editor.upload_file=Enviar ficheiro
@@ -1202,119 +1204,120 @@ config.ssh.port=Porta exposta
config.ssh.root_path=Caminho para a raíz config.ssh.root_path=Caminho para a raíz
config.ssh.keygen_path=Localização do gerador de chaves criptográficas config.ssh.keygen_path=Localização do gerador de chaves criptográficas
config.ssh.key_test_path=Localização do teste das chaves criptográficas config.ssh.key_test_path=Localização do teste das chaves criptográficas
config.ssh.minimum_key_size_check=Minimum key size check config.ssh.minimum_key_size_check=Verificação de tamanho mínimo da chave
config.ssh.minimum_key_sizes=Minimum key sizes config.ssh.minimum_key_sizes=Tamanhos mínimos de chaves
config.ssh.rewrite_authorized_keys_at_start=Rewrite "authorized_keys" at start config.ssh.rewrite_authorized_keys_at_start=Rewrite "authorized_keys" at start
config.ssh.start_builtin_server=Start builtin server config.ssh.start_builtin_server=Iniciar servidor embutido
config.ssh.listen_host=Servidor config.ssh.listen_host=Servidor
config.ssh.listen_port=Porta do servidor config.ssh.listen_port=Porta do servidor
config.ssh.server_ciphers=Cifras do servidor config.ssh.server_ciphers=Cifras do servidor
config.ssh.server_macs=Server MACs config.ssh.server_macs=MACs do servidor
config.ssh.server_algorithms=Server algorithms config.ssh.server_algorithms=Algoritmos do servidor
config.repo_config=Configuração de repositório config.repo_config=Configuração de repositório
config.repo.root_path=Localização base config.repo.root_path=Localização base
config.repo.script_type=Tipo de script config.repo.script_type=Tipo de script
config.repo.ansi_chatset=ANSI charset config.repo.ansi_chatset=ANSI charset
config.repo.force_private=Force private config.repo.force_private=Forçar privado
config.repo.max_creation_limit=Max creation limit config.repo.max_creation_limit=Limite máximo de criação
config.repo.preferred_licenses=Preferred licenses config.repo.preferred_licenses=Licenças preferidas
config.repo.disable_http_git=Disable HTTP Git config.repo.disable_http_git=Desativar Git HTTP
config.repo.enable_local_path_migration=Enable local path migration config.repo.enable_local_path_migration=Ativar a migração de caminho local
config.repo.enable_raw_file_render_mode=Enable raw file render mode config.repo.enable_raw_file_render_mode=Ativar o modo de renderização do ficheiro bruto
config.repo.commits_fetch_concurrency=Commits fetch concurrency config.repo.commits_fetch_concurrency=Commits fetch concurrency
config.repo.editor.line_wrap_extensions=Editor line wrap extensions config.repo.editor.line_wrap_extensions=Extensões de quebra automática de linha do editor
config.repo.editor.previewable_file_modes=Editor previewable file modes config.repo.editor.previewable_file_modes=Editor previewable file modes
config.repo.upload.enabled=Upload enabled config.repo.upload.enabled=Envio ativado
config.repo.upload.temp_path=Upload temporary path config.repo.upload.temp_path=Caminho temporário para envios
config.repo.upload.allowed_types=Upload allowed types config.repo.upload.allowed_types=Tipos de envios permitidos
config.repo.upload.file_max_size=Upload file size limit config.repo.upload.file_max_size=Tamanho limite de ficheiros enviados
config.repo.upload.max_files=Upload files limit config.repo.upload.max_files=Quantidade limite de ficheiros enviados
config.db_config=Configuração da base de dados config.db_config=Configuração da base de dados
config.db.type=Type config.db.type=Tipo
config.db.host=Host config.db.host=Anfitrião
config.db.name=Name config.db.name=Nome
config.db.schema=Schema config.db.schema=Esquema
config.db.schema_helper=(for "postgres" only) config.db.schema_helper=(apenas para "postgres")
config.db.user=User config.db.user=Utilizador
config.db.ssl_mode=SSL mode config.db.ssl_mode=Modo SSL
config.db.ssl_mode_helper=(for "postgres" only) config.db.ssl_mode_helper=(apenas para "postgres")
config.db.path=Path config.db.path=Caminho
config.db.path_helper=(for "sqlite3"only) config.db.path_helper=(apenas para "sqlite3")
config.db.max_open_conns=Maximum open connections config.db.max_open_conns=Máximo de conexões abertas
config.db.max_idle_conns=Maximum idle connections config.db.max_idle_conns=Máximo de conexões ociosas
config.security_config=Security configuration config.security_config=Configuração da segurança
config.security.login_remember_days=Login remember days config.security.login_remember_days=Dias lembrados de login
config.security.cookie_remember_name=Remember cookie config.security.cookie_remember_name=Lembrar do cookie
config.security.cookie_username=Username cookie config.security.cookie_username=Cookie do nome do utilizador
config.security.cookie_secure=Enable secure cookie config.security.cookie_secure=Ativar cookie seguro
config.security.reverse_proxy_auth_user=Reverse proxy authentication header config.security.reverse_proxy_auth_user=Cabeçalho de autenticação de proxy reverso
config.security.enable_login_status_cookie=Enable login status cookie config.security.enable_login_status_cookie=Enable login status cookie
config.security.login_status_cookie_name=Login status cookie config.security.login_status_cookie_name=Login status cookie
config.security.local_network_allowlist=Local network allowlist config.security.local_network_allowlist=Local network allowlist
config.email_config=Email configuration config.email_config=Configuração de E-mail
config.email.enabled=Enabled config.email.enabled=Ativado
config.email.subject_prefix=Subject prefix config.email.subject_prefix=Prefixo do assunto
config.email.host=Host config.email.host=Anfitrião
config.email.from=From config.email.from=De
config.email.user=User config.email.user=Utilizador
config.email.disable_helo=Disable HELO config.email.disable_helo=Desativar HELO
config.email.helo_hostname=HELO hostname config.email.helo_hostname=Nome de anfitrião HELO
config.email.skip_verify=Skip certificate verify config.email.skip_verify=Skip certificate verify
config.email.use_certificate=Use custom certificate config.email.use_certificate=Usar certificado personalizado
config.email.cert_file=Certificate file config.email.cert_file=Ficheiro de certificado criptográfico
config.email.key_file=Key file config.email.key_file=Ficheiro da chave criptográfica
config.email.use_plain_text=Use plain text config.email.use_plain_text=Usar texto simples
config.email.add_plain_text_alt=Add plain text alternative config.email.add_plain_text_alt=Adicionar alternativa de texto simples
config.email.send_test_mail=Send test email config.email.send_test_mail=Enviar e-mail de teste
config.email.test_mail_failed=Failed to send test email to '%s': %v config.email.test_mail_failed=Falhou o envio do e-mail de teste para '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'. config.email.test_mail_sent=O e-mail de teste foi enviado para '%s'.
config.auth_config=Authentication configuration config.auth_config=Configuração da autenticação
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation config.auth.require_email_confirm=Exigir confirmação por e-mail
config.auth.require_sign_in_view=Require sign in view config.auth.require_sign_in_view=Exigir login para ver
config.auth.disable_registration=Disable registration config.auth.disable_registration=Desativar registo
config.auth.enable_registration_captcha=Enable registration captcha config.auth.enable_registration_captcha=Ativar captcha para registar
config.auth.enable_reverse_proxy_authentication=Enable reverse proxy authentication config.auth.enable_reverse_proxy_authentication=Ativar autenticação do proxy reverso
config.auth.enable_reverse_proxy_auto_registration=Enable reverse proxy auto registration config.auth.enable_reverse_proxy_auto_registration=Ativar o registo automático do proxy reverso
config.auth.reverse_proxy_authentication_header=Reverse proxy authentication header config.auth.reverse_proxy_authentication_header=Cabeçalho de autenticação de proxy reverso
config.user_config=User configuration config.user_config=Configuração do utilizador
config.user.enable_email_notify=Enable email notification config.user.enable_email_notify=Ativar a notificação por e-mail
config.session_config=Configuração de sessão config.session_config=Configuração de sessão
config.session.provider=Provider config.session.provider=Provedor
config.session.provider_config=Provider config config.session.provider_config=Configuração do provedor
config.session.cookie_name=Cookie config.session.cookie_name=Cookie
config.session.https_only=HTTPS only config.session.https_only=Somente HTTPS
config.session.gc_interval=GC interval config.session.gc_interval=GC interval
config.session.max_life_time=Max life time config.session.max_life_time=Max life time
config.session.csrf_cookie_name=CSRF cookie config.session.csrf_cookie_name=CSRF cookie
config.cache_config=Configuração de cache config.cache_config=Configuração de cache
config.cache.adapter=Adapter config.cache.adapter=Adaptador
config.cache.interval=GC interval config.cache.interval=Intervalo de GC
config.cache.host=Host config.cache.host=Anfitrião
config.http_config=Configuração HTTP config.http_config=Configuração HTTP
config.http.access_control_allow_origin=Access control allow origin config.http.access_control_allow_origin=Access control allow origin
config.attachment_config=Attachment configuration config.attachment_config=Configuração de anexos
config.attachment.enabled=Enabled config.attachment.enabled=Ativado
config.attachment.path=Path config.attachment.path=Caminho
config.attachment.allowed_types=Allowed types config.attachment.allowed_types=Tipos permitidos
config.attachment.max_size=Size limit config.attachment.max_size=Limite de tamanho
config.attachment.max_files=Files limit config.attachment.max_files=Limite de ficheiros
config.release_config=Release configuration config.release_config=Release configuration
config.release.attachment.enabled=Attachment enabled config.release.attachment.enabled=Attachment enabled
config.release.attachment.allowed_types=Attachment allowed types config.release.attachment.allowed_types=Attachment allowed types
config.release.attachment.max_size=Attachment size limit config.release.attachment.max_size=Tamanho máximo dos anexos
config.release.attachment.max_files=Attachment files limit config.release.attachment.max_files=Attachment files limit
config.picture_config=Configuração de imagem config.picture_config=Configuração de imagem
@@ -1325,10 +1328,10 @@ config.picture.disable_gravatar=Disable Gravatar
config.picture.enable_federated_avatar=Enable federated avatars config.picture.enable_federated_avatar=Enable federated avatars
config.mirror_config=Mirror configuration config.mirror_config=Mirror configuration
config.mirror.default_interval=Default interval config.mirror.default_interval=Intervalo predefinido
config.webhook_config=Configuração de WebHook config.webhook_config=Configuração de WebHook
config.webhook.types=Types config.webhook.types=Tipos
config.webhook.deliver_timeout=Deliver timeout config.webhook.deliver_timeout=Deliver timeout
config.webhook.skip_tls_verify=Skip TLS verify config.webhook.skip_tls_verify=Skip TLS verify

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Ramuri vechi
branches.all=Toate ramurile branches.all=Toate ramurile
branches.updated_by=Actualizat %[1]s prin %[2]s branches.updated_by=Actualizat %[1]s prin %[2]s
branches.change_default_branch=Schimbarea ramurii implicite branches.change_default_branch=Schimbarea ramurii implicite
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Fișier nou editor.new_file=Fișier nou
editor.upload_file=Încărcați fișier editor.upload_file=Încărcați fișier
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Nu a reușit să trimită un e-mail de test către
config.email.test_mail_sent=E-mailul de test a fost trimis la '%s'. config.email.test_mail_sent=E-mailul de test a fost trimis la '%s'.
config.auth_config=Configurația de autentificare config.auth_config=Configurația de autentificare
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activați viețile de cod config.auth.activate_code_lives=Activați viețile de cod
config.auth.reset_password_code_lives=Resetează viețile codului parolei config.auth.reset_password_code_lives=Resetează viețile codului parolei
config.auth.require_email_confirm=Solicită confirmarea prin e-mail config.auth.require_email_confirm=Solicită confirmarea prin e-mail

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Устаревшие ветки
branches.all=Все ветки branches.all=Все ветки
branches.updated_by=Обновлено %[1]s пользователем %[2]s branches.updated_by=Обновлено %[1]s пользователем %[2]s
branches.change_default_branch=Change Default Branch branches.change_default_branch=Change Default Branch
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Новый файл editor.new_file=Новый файл
editor.upload_file=Загрузить файл editor.upload_file=Загрузить файл
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Не удалось отправить тесто
config.email.test_mail_sent=Тестовое письмо было отправлено на %s config.email.test_mail_sent=Тестовое письмо было отправлено на %s
config.auth_config=Конфигурация аутентификации config.auth_config=Конфигурация аутентификации
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Срок действия кода сброса пароля config.auth.reset_password_code_lives=Срок действия кода сброса пароля
config.auth.require_email_confirm=Требовать подтверждение по электронной почте config.auth.require_email_confirm=Требовать подтверждение по электронной почте

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Zastaralé vetvy
branches.all=Všetky vetvy branches.all=Všetky vetvy
branches.updated_by=%[2]s zmenil %[1]s branches.updated_by=%[2]s zmenil %[1]s
branches.change_default_branch=Zmeniť základnú vetvu branches.change_default_branch=Zmeniť základnú vetvu
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Nový súbor editor.new_file=Nový súbor
editor.upload_file=Nahrať súbor editor.upload_file=Nahrať súbor
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'. config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Застареле гране
branches.all=Све гране branches.all=Све гране
branches.updated_by=Ажуриран %[1]s од %[2]s branches.updated_by=Ажуриран %[1]s од %[2]s
branches.change_default_branch=Промените подразумевану грану branches.change_default_branch=Промените подразумевану грану
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Нова датотека editor.new_file=Нова датотека
editor.upload_file=Отпреми датотеку editor.upload_file=Отпреми датотеку
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'. config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Djärva brancher
branches.all=Alla brancher branches.all=Alla brancher
branches.updated_by=Uppdaterade %[1]s med %[2]s branches.updated_by=Uppdaterade %[1]s med %[2]s
branches.change_default_branch=Ändra standard branch branches.change_default_branch=Ändra standard branch
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Ny fil editor.new_file=Ny fil
editor.upload_file=Ladda upp fil editor.upload_file=Ladda upp fil
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'. config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Eskimiş Bölümler
branches.all=Bütün Bölümler branches.all=Bütün Bölümler
branches.updated_by=%[2]s tarafından %[1]s güncellendi branches.updated_by=%[2]s tarafından %[1]s güncellendi
branches.change_default_branch=Varsayılan Bölümü Değiştir branches.change_default_branch=Varsayılan Bölümü Değiştir
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Yeni dosya editor.new_file=Yeni dosya
editor.upload_file=Dosyayı yükle editor.upload_file=Dosyayı yükle
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'. config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Застарілі гілки
branches.all=Усі гілки branches.all=Усі гілки
branches.updated_by=Оновлено %[1]s з %[2]s branches.updated_by=Оновлено %[1]s з %[2]s
branches.change_default_branch=Гілку за замовчуванням змінено branches.change_default_branch=Гілку за замовчуванням змінено
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Новий файл editor.new_file=Новий файл
editor.upload_file=Завантажити файл editor.upload_file=Завантажити файл
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Помилка відправлення пробн
config.email.test_mail_sent=Пробного листа було відправлено до '%s'. config.email.test_mail_sent=Пробного листа було відправлено до '%s'.
config.auth_config=Налаштування аутентифікації config.auth_config=Налаштування аутентифікації
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Активувати код підтвердження config.auth.activate_code_lives=Активувати код підтвердження
config.auth.reset_password_code_lives=Термін придатності кода при скиданні пароля config.auth.reset_password_code_lives=Термін придатності кода при скиданні пароля
config.auth.require_email_confirm=Вимагає підтвердження електронною поштою config.auth.require_email_confirm=Вимагає підтвердження електронною поштою

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Các nhánh cũ
branches.all=Tất cả các nhánh branches.all=Tất cả các nhánh
branches.updated_by=Updated %[1]s by %[2]s branches.updated_by=Updated %[1]s by %[2]s
branches.change_default_branch=Thay đổi nhánh mặc định branches.change_default_branch=Thay đổi nhánh mặc định
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=Tập tin mới editor.new_file=Tập tin mới
editor.upload_file=Tải tập tin lên editor.upload_file=Tải tập tin lên
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Gửi email kiểm tra đến '%s':%v thất bại
config.email.test_mail_sent=Email kiểm tra đã được gửi đến '%s'. config.email.test_mail_sent=Email kiểm tra đã được gửi đến '%s'.
config.auth_config=Cấu hình xác thực config.auth_config=Cấu hình xác thực
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Yêu cầu xác nhận email config.auth.require_email_confirm=Yêu cầu xác nhận email

View File

@@ -494,6 +494,8 @@ branches.stale_branches=陈旧分支
branches.all=所有分支 branches.all=所有分支
branches.updated_by=由 %[2]s 更新于 %[1]s branches.updated_by=由 %[2]s 更新于 %[1]s
branches.change_default_branch=更改默认分支 branches.change_default_branch=更改默认分支
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=新的文件 editor.new_file=新的文件
editor.upload_file=上传文件 editor.upload_file=上传文件
@@ -1275,6 +1277,7 @@ config.email.test_mail_failed=发送测试邮件至 '%s' 时失败:%v
config.email.test_mail_sent=测试邮件已经发送至 '%s'。 config.email.test_mail_sent=测试邮件已经发送至 '%s'。
config.auth_config=认证配置 config.auth_config=认证配置
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=激活用户链接有效期 config.auth.activate_code_lives=激活用户链接有效期
config.auth.reset_password_code_lives=重置密码链接有效期 config.auth.reset_password_code_lives=重置密码链接有效期
config.auth.require_email_confirm=注册邮件确认 config.auth.require_email_confirm=注册邮件确认

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Stale Branches
branches.all=All Branches branches.all=All Branches
branches.updated_by=Updated %[1]s by %[2]s branches.updated_by=Updated %[1]s by %[2]s
branches.change_default_branch=Change Default Branch branches.change_default_branch=Change Default Branch
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=New file editor.new_file=New file
editor.upload_file=Upload file editor.upload_file=Upload file
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'. config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=陳舊分支
branches.all=所有分支 branches.all=所有分支
branches.updated_by=%[2]s 更新了 %[1]s branches.updated_by=%[2]s 更新了 %[1]s
branches.change_default_branch=變更預設分支 branches.change_default_branch=變更預設分支
branches.default_deletion_not_allowed=Cannot delete the default branch.
branches.protected_deletion_not_allowed=Cannot delete a protected branch.
editor.new_file=開新檔案 editor.new_file=開新檔案
editor.upload_file=上傳檔案 editor.upload_file=上傳檔案
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=發送測試郵件至 '%s' 時失敗:%v
config.email.test_mail_sent=測試電子郵件已發送到 '%s'。 config.email.test_mail_sent=測試電子郵件已發送到 '%s'。
config.auth_config=Authentication configuration config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation config.auth.require_email_confirm=Require email confirmation

View File

@@ -1,7 +1,7 @@
# Docker for Gogs (Next Generation) # Docker for Gogs (Next Generation)
> [!NOTE] > [!NOTE]
> This is the next-generation, security-focused Docker image. This will become the default image distribution (`gogs/gogs:latest`) starting 0.15.0. > This is the next-generation, security-focused Docker image. This will become the default image distribution (`gogs/gogs:latest`) starting 0.16.0.
![Docker pulls](https://img.shields.io/docker/pulls/gogs/gogs?logo=docker&style=for-the-badge) ![Docker pulls](https://img.shields.io/docker/pulls/gogs/gogs?logo=docker&style=for-the-badge)

View File

@@ -1,10 +1,15 @@
# Docker for Gogs # Docker for Gogs
> [!WARNING] > [!WARNING]
> This is now the legacy Docker image that lacks modern security best practices. It will be published as `gogs/gogs:legacy-latest` starting 0.15.0, and be completely removed starting 0.16.0. > This is now the legacy Docker image that lacks modern security best practices. It will be published as `gogs/gogs:legacy-latest` starting 0.16.0, and be completely removed no earlier than 0.17.0.
> >
> To use the next-generation, security-focused Docker image, see [docker-next/README.md](../docker-next/README.md). > To use the next-generation, security-focused Docker image, see [docker-next/README.md](../docker-next/README.md).
> [!IMPORTANT]
> Image versions:
> - Every released version has its own tag , e.g., `gogs/gogs:0.13.4`, and a tag points to the latest patch of the minor version, e.g., `gogs/gogs:0.13`.
> - The `latest` tag is the image version built from the latest `main` branch.
![Docker pulls](https://img.shields.io/docker/pulls/gogs/gogs?logo=docker&style=for-the-badge) ![Docker pulls](https://img.shields.io/docker/pulls/gogs/gogs?logo=docker&style=for-the-badge)
Visit [Docker Hub](https://hub.docker.com/u/gogs) or [GitHub Container registry](https://github.com/gogs/gogs/pkgs/container/gogs) to see all available images and tags. Visit [Docker Hub](https://hub.docker.com/u/gogs) or [GitHub Container registry](https://github.com/gogs/gogs/pkgs/container/gogs) to see all available images and tags.

View File

@@ -0,0 +1,131 @@
# Table "access"
```
Field | Column | PostgreSQL | MySQL | SQLite3
--------+---------+-----------------+-----------------------+-----------------------
ID | id | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER AUTOINCREMENT
UserID | user_id | BIGINT NOT NULL | BIGINT NOT NULL | INTEGER NOT NULL
RepoID | repo_id | BIGINT NOT NULL | BIGINT NOT NULL | INTEGER NOT NULL
Mode | mode | BIGINT NOT NULL | BIGINT NOT NULL | INTEGER NOT NULL
Primary keys: id
Indexes:
"access_user_repo_unique" UNIQUE (user_id, repo_id)
```
# Table "access_token"
```
Field | Column | PostgreSQL | MySQL | SQLite3
-------------+--------------+-----------------------------+-----------------------------+-----------------------------
ID | id | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER AUTOINCREMENT
UserID | uid | BIGINT | BIGINT | INTEGER
Name | name | TEXT | LONGTEXT | TEXT
Sha1 | sha1 | VARCHAR(40) UNIQUE | VARCHAR(40) UNIQUE | VARCHAR(40) UNIQUE
SHA256 | sha256 | VARCHAR(64) NOT NULL UNIQUE | VARCHAR(64) NOT NULL UNIQUE | VARCHAR(64) NOT NULL UNIQUE
CreatedUnix | created_unix | BIGINT | BIGINT | INTEGER
UpdatedUnix | updated_unix | BIGINT | BIGINT | INTEGER
Primary keys: id
Indexes:
"idx_access_token_user_id" (uid)
```
# Table "action"
```
Field | Column | PostgreSQL | MySQL | SQLite3
--------------+----------------+--------------------------------+--------------------------------+--------------------------------
ID | id | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER AUTOINCREMENT
UserID | user_id | BIGINT | BIGINT | INTEGER
OpType | op_type | BIGINT | BIGINT | INTEGER
ActUserID | act_user_id | BIGINT | BIGINT | INTEGER
ActUserName | act_user_name | TEXT | LONGTEXT | TEXT
RepoID | repo_id | BIGINT | BIGINT | INTEGER
RepoUserName | repo_user_name | TEXT | LONGTEXT | TEXT
RepoName | repo_name | TEXT | LONGTEXT | TEXT
RefName | ref_name | TEXT | LONGTEXT | TEXT
IsPrivate | is_private | BOOLEAN NOT NULL DEFAULT FALSE | BOOLEAN NOT NULL DEFAULT FALSE | NUMERIC NOT NULL DEFAULT FALSE
Content | content | TEXT | LONGTEXT | TEXT
CreatedUnix | created_unix | BIGINT | BIGINT | INTEGER
Primary keys: id
Indexes:
"idx_action_repo_id" (repo_id)
"idx_action_user_id" (user_id)
```
# Table "email_address"
```
Field | Column | PostgreSQL | MySQL | SQLite3
-------------+--------------+--------------------------------+--------------------------------+--------------------------------
ID | id | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER AUTOINCREMENT
UserID | uid | BIGINT NOT NULL | BIGINT NOT NULL | INTEGER NOT NULL
Email | email | VARCHAR(254) NOT NULL | VARCHAR(254) NOT NULL | TEXT NOT NULL
IsActivated | is_activated | BOOLEAN NOT NULL DEFAULT FALSE | BOOLEAN NOT NULL DEFAULT FALSE | NUMERIC NOT NULL DEFAULT FALSE
Primary keys: id
Indexes:
"email_address_user_email_unique" UNIQUE (uid, email)
"idx_email_address_user_id" (uid)
```
# Table "follow"
```
Field | Column | PostgreSQL | MySQL | SQLite3
----------+-----------+-----------------+-----------------------+-----------------------
ID | id | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER AUTOINCREMENT
UserID | user_id | BIGINT NOT NULL | BIGINT NOT NULL | INTEGER NOT NULL
FollowID | follow_id | BIGINT NOT NULL | BIGINT NOT NULL | INTEGER NOT NULL
Primary keys: id
Indexes:
"follow_user_follow_unique" UNIQUE (user_id, follow_id)
```
# Table "lfs_object"
```
Field | Column | PostgreSQL | MySQL | SQLite3
-----------+------------+----------------------+----------------------+-------------------
RepoID | repo_id | BIGINT | BIGINT | INTEGER
OID | oid | TEXT | VARCHAR(191) | TEXT
Size | size | BIGINT NOT NULL | BIGINT NOT NULL | INTEGER NOT NULL
Storage | storage | TEXT NOT NULL | LONGTEXT NOT NULL | TEXT NOT NULL
CreatedAt | created_at | TIMESTAMPTZ NOT NULL | DATETIME(3) NOT NULL | DATETIME NOT NULL
Primary keys: repo_id, oid
```
# Table "login_source"
```
Field | Column | PostgreSQL | MySQL | SQLite3
-------------+--------------+------------------+-----------------------+-----------------------
ID | id | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER AUTOINCREMENT
Type | type | BIGINT | BIGINT | INTEGER
Name | name | TEXT UNIQUE | VARCHAR(191) UNIQUE | TEXT UNIQUE
IsActived | is_actived | BOOLEAN NOT NULL | BOOLEAN NOT NULL | NUMERIC NOT NULL
IsDefault | is_default | BOOLEAN | BOOLEAN | NUMERIC
Config | cfg | TEXT | TEXT | TEXT
CreatedUnix | created_unix | BIGINT | BIGINT | INTEGER
UpdatedUnix | updated_unix | BIGINT | BIGINT | INTEGER
Primary keys: id
```
# Table "notice"
```
Field | Column | PostgreSQL | MySQL | SQLite3
-------------+--------------+------------+-----------------------+-----------------------
ID | id | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER AUTOINCREMENT
Type | type | BIGINT | BIGINT | INTEGER
Description | description | TEXT | TEXT | TEXT
CreatedUnix | created_unix | BIGINT | BIGINT | INTEGER
Primary keys: id
```

View File

@@ -6,7 +6,7 @@
1. Run the `import` subcommand: 1. Run the `import` subcommand:
``` ```
$ ./gogs import locale --source <path to the unzipped directory> --target ./conf/locale $ ./.bin/gogs import locale --source <path to the unzipped directory> --target ./conf/locale
Locale files has been successfully imported! Locale files has been successfully imported!
``` ```

View File

@@ -1,131 +0,0 @@
# Table "access"
```
FIELD | COLUMN | POSTGRESQL | MYSQL | SQLITE3
---------+---------+-----------------+-----------------------+-------------------
ID | id | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER
UserID | user_id | BIGINT NOT NULL | BIGINT NOT NULL | INTEGER NOT NULL
RepoID | repo_id | BIGINT NOT NULL | BIGINT NOT NULL | INTEGER NOT NULL
Mode | mode | BIGINT NOT NULL | BIGINT NOT NULL | INTEGER NOT NULL
Primary keys: id
Indexes:
"access_user_repo_unique" UNIQUE (user_id, repo_id)
```
# Table "access_token"
```
FIELD | COLUMN | POSTGRESQL | MYSQL | SQLITE3
--------------+--------------+-----------------------------+-----------------------------+------------------------------
ID | id | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER
UserID | uid | BIGINT | BIGINT | INTEGER
Name | name | TEXT | LONGTEXT | TEXT
Sha1 | sha1 | VARCHAR(40) UNIQUE | VARCHAR(40) UNIQUE | VARCHAR(40) UNIQUE
SHA256 | sha256 | VARCHAR(64) NOT NULL UNIQUE | VARCHAR(64) NOT NULL UNIQUE | VARCHAR(64) NOT NULL UNIQUE
CreatedUnix | created_unix | BIGINT | BIGINT | INTEGER
UpdatedUnix | updated_unix | BIGINT | BIGINT | INTEGER
Primary keys: id
Indexes:
"idx_access_token_user_id" (uid)
```
# Table "action"
```
FIELD | COLUMN | POSTGRESQL | MYSQL | SQLITE3
---------------+----------------+--------------------------------+--------------------------------+---------------------------------
ID | id | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER
UserID | user_id | BIGINT | BIGINT | INTEGER
OpType | op_type | BIGINT | BIGINT | INTEGER
ActUserID | act_user_id | BIGINT | BIGINT | INTEGER
ActUserName | act_user_name | TEXT | LONGTEXT | TEXT
RepoID | repo_id | BIGINT | BIGINT | INTEGER
RepoUserName | repo_user_name | TEXT | LONGTEXT | TEXT
RepoName | repo_name | TEXT | LONGTEXT | TEXT
RefName | ref_name | TEXT | LONGTEXT | TEXT
IsPrivate | is_private | BOOLEAN NOT NULL DEFAULT FALSE | BOOLEAN NOT NULL DEFAULT FALSE | NUMERIC NOT NULL DEFAULT FALSE
Content | content | TEXT | LONGTEXT | TEXT
CreatedUnix | created_unix | BIGINT | BIGINT | INTEGER
Primary keys: id
Indexes:
"idx_action_repo_id" (repo_id)
"idx_action_user_id" (user_id)
```
# Table "email_address"
```
FIELD | COLUMN | POSTGRESQL | MYSQL | SQLITE3
--------------+--------------+--------------------------------+--------------------------------+---------------------------------
ID | id | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER
UserID | uid | BIGINT NOT NULL | BIGINT NOT NULL | INTEGER NOT NULL
Email | email | VARCHAR(254) NOT NULL | VARCHAR(254) NOT NULL | TEXT NOT NULL
IsActivated | is_activated | BOOLEAN NOT NULL DEFAULT FALSE | BOOLEAN NOT NULL DEFAULT FALSE | NUMERIC NOT NULL DEFAULT FALSE
Primary keys: id
Indexes:
"email_address_user_email_unique" UNIQUE (uid, email)
"idx_email_address_user_id" (uid)
```
# Table "follow"
```
FIELD | COLUMN | POSTGRESQL | MYSQL | SQLITE3
-----------+-----------+-----------------+-----------------------+-------------------
ID | id | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER
UserID | user_id | BIGINT NOT NULL | BIGINT NOT NULL | INTEGER NOT NULL
FollowID | follow_id | BIGINT NOT NULL | BIGINT NOT NULL | INTEGER NOT NULL
Primary keys: id
Indexes:
"follow_user_follow_unique" UNIQUE (user_id, follow_id)
```
# Table "lfs_object"
```
FIELD | COLUMN | POSTGRESQL | MYSQL | SQLITE3
------------+------------+----------------------+----------------------+--------------------
RepoID | repo_id | BIGINT | BIGINT | INTEGER
OID | oid | TEXT | VARCHAR(191) | TEXT
Size | size | BIGINT NOT NULL | BIGINT NOT NULL | INTEGER NOT NULL
Storage | storage | TEXT NOT NULL | LONGTEXT NOT NULL | TEXT NOT NULL
CreatedAt | created_at | TIMESTAMPTZ NOT NULL | DATETIME(3) NOT NULL | DATETIME NOT NULL
Primary keys: repo_id, oid
```
# Table "login_source"
```
FIELD | COLUMN | POSTGRESQL | MYSQL | SQLITE3
--------------+--------------+------------------+-----------------------+-------------------
ID | id | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER
Type | type | BIGINT | BIGINT | INTEGER
Name | name | TEXT UNIQUE | VARCHAR(191) UNIQUE | TEXT UNIQUE
IsActived | is_actived | BOOLEAN NOT NULL | BOOLEAN NOT NULL | NUMERIC NOT NULL
IsDefault | is_default | BOOLEAN | BOOLEAN | NUMERIC
Config | cfg | TEXT | TEXT | TEXT
CreatedUnix | created_unix | BIGINT | BIGINT | INTEGER
UpdatedUnix | updated_unix | BIGINT | BIGINT | INTEGER
Primary keys: id
```
# Table "notice"
```
FIELD | COLUMN | POSTGRESQL | MYSQL | SQLITE3
--------------+--------------+------------+-----------------------+----------
ID | id | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER
Type | type | BIGINT | BIGINT | INTEGER
Description | description | TEXT | TEXT | TEXT
CreatedUnix | created_unix | BIGINT | BIGINT | INTEGER
Primary keys: id
```

58
go.mod
View File

@@ -6,8 +6,11 @@ require (
github.com/Masterminds/semver/v3 v3.4.0 github.com/Masterminds/semver/v3 v3.4.0
github.com/cockroachdb/errors v1.12.0 github.com/cockroachdb/errors v1.12.0
github.com/derision-test/go-mockgen/v2 v2.1.1 github.com/derision-test/go-mockgen/v2 v2.1.1
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3 github.com/editorconfig/editorconfig-core-go/v2 v2.6.4
github.com/go-ldap/ldap/v3 v3.4.11 github.com/fergusstrange/embedded-postgres v1.33.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/binding v1.2.0
github.com/go-macaron/cache v0.0.0-20190810181446-10f7c57e2196 github.com/go-macaron/cache v0.0.0-20190810181446-10f7c57e2196
github.com/go-macaron/captcha v0.2.0 github.com/go-macaron/captcha v0.2.0
@@ -18,19 +21,19 @@ require (
github.com/go-macaron/toolbox v0.0.0-20190813233741-94defb8383c6 github.com/go-macaron/toolbox v0.0.0-20190813233741-94defb8383c6
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561 github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14
github.com/gogs/git-module v1.8.4 github.com/gogs/git-module v1.8.6
github.com/gogs/go-gogs-client v0.0.0-20200128182646-c69cb7680fd4 github.com/gogs/go-gogs-client v0.0.0-20200128182646-c69cb7680fd4
github.com/gogs/go-libravatar v0.0.0-20191106065024-33a75213d0a0 github.com/gogs/go-libravatar v0.0.0-20191106065024-33a75213d0a0
github.com/gogs/minwinsvc v0.0.0-20170301035411-95be6356811a github.com/gogs/minwinsvc v0.0.0-20170301035411-95be6356811a
github.com/google/go-github v17.0.0+incompatible github.com/google/go-github v17.0.0+incompatible
github.com/inbucket/html2text v1.0.0
github.com/issue9/identicon v1.2.1 github.com/issue9/identicon v1.2.1
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43
github.com/json-iterator/go v1.1.12 github.com/json-iterator/go v1.1.12
github.com/microcosm-cc/bluemonday v1.0.27 github.com/microcosm-cc/bluemonday v1.0.27
github.com/msteinert/pam v1.2.0 github.com/msteinert/pam v1.2.0
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/niklasfasching/go-org v1.9.1 github.com/niklasfasching/go-org v1.9.1
github.com/olekukonko/tablewriter v0.0.5 github.com/olekukonko/tablewriter v1.1.3
github.com/pquerna/otp v1.5.0 github.com/pquerna/otp v1.5.0
github.com/prometheus/client_golang v1.23.0 github.com/prometheus/client_golang v1.23.0
github.com/russross/blackfriday v1.6.0 github.com/russross/blackfriday v1.6.0
@@ -43,47 +46,51 @@ require (
github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6
github.com/unknwon/paginater v0.0.0-20170405233947-45e5d631308e github.com/unknwon/paginater v0.0.0-20170405233947-45e5d631308e
github.com/urfave/cli v1.22.17 github.com/urfave/cli v1.22.17
golang.org/x/crypto v0.45.0 golang.org/x/crypto v0.47.0
golang.org/x/net v0.47.0 golang.org/x/net v0.48.0
golang.org/x/text v0.31.0 golang.org/x/text v0.33.0
gopkg.in/DATA-DOG/go-sqlmock.v2 v2.0.0-20180914054222-c19298f520d0 gopkg.in/DATA-DOG/go-sqlmock.v2 v2.0.0-20180914054222-c19298f520d0
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.67.0 gopkg.in/ini.v1 v1.67.0
gopkg.in/macaron.v1 v1.5.0 gopkg.in/macaron.v1 v1.5.1
gorm.io/driver/mysql v1.5.2 gorm.io/driver/mysql v1.5.2
gorm.io/driver/postgres v1.6.0 gorm.io/driver/postgres v1.6.0
gorm.io/driver/sqlite v1.4.2
gorm.io/driver/sqlserver v1.4.1 gorm.io/driver/sqlserver v1.4.1
gorm.io/gorm v1.25.12 gorm.io/gorm v1.25.12
modernc.org/sqlite v1.38.2
unknwon.dev/clog/v2 v2.2.0 unknwon.dev/clog/v2 v2.2.0
xorm.io/builder v0.3.6
xorm.io/core v0.7.2
xorm.io/xorm v0.8.0
) )
require ( require (
bitbucket.org/creachadair/shell v0.0.7 // indirect bitbucket.org/creachadair/shell v0.0.7 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/aymerick/douceur v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 // indirect github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // 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/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/redact v1.1.5 // indirect github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.1 // 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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/djherbis/buffer v1.2.0 // indirect github.com/djherbis/buffer v1.2.0 // indirect
github.com/djherbis/nio/v3 v3.0.1 // indirect github.com/djherbis/nio/v3 v3.0.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.13.0 // indirect github.com/fatih/color v1.18.0 // indirect
github.com/getsentry/sentry-go v0.27.0 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-macaron/inject v0.0.0-20200308113650-138e5925c53b // 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-redis/redis/v8 v8.11.5 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect
@@ -101,34 +108,38 @@ require (
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/kr/pretty v0.3.1 // indirect github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/mattn/go-sqlite3 v1.14.24 // indirect
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 // indirect github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 // indirect
github.com/microsoft/go-mssqldb v1.8.2 // 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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
go.bobheadxi.dev/streamline v1.2.1 // indirect go.bobheadxi.dev/streamline v1.2.1 // indirect
go.opentelemetry.io/otel v1.11.0 // indirect go.opentelemetry.io/otel v1.11.0 // indirect
go.opentelemetry.io/otel/trace v1.11.0 // indirect go.opentelemetry.io/otel/trace v1.11.0 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/mod v0.29.0 // indirect golang.org/x/mod v0.31.0 // indirect
golang.org/x/sync v0.18.0 // indirect golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.38.0 // indirect golang.org/x/sys v0.40.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e // indirect gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e // indirect
@@ -137,7 +148,8 @@ require (
modernc.org/libc v1.66.3 // indirect modernc.org/libc v1.66.3 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.39.0 // indirect
) )
// +heroku goVersion go1.25 // +heroku goVersion go1.25
// +heroku install ./ // +heroku install ./cmd/gogs

262
go.sum
View File

@@ -1,35 +1,33 @@
bitbucket.org/creachadair/shell v0.0.7 h1:Z96pB6DkSb7F3Y3BBnJeOZH2gazyMTWlvecSD4vDqfk= bitbucket.org/creachadair/shell v0.0.7 h1:Z96pB6DkSb7F3Y3BBnJeOZH2gazyMTWlvecSD4vDqfk=
bitbucket.org/creachadair/shell v0.0.7/go.mod h1:oqtXSSvSYr4624lnnabXHaBsYW6RD80caLi2b3hJk0U= bitbucket.org/creachadair/shell v0.0.7/go.mod h1:oqtXSSvSYr4624lnnabXHaBsYW6RD80caLi2b3hJk0U=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e/go.mod h1:uJEsN4LQpeGYRCjuPXPZBClU7N5pWzGuyF4uqLpE/e0= 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= 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/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM= 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/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
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/azidentity v1.0.0/go.mod h1:+6sju8gk8FRmSajX3Oz4G5Gm7P+mbqE9FVaXXFYTkCM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg= 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/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= 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/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/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 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/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= 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/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
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=
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 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/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 h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
@@ -42,6 +40,13 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 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/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/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 h1:d7oCs6vuIMUQRVbi6jWWWEJZahLCfJpnJSVobd1/sUo=
github.com/cockroachdb/errors v1.12.0/go.mod h1:SvzfYNNBshAVbZ8wzNc/UPK3w1vf0dKDUP41ucAIf7g= github.com/cockroachdb/errors v1.12.0/go.mod h1:SvzfYNNBshAVbZ8wzNc/UPK3w1vf0dKDUP41ucAIf7g=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
@@ -61,6 +66,9 @@ github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGii
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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 h1:MXG9rzyvsrDBfa1a1GatvHjCrbmEug3hVt0rSOXipCw=
github.com/derision-test/go-mockgen/v2 v2.1.1/go.mod h1:cDK2Y9IF5roTJgugWV23IvlOJsllhDN5zxRDN+g4cZo= 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= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
@@ -74,12 +82,17 @@ github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= 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 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3 h1:XVUp6qW3BIkmM3/1EkrHpa6bL56APOynfXcZEmIgOhs= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3/go.mod h1:ThHVc+hqbUsmE1wmK/MASpQEhCleWu1JDJDNhUOMy0c= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.4 h1:CHwUbBVVyKWRX9kt5A/OtwhYUJB32DrFp9xzmjR6cac=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.4/go.mod h1:JWRVKHdVW+dkv6F8p+xGCa6a+TyMrqsFbFkSs/aQkrQ=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fergusstrange/embedded-postgres v1.33.0 h1:ka8vmRpm4IDsES7NPXQ/NThAp1fc/f+crcXYjCW7wK0=
github.com/fergusstrange/embedded-postgres v1.33.0/go.mod h1:w0YvnCgf19o6tskInrOOACtnqfVlOvluz3hlNLY7tRk=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= 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.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -87,12 +100,18 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 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 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
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=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM= 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-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 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 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -121,18 +140,22 @@ github.com/go-macaron/toolbox v0.0.0-20190813233741-94defb8383c6/go.mod h1:YFNJ/
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= 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-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.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.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 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=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561 h1:aBzukfDxQlCTVS0NBUjI5YA3iVeaZ9Tb5PxNrrIP1xs= github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561 h1:aBzukfDxQlCTVS0NBUjI5YA3iVeaZ9Tb5PxNrrIP1xs=
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14= 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 h1:yXtpJr/LV6PFu4nTLgfjQdcMdzjbqqXMEnHfq0Or6p8=
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14/go.mod h1:jPoNZLWDAqA5N3G5amEoiNbhVrmM+ZQEcnQvNQ2KaZk= github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14/go.mod h1:jPoNZLWDAqA5N3G5amEoiNbhVrmM+ZQEcnQvNQ2KaZk=
github.com/gogs/git-module v1.8.4 h1:oSt8sOL4NWOGrSo/CwbS+C4YXtk76QvxyPofem/ViTU= github.com/gogs/git-module v1.8.6 h1:4Io9vWZYQyIjdIPxfKgeYZXnDKNgydc6OZTxII5xCH4=
github.com/gogs/git-module v1.8.4/go.mod h1:bQY0aoMK5Q5+NKgy4jXe3K1GFW+GnsSk0SJK0jh6yD0= 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 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/go-gogs-client v0.0.0-20200128182646-c69cb7680fd4/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
github.com/gogs/go-libravatar v0.0.0-20191106065024-33a75213d0a0 h1:K02vod+sn3M1OOkdqi2tPxN2+xESK4qyITVQ3JkGEv4= github.com/gogs/go-libravatar v0.0.0-20191106065024-33a75213d0a0 h1:K02vod+sn3M1OOkdqi2tPxN2+xESK4qyITVQ3JkGEv4=
@@ -140,16 +163,17 @@ github.com/gogs/go-libravatar v0.0.0-20191106065024-33a75213d0a0/go.mod h1:Zas3B
github.com/gogs/minwinsvc v0.0.0-20170301035411-95be6356811a h1:8DZwxETOVWIinYxDK+i6L+rMb7eGATGaakD6ZucfHVk= 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/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.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+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-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 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 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.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 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/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=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -163,6 +187,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -175,6 +201,8 @@ github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+u
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= 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/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/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-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 h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
@@ -182,14 +210,19 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.3.0/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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hexops/autogold v1.3.1 h1:YgxF9OHWbEIUjhDbpnLhgVsjUDsiHDTyDfy2lrfdlzo= github.com/hexops/autogold v1.3.1 h1:YgxF9OHWbEIUjhDbpnLhgVsjUDsiHDTyDfy2lrfdlzo=
github.com/hexops/autogold v1.3.1/go.mod h1:sQO+mQUCVfxOKPht+ipDSkJ2SCJ7BNJVHZexsXqWMx4= github.com/hexops/autogold v1.3.1/go.mod h1:sQO+mQUCVfxOKPht+ipDSkJ2SCJ7BNJVHZexsXqWMx4=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
@@ -198,6 +231,8 @@ github.com/hexops/valast v1.4.3 h1:oBoGERMJh6UZdRc6cduE1CTPK+VAdXA59Y1HFgu3sm0=
github.com/hexops/valast v1.4.3/go.mod h1:Iqx2kLj3Jn47wuXpj3wX40xn6F93QNFBHuiKBerkTGA= github.com/hexops/valast v1.4.3/go.mod h1:Iqx2kLj3Jn47wuXpj3wX40xn6F93QNFBHuiKBerkTGA=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inbucket/html2text v1.0.0 h1:N5kza++4uBBDJ2Z3KUnTRyPNoBcW+YfOgNiNmNB+sgs=
github.com/inbucket/html2text v1.0.0/go.mod h1:5TrhXQKGU+LXurODaSm55Y9eXoPBRnYiOz4x2XfUoJU=
github.com/issue9/assert/v2 v2.0.0 h1:vN7fr70g5ND6zM39tPZk/E4WCyjGMqApmFbujSTmEo0= github.com/issue9/assert/v2 v2.0.0 h1:vN7fr70g5ND6zM39tPZk/E4WCyjGMqApmFbujSTmEo0=
github.com/issue9/assert/v2 v2.0.0/go.mod h1:rKr1eVGzXUhAo2af1thiKAhIA8uiSK9Wyn7mcZ4BzAg= github.com/issue9/assert/v2 v2.0.0/go.mod h1:rKr1eVGzXUhAo2af1thiKAhIA8uiSK9Wyn7mcZ4BzAg=
github.com/issue9/identicon v1.2.1 h1:9RUq3DcmDJvfXAYZWJDaq/Bi45oS/Fr79W0CazbXNaY= github.com/issue9/identicon v1.2.1 h1:9RUq3DcmDJvfXAYZWJDaq/Bi45oS/Fr79W0CazbXNaY=
@@ -214,8 +249,6 @@ 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.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= 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/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43 h1:jTkyeF7NZ5oIr0ESmcrpiDgAfoidCBF4F5kJhjtaRwE=
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
@@ -235,13 +268,17 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/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.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -251,36 +288,32 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
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.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ= 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/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af/go.mod h1:Cqz6pqow14VObJ7peltM+2n3PWOz7yTrfUuGbVFkzN0=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 h1:YocNLcTBdEdvY3iDK6jfWXvEaM5OCKkjxPKoJRdB3Gg= github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 h1:YocNLcTBdEdvY3iDK6jfWXvEaM5OCKkjxPKoJRdB3Gg=
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo= 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 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= 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/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ=
github.com/microsoft/go-mssqldb v1.8.2 h1:236sewazvC8FvG6Dr3bszrVhMkAl4KYImryLkRMCd0I=
github.com/microsoft/go-mssqldb v1.8.2/go.mod h1:vp38dT33FGfVotRiTmDo3bFyaHq+p3LektQrjTULowo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -292,6 +325,7 @@ 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/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 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/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=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
@@ -303,8 +337,14 @@ github.com/niklasfasching/go-org v1.9.1/go.mod h1:ZAGFFkWvUQcpazmi/8nHqwvARpr1xp
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0 h1:jrYnow5+hy3WRDCBypUFvVKNSPPCdqgSXIE9eJDD8LM=
github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew=
github.com/olekukonko/tablewriter v1.1.3 h1:VSHhghXxrP0JHl+0NnKid7WoEmd9/urKRJLysb70nnA=
github.com/olekukonko/tablewriter v1.1.3/go.mod h1:9VU0knjhmMkXjnMKrZ3+L2JhhtsQ/L38BbL3CRNE8tM=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -321,15 +361,17 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
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 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= 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-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -337,18 +379,24 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs= github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= 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 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
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 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= 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 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
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 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= 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/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= 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/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 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/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
@@ -366,22 +414,27 @@ github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBf
github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d/go.mod h1:vq0tzqLRu6TS7Id0wMo2N5QzJoKedVeovOpHjnykSzY= github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d/go.mod h1:vq0tzqLRu6TS7Id0wMo2N5QzJoKedVeovOpHjnykSzY=
github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg= github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA= github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
github.com/sourcegraph/run v0.12.0 h1:3A8w5e8HIYPfafHekvmdmmh42RHKGVhmiTZAPJclg7I= github.com/sourcegraph/run v0.12.0 h1:3A8w5e8HIYPfafHekvmdmmh42RHKGVhmiTZAPJclg7I=
github.com/sourcegraph/run v0.12.0/go.mod h1:PwaP936BTnAJC1cqR5rSbG5kOs/EWStTK3lqvMX5GUA= github.com/sourcegraph/run v0.12.0/go.mod h1:PwaP936BTnAJC1cqR5rSbG5kOs/EWStTK3lqvMX5GUA=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@@ -405,10 +458,15 @@ github.com/unknwon/paginater v0.0.0-20170405233947-45e5d631308e h1:Qf3QQl/zmEbWD
github.com/unknwon/paginater v0.0.0-20170405233947-45e5d631308e/go.mod h1:TBwoao3Q4Eb/cp+dHbXDfRTrZSsj/k7kLr2j1oWRWC0= 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 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo= github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
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= go.bobheadxi.dev/streamline v1.2.1 h1:IqKSA1TbeuDqCzYNAwtlh8sqf3tsQus8XgJdkCWFT8c=
go.bobheadxi.dev/streamline v1.2.1/go.mod h1:yJsVXOSBFLgAKvsnf6WmIzmB2A65nWqkR/sRNxJPa74= go.bobheadxi.dev/streamline v1.2.1/go.mod h1:yJsVXOSBFLgAKvsnf6WmIzmB2A65nWqkR/sRNxJPa74=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opentelemetry.io/otel v1.11.0 h1:kfToEGMDq6TrVrJ9Vht84Y8y9enykSZzDDZglV0kIEk= go.opentelemetry.io/otel v1.11.0 h1:kfToEGMDq6TrVrJ9Vht84Y8y9enykSZzDDZglV0kIEk=
go.opentelemetry.io/otel v1.11.0/go.mod h1:H2KtuEphyMvlhZ+F7tg9GRhAOe60moNx61Ex+WmiKkk= go.opentelemetry.io/otel v1.11.0/go.mod h1:H2KtuEphyMvlhZ+F7tg9GRhAOe60moNx61Ex+WmiKkk=
go.opentelemetry.io/otel/sdk v1.11.0 h1:ZnKIL9V9Ztaq+ME43IUi/eo22mNsb6a7tGfzaOWB5fo= go.opentelemetry.io/otel/sdk v1.11.0 h1:ZnKIL9V9Ztaq+ME43IUi/eo22mNsb6a7tGfzaOWB5fo=
@@ -417,7 +475,9 @@ 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.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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -426,15 +486,25 @@ golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 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-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.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
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 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/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
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= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
@@ -445,18 +515,27 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
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= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 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= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -468,8 +547,6 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -478,26 +555,30 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/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-20210630005230-0f9fa26af87c/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-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.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
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= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190802220118-1d1727260058/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190802220118-1d1727260058/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
@@ -505,14 +586,23 @@ 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-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-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -525,6 +615,7 @@ google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9x
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/DATA-DOG/go-sqlmock.v2 v2.0.0-20180914054222-c19298f520d0 h1:/21c4hNFgj8A1D54vgJZwQlywp64/RUBHzlPdpy5h4s= gopkg.in/DATA-DOG/go-sqlmock.v2 v2.0.0-20180914054222-c19298f520d0 h1:/21c4hNFgj8A1D54vgJZwQlywp64/RUBHzlPdpy5h4s=
gopkg.in/DATA-DOG/go-sqlmock.v2 v2.0.0-20180914054222-c19298f520d0/go.mod h1:0uueny64T996pN6bez2N3S8HWyPcpyfTPma8Wc1Awx4= gopkg.in/DATA-DOG/go-sqlmock.v2 v2.0.0-20180914054222-c19298f520d0/go.mod h1:0uueny64T996pN6bez2N3S8HWyPcpyfTPma8Wc1Awx4=
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 h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= 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 h1:wGA78yza6bu/mWcc4QfBuIEHEtc06xdiU0X8sY36yUU=
@@ -544,8 +635,8 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/macaron.v1 v1.3.4/go.mod h1:/RoHTdC8ALpyJ3+QR36mKjwnT1F1dyYtsGM9Ate6ZFI= 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.3.5/go.mod h1:uMZCFccv9yr5TipIalVOyAyZQuOH3OkmXvgcWwhJuP4=
gopkg.in/macaron.v1 v1.4.0/go.mod h1:uMZCFccv9yr5TipIalVOyAyZQuOH3OkmXvgcWwhJuP4= gopkg.in/macaron.v1 v1.4.0/go.mod h1:uMZCFccv9yr5TipIalVOyAyZQuOH3OkmXvgcWwhJuP4=
gopkg.in/macaron.v1 v1.5.0 h1:/dXJaeQagWLjVjCrKH8dgSSU7yG4qTv6rBKpqhYaCyc= gopkg.in/macaron.v1 v1.5.1 h1:0ytdXYcf6//a8bzedl1fVXzPeIXblEqoNPntWAo9YLU=
gopkg.in/macaron.v1 v1.5.0/go.mod h1:sAYUd2r8Q+jLnCN4/ZmdAYHzQn67agV5sAqKFQgrRrw= gopkg.in/macaron.v1 v1.5.1/go.mod h1:AiquOw8YeZJC8sUe11vIO6NeA1/TKSlzQXuJ7Tc4cCQ=
gopkg.in/redis.v2 v2.3.2 h1:GPVIIB/JnL1wvfULefy3qXmPu1nfNu2d0yA09FHgwfs= gopkg.in/redis.v2 v2.3.2 h1:GPVIIB/JnL1wvfULefy3qXmPu1nfNu2d0yA09FHgwfs=
gopkg.in/redis.v2 v2.3.2/go.mod h1:4wl9PJ/CqzeHk3LVq1hNLHH8krm3+AXEgut4jVc++LU= gopkg.in/redis.v2 v2.3.2/go.mod h1:4wl9PJ/CqzeHk3LVq1hNLHH8krm3+AXEgut4jVc++LU=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
@@ -566,14 +657,15 @@ 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/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 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/driver/sqlite v1.4.2 h1:F6vYJcmR4Cnh0ErLyoY8JSfabBGyR0epIGuhgHJuNws=
gorm.io/driver/sqlite v1.4.2/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0=
gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig= 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.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.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 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM= modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM=
modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
@@ -594,8 +686,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= modernc.org/sqlite v1.39.0 h1:6bwu9Ooim0yVYA7IZn9demiQk/Ejp0BtTjBWFLymSeY=
modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= modernc.org/sqlite v1.39.0/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
@@ -604,3 +696,9 @@ mvdan.cc/gofumpt v0.4.0 h1:JVf4NN1mIpHogBj7ABpgOyZc65/UUOkKQFkoURsz4MM=
mvdan.cc/gofumpt v0.4.0/go.mod h1:PljLOHDeZqgS8opHRKLzp2It2VBuSdteAgqUfzMTxlQ= mvdan.cc/gofumpt v0.4.0/go.mod h1:PljLOHDeZqgS8opHRKLzp2It2VBuSdteAgqUfzMTxlQ=
unknwon.dev/clog/v2 v2.2.0 h1:jkPdsxux0MC04BT/9NHbT75z4prK92SH10VBNmIpVCc= unknwon.dev/clog/v2 v2.2.0 h1:jkPdsxux0MC04BT/9NHbT75z4prK92SH10VBNmIpVCc=
unknwon.dev/clog/v2 v2.2.0/go.mod h1:zvUlyibDHI4mykYdWyWje2G9nF/nBzfDOqRo2my4mWc= unknwon.dev/clog/v2 v2.2.0/go.mod h1:zvUlyibDHI4mykYdWyWje2G9nF/nBzfDOqRo2my4mWc=
xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8=
xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU=
xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw=
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
xorm.io/xorm v0.8.0 h1:iALxgJrX8O00f8Jk22GbZwPmxJNgssV5Mv4uc2HL9PM=
xorm.io/xorm v0.8.0/go.mod h1:ZkJLEYLoVyg7amJK/5r779bHyzs2AU8f8VMiP6BM7uY=

View File

@@ -70,7 +70,7 @@ func Init(customConf string) error {
if err = File.Append(customConf); err != nil { if err = File.Append(customConf); err != nil {
return errors.Wrapf(err, "append %q", customConf) return errors.Wrapf(err, "append %q", customConf)
} }
} else { } else if !HookMode {
log.Warn("Custom config %q not found. Ignore this warning if you're running for the first time", customConf) log.Warn("Custom config %q not found. Ignore this warning if you're running for the first time", customConf)
} }
@@ -142,9 +142,11 @@ func Init(customConf string) error {
} }
if IsWindowsRuntime() || semverutil.Compare(sshVersion, "<", "5.1") { if IsWindowsRuntime() || semverutil.Compare(sshVersion, "<", "5.1") {
if !HookMode {
log.Warn(`SSH minimum key size check is forced to be disabled because server is not eligible: log.Warn(`SSH minimum key size check is forced to be disabled because server is not eligible:
1. Windows server 1. Windows server
2. OpenSSH version is lower than 5.1`) 2. OpenSSH version is lower than 5.1`)
}
} else { } else {
SSH.MinimumKeySizes = map[string]int{} SSH.MinimumKeySizes = map[string]int{}
for _, key := range File.Section("ssh.minimum_key_sizes").Keys() { for _, key := range File.Section("ssh.minimum_key_sizes").Keys() {
@@ -346,6 +348,11 @@ func Init(customConf string) error {
LFS.ObjectsPath = ensureAbs(LFS.ObjectsPath) LFS.ObjectsPath = ensureAbs(LFS.ObjectsPath)
handleDeprecated() handleDeprecated()
if !HookMode {
for _, warning := range checkInvalidOptions(File) {
log.Warn("%s", warning)
}
}
if err = File.Section("cache").MapTo(&Cache); err != nil { if err = File.Section("cache").MapTo(&Cache); err != nil {
return errors.Wrap(err, "mapping [cache] section") return errors.Wrap(err, "mapping [cache] section")

View File

@@ -1,11 +1,15 @@
package conf package conf
import ( import (
"fmt"
"net/url" "net/url"
"os" "os"
"time" "time"
"github.com/gogs/go-libravatar" "github.com/gogs/go-libravatar"
"gopkg.in/ini.v1"
"gogs.io/gogs/conf"
) )
// README: This file contains static values that should only be set at initialization time. // README: This file contains static values that should only be set at initialization time.
@@ -227,7 +231,7 @@ var (
) )
type AppOpts struct { type AppOpts struct {
// ⚠️ WARNING: Should only be set by the main package (i.e. "gogs.go"). // ⚠️ WARNING: Should only be set by the main package (i.e. "cmd/gogs/main.go").
Version string `ini:"-"` Version string `ini:"-"`
BrandName string BrandName string
@@ -430,10 +434,75 @@ func handleDeprecated() {
// } // }
} }
// checkInvalidOptions checks invalid (renamed/deleted) configuration sections
// and options and returns a list of warnings.
func checkInvalidOptions(config *ini.File) (warnings []string) {
renamedSections := map[string]string{
"mailer": "email",
"service": "auth",
}
for oldSection, newSection := range renamedSections {
if len(config.Section(oldSection).KeyStrings()) > 0 {
warnings = append(warnings, fmt.Sprintf("section [%s] is invalid, use [%s] instead", oldSection, newSection))
}
}
type optionPath struct {
section string
option string
}
renamedOptionPaths := map[optionPath]optionPath{
// Example:
// {"security", "REVERSE_PROXY_AUTHENTICATION_USER"}: {"auth", "REVERSE_PROXY_AUTHENTICATION_HEADER"},
}
for oldPath, newPath := range renamedOptionPaths {
if config.Section(oldPath.section).HasKey(oldPath.option) {
warnings = append(
warnings,
fmt.Sprintf("option [%s] %s is invalid, use [%s] %s instead",
oldPath.section, oldPath.option,
newPath.section, newPath.option,
),
)
}
}
// Check options that don't exist anymore.
defaultConfigData, err := conf.Files.ReadFile("app.ini")
if err != nil {
// Warning is best-effort, OK to skip on error.
return warnings
}
defaultConfig, err := ini.LoadSources(
ini.LoadOptions{IgnoreInlineComment: true},
defaultConfigData,
)
if err != nil {
// Warning is best-effort, OK to skip on error.
return warnings
}
for _, section := range config.Sections() {
// Skip sections already warned about.
if _, ok := renamedSections[section.Name()]; ok {
continue
}
for _, option := range section.Keys() {
if _, ok := renamedOptionPaths[optionPath{section.Name(), option.Name()}]; ok {
continue
}
if !defaultConfig.Section(section.Name()).HasKey(option.Name()) {
warnings = append(warnings, fmt.Sprintf("option [%s] %s is invalid", section.Name(), option.Name()))
}
}
}
return warnings
}
// HookMode indicates whether program starts as Git server-side hook callback. // HookMode indicates whether program starts as Git server-side hook callback.
// All operations should be done synchronously to prevent program exits before finishing. // All operations should be done synchronously to prevent program exits before finishing.
// //
// ⚠️ WARNING: Should only be set by "internal/cmd/serv.go". // ⚠️ WARNING: Should only be set by "cmd/gogs/serv.go".
var HookMode bool var HookMode bool
// Indicates which database backend is currently being used. // Indicates which database backend is currently being used.

View File

@@ -1,9 +1,11 @@
package conf package conf
import ( import (
"sort"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/ini.v1"
) )
func Test_i18n_DateLang(t *testing.T) { func Test_i18n_DateLang(t *testing.T) {
@@ -29,3 +31,48 @@ func Test_i18n_DateLang(t *testing.T) {
}) })
} }
} }
func TestCheckInvalidOptions(t *testing.T) {
cfg := ini.Empty()
_, _ = cfg.Section("mailer").NewKey("ENABLED", "true")
_, _ = cfg.Section("service").NewKey("START_TYPE", "true")
_, _ = cfg.Section("security").NewKey("REVERSE_PROXY_AUTHENTICATION_USER", "true")
_, _ = cfg.Section("auth").NewKey("ACTIVE_CODE_LIVE_MINUTES", "10")
_, _ = cfg.Section("auth").NewKey("RESET_PASSWD_CODE_LIVE_MINUTES", "10")
_, _ = cfg.Section("auth").NewKey("ENABLE_CAPTCHA", "true")
_, _ = cfg.Section("auth").NewKey("ENABLE_NOTIFY_MAIL", "true")
_, _ = cfg.Section("auth").NewKey("REGISTER_EMAIL_CONFIRM", "true")
_, _ = cfg.Section("session").NewKey("GC_INTERVAL_TIME", "10")
_, _ = cfg.Section("session").NewKey("SESSION_LIFE_TIME", "10")
_, _ = cfg.Section("server").NewKey("ROOT_URL", "true")
_, _ = 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")
wantWarnings := []string{
"option [auth] ACTIVE_CODE_LIVE_MINUTES is invalid",
"option [auth] ENABLE_CAPTCHA is invalid",
"option [auth] ENABLE_NOTIFY_MAIL is invalid",
"option [auth] REGISTER_EMAIL_CONFIRM is invalid",
"option [auth] RESET_PASSWD_CODE_LIVE_MINUTES is invalid",
"option [database] DB_TYPE is invalid",
"option [database] PASSWD is invalid",
"option [security] REVERSE_PROXY_AUTHENTICATION_USER is invalid",
"option [session] GC_INTERVAL_TIME is invalid",
"option [session] SESSION_LIFE_TIME is invalid",
"section [mailer] is invalid, use [email] instead",
"section [service] is invalid, use [auth] instead",
"option [server] ROOT_URL is invalid",
"option [server] LANDING_PAGE is invalid",
"option [server] NONEXISTENT_OPTION is invalid",
}
gotWarnings := checkInvalidOptions(cfg)
sort.Strings(wantWarnings)
sort.Strings(gotWarnings)
assert.Equal(t, wantWarnings, gotWarnings)
}

View File

@@ -28,8 +28,7 @@ PASSWORD = 87654321
ACTIVATE_CODE_LIVES = 10 ACTIVATE_CODE_LIVES = 10
RESET_PASSWORD_CODE_LIVES = 10 RESET_PASSWORD_CODE_LIVES = 10
REQUIRE_EMAIL_CONFIRMATION = true REQUIRE_EMAIL_CONFIRMATION = true
ENABLE_CAPTCHA = true ENABLE_REGISTRATION_CAPTCHA = true
ENABLE_NOTIFY_MAIL = true
REVERSE_PROXY_AUTHENTICATION_HEADER = X-FORWARDED-FOR REVERSE_PROXY_AUTHENTICATION_HEADER = X-FORWARDED-FOR
[user] [user]

View File

@@ -147,13 +147,13 @@ func (c *Context) RedirectSubpath(location string, status ...int) {
} }
// RenderWithErr used for page has form validation but need to prompt error to users. // RenderWithErr used for page has form validation but need to prompt error to users.
func (c *Context) RenderWithErr(msg, tpl string, f any) { func (c *Context) RenderWithErr(msg string, status int, tpl string, f any) {
if f != nil { if f != nil {
form.Assign(f, c.Data) form.Assign(f, c.Data)
} }
c.Flash.ErrorMsg = msg c.Flash.ErrorMsg = msg
c.Data["Flash"] = c.Flash c.Data["Flash"] = c.Flash
c.HTML(http.StatusOK, tpl) c.HTML(status, tpl)
} }
// NotFound renders the 404 page. // NotFound renders the 404 page.

View File

@@ -142,6 +142,11 @@ func actionsCommitRepo(t *testing.T, ctx context.Context, s *ActionsStore) {
now := time.Unix(1588568886, 0).UTC() now := time.Unix(1588568886, 0).UTC()
conf.SetMockSSH(t, conf.SSHOpts{}) conf.SetMockSSH(t, conf.SSHOpts{})
conf.SetMockUI(t, conf.UIOpts{
User: conf.UIUserOpts{
NewsFeedPagingNum: 20,
},
})
t.Run("new commit", func(t *testing.T) { t.Run("new commit", func(t *testing.T) {
t.Cleanup(func() { t.Cleanup(func() {
@@ -432,6 +437,12 @@ func actionsListByUser(t *testing.T, ctx context.Context, s *ActionsStore) {
} }
func actionsMergePullRequest(t *testing.T, ctx context.Context, s *ActionsStore) { func actionsMergePullRequest(t *testing.T, ctx context.Context, s *ActionsStore) {
conf.SetMockUI(t, conf.UIOpts{
User: conf.UIUserOpts{
NewsFeedPagingNum: 20,
},
})
alice, err := newUsersStore(s.db).Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) alice, err := newUsersStore(s.db).Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
repo, err := newReposStore(s.db).Create(ctx, repo, err := newReposStore(s.db).Create(ctx,
@@ -477,6 +488,12 @@ func actionsMergePullRequest(t *testing.T, ctx context.Context, s *ActionsStore)
} }
func actionsMirrorSyncCreate(t *testing.T, ctx context.Context, s *ActionsStore) { func actionsMirrorSyncCreate(t *testing.T, ctx context.Context, s *ActionsStore) {
conf.SetMockUI(t, conf.UIOpts{
User: conf.UIUserOpts{
NewsFeedPagingNum: 20,
},
})
alice, err := newUsersStore(s.db).Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) alice, err := newUsersStore(s.db).Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
repo, err := newReposStore(s.db).Create(ctx, repo, err := newReposStore(s.db).Create(ctx,
@@ -518,6 +535,12 @@ func actionsMirrorSyncCreate(t *testing.T, ctx context.Context, s *ActionsStore)
} }
func actionsMirrorSyncDelete(t *testing.T, ctx context.Context, s *ActionsStore) { func actionsMirrorSyncDelete(t *testing.T, ctx context.Context, s *ActionsStore) {
conf.SetMockUI(t, conf.UIOpts{
User: conf.UIUserOpts{
NewsFeedPagingNum: 20,
},
})
alice, err := newUsersStore(s.db).Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) alice, err := newUsersStore(s.db).Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
repo, err := newReposStore(s.db).Create(ctx, repo, err := newReposStore(s.db).Create(ctx,
@@ -559,6 +582,12 @@ func actionsMirrorSyncDelete(t *testing.T, ctx context.Context, s *ActionsStore)
} }
func actionsMirrorSyncPush(t *testing.T, ctx context.Context, s *ActionsStore) { func actionsMirrorSyncPush(t *testing.T, ctx context.Context, s *ActionsStore) {
conf.SetMockUI(t, conf.UIOpts{
User: conf.UIUserOpts{
NewsFeedPagingNum: 20,
},
})
alice, err := newUsersStore(s.db).Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) alice, err := newUsersStore(s.db).Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
repo, err := newReposStore(s.db).Create(ctx, repo, err := newReposStore(s.db).Create(ctx,
@@ -624,6 +653,12 @@ func actionsMirrorSyncPush(t *testing.T, ctx context.Context, s *ActionsStore) {
} }
func actionsNewRepo(t *testing.T, ctx context.Context, s *ActionsStore) { func actionsNewRepo(t *testing.T, ctx context.Context, s *ActionsStore) {
conf.SetMockUI(t, conf.UIOpts{
User: conf.UIUserOpts{
NewsFeedPagingNum: 20,
},
})
alice, err := newUsersStore(s.db).Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) alice, err := newUsersStore(s.db).Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
repo, err := newReposStore(s.db).Create(ctx, repo, err := newReposStore(s.db).Create(ctx,
@@ -703,6 +738,11 @@ func actionsPushTag(t *testing.T, ctx context.Context, s *ActionsStore) {
// to the mock server because this function holds a lock. // to the mock server because this function holds a lock.
conf.SetMockServer(t, conf.ServerOpts{}) conf.SetMockServer(t, conf.ServerOpts{})
conf.SetMockSSH(t, conf.SSHOpts{}) conf.SetMockSSH(t, conf.SSHOpts{})
conf.SetMockUI(t, conf.UIOpts{
User: conf.UIUserOpts{
NewsFeedPagingNum: 20,
},
})
alice, err := newUsersStore(s.db).Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) alice, err := newUsersStore(s.db).Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
@@ -796,6 +836,12 @@ func actionsPushTag(t *testing.T, ctx context.Context, s *ActionsStore) {
} }
func actionsRenameRepo(t *testing.T, ctx context.Context, s *ActionsStore) { func actionsRenameRepo(t *testing.T, ctx context.Context, s *ActionsStore) {
conf.SetMockUI(t, conf.UIOpts{
User: conf.UIUserOpts{
NewsFeedPagingNum: 20,
},
})
alice, err := newUsersStore(s.db).Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) alice, err := newUsersStore(s.db).Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
repo, err := newReposStore(s.db).Create(ctx, repo, err := newReposStore(s.db).Create(ctx,
@@ -833,6 +879,12 @@ func actionsRenameRepo(t *testing.T, ctx context.Context, s *ActionsStore) {
} }
func actionsTransferRepo(t *testing.T, ctx context.Context, s *ActionsStore) { func actionsTransferRepo(t *testing.T, ctx context.Context, s *ActionsStore) {
conf.SetMockUI(t, conf.UIOpts{
User: conf.UIUserOpts{
NewsFeedPagingNum: 20,
},
})
alice, err := newUsersStore(s.db).Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) alice, err := newUsersStore(s.db).Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
bob, err := newUsersStore(s.db).Create(ctx, "bob", "bob@example.com", CreateUserOptions{}) bob, err := newUsersStore(s.db).Create(ctx, "bob", "bob@example.com", CreateUserOptions{})

View File

@@ -10,7 +10,7 @@ import (
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
gouuid "github.com/satori/go.uuid" gouuid "github.com/satori/go.uuid"
"gorm.io/gorm" "xorm.io/xorm"
"gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/errutil" "gogs.io/gogs/internal/errutil"
@@ -19,26 +19,25 @@ import (
// Attachment represent a attachment of issue/comment/release. // Attachment represent a attachment of issue/comment/release.
type Attachment struct { type Attachment struct {
ID int64 ID int64
UUID string `gorm:"column:uuid;type:varchar(191);uniqueIndex"` UUID string `xorm:"uuid UNIQUE"`
IssueID int64 `gorm:"index"` IssueID int64 `xorm:"INDEX"`
CommentID int64 CommentID int64
ReleaseID int64 `gorm:"index"` ReleaseID int64 `xorm:"INDEX"`
Name string Name string
Created time.Time `gorm:"-" json:"-"` Created time.Time `xorm:"-" json:"-" gorm:"-"`
CreatedUnix int64 CreatedUnix int64
} }
func (a *Attachment) BeforeCreate(tx *gorm.DB) error { func (a *Attachment) BeforeInsert() {
if a.CreatedUnix == 0 { a.CreatedUnix = time.Now().Unix()
a.CreatedUnix = tx.NowFunc().Unix()
}
return nil
} }
func (a *Attachment) AfterFind(tx *gorm.DB) error { func (a *Attachment) AfterSet(colName string, _ xorm.Cell) {
switch colName {
case "created_unix":
a.Created = time.Unix(a.CreatedUnix, 0).Local() a.Created = time.Unix(a.CreatedUnix, 0).Local()
return nil }
} }
// AttachmentLocalPath returns where attachment is stored in local file system based on given UUID. // AttachmentLocalPath returns where attachment is stored in local file system based on given UUID.
@@ -75,7 +74,7 @@ func NewAttachment(name string, buf []byte, file multipart.File) (_ *Attachment,
return nil, errors.Newf("copy: %v", err) return nil, errors.Newf("copy: %v", err)
} }
if err := db.Create(attach).Error; err != nil { if _, err := x.Insert(attach); err != nil {
return nil, err return nil, err
} }
@@ -101,60 +100,60 @@ func (ErrAttachmentNotExist) NotFound() bool {
return true return true
} }
func getAttachmentByUUID(e *gorm.DB, uuid string) (*Attachment, error) { func getAttachmentByUUID(e Engine, uuid string) (*Attachment, error) {
attach := &Attachment{} attach := &Attachment{UUID: uuid}
err := e.Where("uuid = ?", uuid).First(attach).Error has, err := e.Get(attach)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrAttachmentNotExist{args: map[string]any{"uuid": uuid}}
}
return nil, err return nil, err
} else if !has {
return nil, ErrAttachmentNotExist{args: map[string]any{"uuid": uuid}}
} }
return attach, nil return attach, nil
} }
func getAttachmentsByUUIDs(e *gorm.DB, uuids []string) ([]*Attachment, error) { func getAttachmentsByUUIDs(e Engine, uuids []string) ([]*Attachment, error) {
if len(uuids) == 0 { if len(uuids) == 0 {
return []*Attachment{}, nil return []*Attachment{}, nil
} }
// Silently drop invalid uuids.
attachments := make([]*Attachment, 0, len(uuids)) attachments := make([]*Attachment, 0, len(uuids))
return attachments, e.Where("uuid IN ?", uuids).Find(&attachments).Error return attachments, e.In("uuid", uuids).Find(&attachments)
} }
// GetAttachmentByUUID returns attachment by given UUID. // GetAttachmentByUUID returns attachment by given UUID.
func GetAttachmentByUUID(uuid string) (*Attachment, error) { func GetAttachmentByUUID(uuid string) (*Attachment, error) {
return getAttachmentByUUID(db, uuid) return getAttachmentByUUID(x, uuid)
} }
func getAttachmentsByIssueID(e *gorm.DB, issueID int64) ([]*Attachment, error) { func getAttachmentsByIssueID(e Engine, issueID int64) ([]*Attachment, error) {
attachments := make([]*Attachment, 0, 5) attachments := make([]*Attachment, 0, 5)
return attachments, e.Where("issue_id = ? AND comment_id = 0", issueID).Find(&attachments).Error return attachments, e.Where("issue_id = ? AND comment_id = 0", issueID).Find(&attachments)
} }
// GetAttachmentsByIssueID returns all attachments of an issue. // GetAttachmentsByIssueID returns all attachments of an issue.
func GetAttachmentsByIssueID(issueID int64) ([]*Attachment, error) { func GetAttachmentsByIssueID(issueID int64) ([]*Attachment, error) {
return getAttachmentsByIssueID(db, issueID) return getAttachmentsByIssueID(x, issueID)
} }
func getAttachmentsByCommentID(e *gorm.DB, commentID int64) ([]*Attachment, error) { func getAttachmentsByCommentID(e Engine, commentID int64) ([]*Attachment, error) {
attachments := make([]*Attachment, 0, 5) attachments := make([]*Attachment, 0, 5)
return attachments, e.Where("comment_id = ?", commentID).Find(&attachments).Error return attachments, e.Where("comment_id=?", commentID).Find(&attachments)
} }
// GetAttachmentsByCommentID returns all attachments of a comment. // GetAttachmentsByCommentID returns all attachments of a comment.
func GetAttachmentsByCommentID(commentID int64) ([]*Attachment, error) { func GetAttachmentsByCommentID(commentID int64) ([]*Attachment, error) {
return getAttachmentsByCommentID(db, commentID) return getAttachmentsByCommentID(x, commentID)
} }
func getAttachmentsByReleaseID(e *gorm.DB, releaseID int64) ([]*Attachment, error) { func getAttachmentsByReleaseID(e Engine, releaseID int64) ([]*Attachment, error) {
attachments := make([]*Attachment, 0, 10) attachments := make([]*Attachment, 0, 10)
return attachments, e.Where("release_id = ?", releaseID).Find(&attachments).Error return attachments, e.Where("release_id = ?", releaseID).Find(&attachments)
} }
// GetAttachmentsByReleaseID returns all attachments of a release. // GetAttachmentsByReleaseID returns all attachments of a release.
func GetAttachmentsByReleaseID(releaseID int64) ([]*Attachment, error) { func GetAttachmentsByReleaseID(releaseID int64) ([]*Attachment, error) {
return getAttachmentsByReleaseID(db, releaseID) return getAttachmentsByReleaseID(x, releaseID)
} }
// DeleteAttachment deletes the given attachment and optionally the associated file. // DeleteAttachment deletes the given attachment and optionally the associated file.
@@ -172,7 +171,7 @@ func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) {
} }
} }
if err := db.Delete(a).Error; err != nil { if _, err := x.Delete(a); err != nil {
return i, err return i, err
} }
} }

View File

@@ -17,6 +17,8 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/schema" "gorm.io/gorm/schema"
log "unknwon.dev/clog/v2" log "unknwon.dev/clog/v2"
"xorm.io/core"
"xorm.io/xorm"
"gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/osutil" "gogs.io/gogs/internal/osutil"
@@ -120,19 +122,19 @@ func dumpLegacyTables(ctx context.Context, dirPath string, verbose bool) error {
log.Trace("Dumping table %q...", tableName) log.Trace("Dumping table %q...", tableName)
} }
err := func() error {
tableFile := filepath.Join(dirPath, tableName+".json") tableFile := filepath.Join(dirPath, tableName+".json")
f, err := os.Create(tableFile) f, err := os.Create(tableFile)
if err != nil { if err != nil {
return errors.Wrap(err, "create JSON file") return errors.Newf("create JSON file: %v", err)
} }
defer func() { _ = f.Close() }()
return dumpTable(ctx, db, table, f) if err = x.Context(ctx).Asc("id").Iterate(table, func(idx int, bean any) (err error) {
}() return jsoniter.NewEncoder(f).Encode(bean)
if err != nil { }); err != nil {
return errors.Wrapf(err, "dump table %q", tableName) _ = f.Close()
return errors.Newf("dump table '%s': %v", tableName, err)
} }
_ = f.Close()
} }
return nil return nil
} }
@@ -227,6 +229,8 @@ func importTable(ctx context.Context, db *gorm.DB, table any, r io.Reader) error
} }
func importLegacyTables(ctx context.Context, dirPath string, verbose bool) error { func importLegacyTables(ctx context.Context, dirPath string, verbose bool) error {
snakeMapper := core.SnakeMapper{}
skipInsertProcessors := map[string]bool{ skipInsertProcessors := map[string]bool{
"mirror": true, "mirror": true,
"milestone": true, "milestone": true,
@@ -251,9 +255,9 @@ func importLegacyTables(ctx context.Context, dirPath string, verbose bool) error
log.Trace("Importing table %q...", tableName) log.Trace("Importing table %q...", tableName)
} }
if err := db.WithContext(ctx).Migrator().DropTable(table); err != nil { if err := x.DropTables(table); err != nil {
return errors.Newf("drop table %q: %v", tableName, err) return errors.Newf("drop table %q: %v", tableName, err)
} else if err = db.WithContext(ctx).Migrator().AutoMigrate(table); err != nil { } else if err = x.Sync2(table); err != nil {
return errors.Newf("sync table %q: %v", tableName, err) return errors.Newf("sync table %q: %v", tableName, err)
} }
@@ -261,28 +265,16 @@ func importLegacyTables(ctx context.Context, dirPath string, verbose bool) error
if err != nil { if err != nil {
return errors.Newf("open JSON file: %v", err) return errors.Newf("open JSON file: %v", err)
} }
rawTableName := x.TableName(table)
s, err := schema.Parse(table, &sync.Map{}, db.NamingStrategy) _, isInsertProcessor := table.(xorm.BeforeInsertProcessor)
if err != nil {
_ = f.Close()
return errors.Wrap(err, "parse schema")
}
rawTableName := s.Table
_, isInsertProcessor := table.(interface{ BeforeCreate(*gorm.DB) error })
scanner := bufio.NewScanner(f) scanner := bufio.NewScanner(f)
for scanner.Scan() { for scanner.Scan() {
// PostgreSQL does not like the null characters (U+0000) if err = jsoniter.Unmarshal(scanner.Bytes(), table); err != nil {
cleaned := bytes.ReplaceAll(scanner.Bytes(), []byte("\\u0000"), []byte(""))
if err = jsoniter.Unmarshal(cleaned, table); err != nil {
_ = f.Close()
return errors.Newf("unmarshal to struct: %v", err) return errors.Newf("unmarshal to struct: %v", err)
} }
if err = db.WithContext(ctx).Create(table).Error; err != nil { if _, err = x.Insert(table); err != nil {
_ = f.Close() return errors.Newf("insert strcut: %v", err)
return errors.Newf("insert struct: %v", err)
} }
var meta struct { var meta struct {
@@ -291,30 +283,30 @@ func importLegacyTables(ctx context.Context, dirPath string, verbose bool) error
DeadlineUnix int64 DeadlineUnix int64
ClosedDateUnix int64 ClosedDateUnix int64
} }
if err = jsoniter.Unmarshal(cleaned, &meta); err != nil { if err = jsoniter.Unmarshal(scanner.Bytes(), &meta); err != nil {
log.Error("Failed to unmarshal to map: %v", err) log.Error("Failed to unmarshal to map: %v", err)
} }
// Reset created_unix back to the date saved in archive because Create method updates its value // Reset created_unix back to the date save in archive because Insert method updates its value
if isInsertProcessor && !skipInsertProcessors[rawTableName] { if isInsertProcessor && !skipInsertProcessors[rawTableName] {
if err = db.WithContext(ctx).Exec("UPDATE `"+rawTableName+"` SET created_unix=? WHERE id=?", meta.CreatedUnix, meta.ID).Error; err != nil { if _, err = x.Exec("UPDATE `"+rawTableName+"` SET created_unix=? WHERE id=?", meta.CreatedUnix, meta.ID); err != nil {
log.Error("Failed to reset '%s.created_unix': %v", rawTableName, err) log.Error("Failed to reset '%s.created_unix': %v", rawTableName, err)
} }
} }
switch rawTableName { switch rawTableName {
case "milestone": case "milestone":
if err = db.WithContext(ctx).Exec("UPDATE `"+rawTableName+"` SET deadline_unix=?, closed_date_unix=? WHERE id=?", meta.DeadlineUnix, meta.ClosedDateUnix, meta.ID).Error; err != nil { if _, err = x.Exec("UPDATE `"+rawTableName+"` SET deadline_unix=?, closed_date_unix=? WHERE id=?", meta.DeadlineUnix, meta.ClosedDateUnix, meta.ID); err != nil {
log.Error("Failed to reset 'milestone.deadline_unix', 'milestone.closed_date_unix': %v", err) log.Error("Failed to reset 'milestone.deadline_unix', 'milestone.closed_date_unix': %v", err)
} }
} }
} }
_ = f.Close()
// PostgreSQL needs manually reset table sequence for auto increment keys // PostgreSQL needs manually reset table sequence for auto increment keys
if conf.UsePostgreSQL { if conf.UsePostgreSQL {
rawTableName := snakeMapper.Obj2Table(tableName)
seqName := rawTableName + "_id_seq" seqName := rawTableName + "_id_seq"
if err = db.WithContext(ctx).Exec(fmt.Sprintf(`SELECT setval('%s', COALESCE((SELECT MAX(id)+1 FROM "%s"), 1), false);`, seqName, rawTableName)).Error; err != nil { if _, err = x.Exec(fmt.Sprintf(`SELECT setval('%s', COALESCE((SELECT MAX(id)+1 FROM "%s"), 1), false);`, seqName, rawTableName)); err != nil {
return errors.Newf("reset table %q' sequence: %v", rawTableName, err) return errors.Newf("reset table %q' sequence: %v", rawTableName, err)
} }
} }

View File

@@ -7,10 +7,11 @@ import (
"time" "time"
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
api "github.com/gogs/go-gogs-client"
"github.com/unknwon/com" "github.com/unknwon/com"
"gorm.io/gorm"
log "unknwon.dev/clog/v2" log "unknwon.dev/clog/v2"
"xorm.io/xorm"
api "github.com/gogs/go-gogs-client"
"gogs.io/gogs/internal/errutil" "gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/markup" "gogs.io/gogs/internal/markup"
@@ -49,50 +50,47 @@ type Comment struct {
ID int64 ID int64
Type CommentType Type CommentType
PosterID int64 PosterID int64
Poster *User `gorm:"-" json:"-"` Poster *User `xorm:"-" json:"-" gorm:"-"`
IssueID int64 `gorm:"index"` IssueID int64 `xorm:"INDEX"`
Issue *Issue `gorm:"-" json:"-"` Issue *Issue `xorm:"-" json:"-" gorm:"-"`
CommitID int64 CommitID int64
Line int64 Line int64
Content string `gorm:"type:text"` Content string `xorm:"TEXT"`
RenderedContent string `gorm:"-" json:"-"` RenderedContent string `xorm:"-" json:"-" gorm:"-"`
Created time.Time `gorm:"-" json:"-"` Created time.Time `xorm:"-" json:"-" gorm:"-"`
CreatedUnix int64 CreatedUnix int64
Updated time.Time `gorm:"-" json:"-"` Updated time.Time `xorm:"-" json:"-" gorm:"-"`
UpdatedUnix int64 UpdatedUnix int64
// Reference issue in commit message // Reference issue in commit message
CommitSHA string `gorm:"type:varchar(40)"` CommitSHA string `xorm:"VARCHAR(40)"`
Attachments []*Attachment `gorm:"-" json:"-"` Attachments []*Attachment `xorm:"-" json:"-" gorm:"-"`
// For view issue page. // For view issue page.
ShowTag CommentTag `gorm:"-" json:"-"` ShowTag CommentTag `xorm:"-" json:"-" gorm:"-"`
} }
func (c *Comment) BeforeCreate(tx *gorm.DB) error { func (c *Comment) BeforeInsert() {
if c.CreatedUnix == 0 { c.CreatedUnix = time.Now().Unix()
c.CreatedUnix = tx.NowFunc().Unix()
}
if c.UpdatedUnix == 0 {
c.UpdatedUnix = c.CreatedUnix c.UpdatedUnix = c.CreatedUnix
} }
return nil
func (c *Comment) BeforeUpdate() {
c.UpdatedUnix = time.Now().Unix()
} }
func (c *Comment) BeforeUpdate(tx *gorm.DB) error { func (c *Comment) AfterSet(colName string, _ xorm.Cell) {
c.UpdatedUnix = tx.NowFunc().Unix() switch colName {
return nil case "created_unix":
}
func (c *Comment) AfterFind(tx *gorm.DB) error {
c.Created = time.Unix(c.CreatedUnix, 0).Local() c.Created = time.Unix(c.CreatedUnix, 0).Local()
case "updated_unix":
c.Updated = time.Unix(c.UpdatedUnix, 0).Local() c.Updated = time.Unix(c.UpdatedUnix, 0).Local()
return nil }
} }
func (c *Comment) loadAttributes(tx *gorm.DB) (err error) { func (c *Comment) loadAttributes(e Engine) (err error) {
if c.Poster == nil { if c.Poster == nil {
c.Poster, err = Handle.Users().GetByID(context.TODO(), c.PosterID) c.Poster, err = Handle.Users().GetByID(context.TODO(), c.PosterID)
if err != nil { if err != nil {
@@ -106,12 +104,12 @@ func (c *Comment) loadAttributes(tx *gorm.DB) (err error) {
} }
if c.Issue == nil { if c.Issue == nil {
c.Issue, err = getRawIssueByID(tx, c.IssueID) c.Issue, err = getRawIssueByID(e, c.IssueID)
if err != nil { if err != nil {
return errors.Newf("getIssueByID [%d]: %v", c.IssueID, err) return errors.Newf("getIssueByID [%d]: %v", c.IssueID, err)
} }
if c.Issue.Repo == nil { if c.Issue.Repo == nil {
c.Issue.Repo, err = getRepositoryByID(tx, c.Issue.RepoID) c.Issue.Repo, err = getRepositoryByID(e, c.Issue.RepoID)
if err != nil { if err != nil {
return errors.Newf("getRepositoryByID [%d]: %v", c.Issue.RepoID, err) return errors.Newf("getRepositoryByID [%d]: %v", c.Issue.RepoID, err)
} }
@@ -119,7 +117,7 @@ func (c *Comment) loadAttributes(tx *gorm.DB) (err error) {
} }
if c.Attachments == nil { if c.Attachments == nil {
c.Attachments, err = getAttachmentsByCommentID(tx, c.ID) c.Attachments, err = getAttachmentsByCommentID(e, c.ID)
if err != nil { if err != nil {
return errors.Newf("getAttachmentsByCommentID [%d]: %v", c.ID, err) return errors.Newf("getAttachmentsByCommentID [%d]: %v", c.ID, err)
} }
@@ -129,7 +127,7 @@ func (c *Comment) loadAttributes(tx *gorm.DB) (err error) {
} }
func (c *Comment) LoadAttributes() error { func (c *Comment) LoadAttributes() error {
return c.loadAttributes(db) return c.loadAttributes(x)
} }
func (c *Comment) HTMLURL() string { func (c *Comment) HTMLURL() string {
@@ -165,9 +163,9 @@ func (c *Comment) EventTag() string {
// mailParticipants sends new comment emails to repository watchers // mailParticipants sends new comment emails to repository watchers
// and mentioned people. // and mentioned people.
func (c *Comment) mailParticipants(tx *gorm.DB, opType ActionType, issue *Issue) (err error) { func (c *Comment) mailParticipants(e Engine, opType ActionType, issue *Issue) (err error) {
mentions := markup.FindAllMentions(c.Content) mentions := markup.FindAllMentions(c.Content)
if err = updateIssueMentions(tx, c.IssueID, mentions); err != nil { if err = updateIssueMentions(e, c.IssueID, mentions); err != nil {
return errors.Newf("UpdateIssueMentions [%d]: %v", c.IssueID, err) return errors.Newf("UpdateIssueMentions [%d]: %v", c.IssueID, err)
} }
@@ -186,7 +184,7 @@ func (c *Comment) mailParticipants(tx *gorm.DB, opType ActionType, issue *Issue)
return nil return nil
} }
func createComment(tx *gorm.DB, opts *CreateCommentOptions) (_ *Comment, err error) { func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) {
comment := &Comment{ comment := &Comment{
Type: opts.Type, Type: opts.Type,
PosterID: opts.Doer.ID, PosterID: opts.Doer.ID,
@@ -197,7 +195,7 @@ func createComment(tx *gorm.DB, opts *CreateCommentOptions) (_ *Comment, err err
Line: opts.LineNum, Line: opts.LineNum,
Content: opts.Content, Content: opts.Content,
} }
if err = tx.Create(comment).Error; err != nil { if _, err = e.Insert(comment); err != nil {
return nil, err return nil, err
} }
@@ -218,14 +216,14 @@ func createComment(tx *gorm.DB, opts *CreateCommentOptions) (_ *Comment, err err
case CommentTypeComment: case CommentTypeComment:
act.OpType = ActionCommentIssue act.OpType = ActionCommentIssue
if err = tx.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID).Error; err != nil { if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil {
return nil, err return nil, err
} }
// Check attachments // Check attachments
attachments := make([]*Attachment, 0, len(opts.Attachments)) attachments := make([]*Attachment, 0, len(opts.Attachments))
for _, uuid := range opts.Attachments { for _, uuid := range opts.Attachments {
attach, err := getAttachmentByUUID(tx, uuid) attach, err := getAttachmentByUUID(e, uuid)
if err != nil { if err != nil {
if IsErrAttachmentNotExist(err) { if IsErrAttachmentNotExist(err) {
continue continue
@@ -238,10 +236,8 @@ func createComment(tx *gorm.DB, opts *CreateCommentOptions) (_ *Comment, err err
for i := range attachments { for i := range attachments {
attachments[i].IssueID = opts.Issue.ID attachments[i].IssueID = opts.Issue.ID
attachments[i].CommentID = comment.ID attachments[i].CommentID = comment.ID
if err = tx.Model(attachments[i]).Where("id = ?", attachments[i].ID).Updates(map[string]any{ // No assign value could be 0, so ignore AllCols().
"issue_id": attachments[i].IssueID, if _, err = e.ID(attachments[i].ID).Update(attachments[i]); err != nil {
"comment_id": attachments[i].CommentID,
}).Error; err != nil {
return nil, errors.Newf("update attachment [%d]: %v", attachments[i].ID, err) return nil, errors.Newf("update attachment [%d]: %v", attachments[i].ID, err)
} }
} }
@@ -253,9 +249,9 @@ func createComment(tx *gorm.DB, opts *CreateCommentOptions) (_ *Comment, err err
} }
if opts.Issue.IsPull { if opts.Issue.IsPull {
err = tx.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls-1 WHERE id=?", opts.Repo.ID).Error _, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls-1 WHERE id=?", opts.Repo.ID)
} else { } else {
err = tx.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?", opts.Repo.ID).Error _, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?", opts.Repo.ID)
} }
if err != nil { if err != nil {
return nil, err return nil, err
@@ -268,38 +264,38 @@ func createComment(tx *gorm.DB, opts *CreateCommentOptions) (_ *Comment, err err
} }
if opts.Issue.IsPull { if opts.Issue.IsPull {
err = tx.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls+1 WHERE id=?", opts.Repo.ID).Error _, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls+1 WHERE id=?", opts.Repo.ID)
} else { } else {
err = tx.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?", opts.Repo.ID).Error _, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?", opts.Repo.ID)
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
if err = tx.Exec("UPDATE `issue` SET updated_unix = ? WHERE id = ?", tx.NowFunc().Unix(), opts.Issue.ID).Error; err != nil { if _, err = e.Exec("UPDATE `issue` SET updated_unix = ? WHERE id = ?", time.Now().Unix(), opts.Issue.ID); err != nil {
return nil, errors.Newf("update issue 'updated_unix': %v", err) return nil, errors.Newf("update issue 'updated_unix': %v", err)
} }
// Notify watchers for whatever action comes in, ignore if no action type. // Notify watchers for whatever action comes in, ignore if no action type.
if act.OpType > 0 { if act.OpType > 0 {
if err = notifyWatchers(tx, act); err != nil { if err = notifyWatchers(e, act); err != nil {
log.Error("notifyWatchers: %v", err) log.Error("notifyWatchers: %v", err)
} }
if err = comment.mailParticipants(tx, act.OpType, opts.Issue); err != nil { if err = comment.mailParticipants(e, act.OpType, opts.Issue); err != nil {
log.Error("MailParticipants: %v", err) log.Error("MailParticipants: %v", err)
} }
} }
return comment, comment.loadAttributes(tx) return comment, comment.loadAttributes(e)
} }
func createStatusComment(tx *gorm.DB, doer *User, repo *Repository, issue *Issue) (*Comment, error) { func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue) (*Comment, error) {
cmtType := CommentTypeClose cmtType := CommentTypeClose
if !issue.IsClosed { if !issue.IsClosed {
cmtType = CommentTypeReopen cmtType = CommentTypeReopen
} }
return createComment(tx, &CreateCommentOptions{ return createComment(e, &CreateCommentOptions{
Type: cmtType, Type: cmtType,
Doer: doer, Doer: doer,
Repo: repo, Repo: repo,
@@ -322,12 +318,18 @@ type CreateCommentOptions struct {
// CreateComment creates comment of issue or commit. // CreateComment creates comment of issue or commit.
func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) { func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) {
err = db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
var err error defer sess.Close()
comment, err = createComment(tx, opts) if err = sess.Begin(); err != nil {
return err return nil, err
}) }
return comment, err
comment, err = createComment(sess, opts)
if err != nil {
return nil, err
}
return comment, sess.Commit()
} }
// CreateIssueComment creates a plain issue comment. // CreateIssueComment creates a plain issue comment.
@@ -365,12 +367,14 @@ func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commi
} }
// Check if same reference from same commit has already existed. // Check if same reference from same commit has already existed.
var count int64 has, err := x.Get(&Comment{
err := db.Model(new(Comment)).Where("type = ? AND issue_id = ? AND commit_sha = ?", Type: CommentTypeCommitRef,
CommentTypeCommitRef, issue.ID, commitSHA).Count(&count).Error IssueID: issue.ID,
CommitSHA: commitSHA,
})
if err != nil { if err != nil {
return errors.Newf("check reference comment: %v", err) return errors.Newf("check reference comment: %v", err)
} else if count > 0 { } else if has {
return nil return nil
} }
@@ -407,20 +411,19 @@ func (ErrCommentNotExist) NotFound() bool {
// GetCommentByID returns the comment by given ID. // GetCommentByID returns the comment by given ID.
func GetCommentByID(id int64) (*Comment, error) { func GetCommentByID(id int64) (*Comment, error) {
c := new(Comment) c := new(Comment)
err := db.Where("id = ?", id).First(c).Error has, err := x.Id(id).Get(c)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrCommentNotExist{args: map[string]any{"commentID": id}}
}
return nil, err return nil, err
} else if !has {
return nil, ErrCommentNotExist{args: map[string]any{"commentID": id}}
} }
return c, c.LoadAttributes() return c, c.LoadAttributes()
} }
// FIXME: use CommentList to improve performance. // FIXME: use CommentList to improve performance.
func loadCommentsAttributes(tx *gorm.DB, comments []*Comment) (err error) { func loadCommentsAttributes(e Engine, comments []*Comment) (err error) {
for i := range comments { for i := range comments {
if err = comments[i].loadAttributes(tx); err != nil { if err = comments[i].loadAttributes(e); err != nil {
return errors.Newf("loadAttributes [%d]: %v", comments[i].ID, err) return errors.Newf("loadAttributes [%d]: %v", comments[i].ID, err)
} }
} }
@@ -428,55 +431,53 @@ func loadCommentsAttributes(tx *gorm.DB, comments []*Comment) (err error) {
return nil return nil
} }
func getCommentsByIssueIDSince(tx *gorm.DB, issueID, since int64) ([]*Comment, error) { func getCommentsByIssueIDSince(e Engine, issueID, since int64) ([]*Comment, error) {
comments := make([]*Comment, 0, 10) comments := make([]*Comment, 0, 10)
query := tx.Where("issue_id = ?", issueID).Order("created_unix ASC") sess := e.Where("issue_id = ?", issueID).Asc("created_unix")
if since > 0 { if since > 0 {
query = query.Where("updated_unix >= ?", since) sess.And("updated_unix >= ?", since)
} }
if err := query.Find(&comments).Error; err != nil { if err := sess.Find(&comments); err != nil {
return nil, err return nil, err
} }
return comments, loadCommentsAttributes(tx, comments) return comments, loadCommentsAttributes(e, comments)
} }
func getCommentsByRepoIDSince(tx *gorm.DB, repoID, since int64) ([]*Comment, error) { func getCommentsByRepoIDSince(e Engine, repoID, since int64) ([]*Comment, error) {
comments := make([]*Comment, 0, 10) comments := make([]*Comment, 0, 10)
query := tx.Joins("INNER JOIN issue ON issue.id = comment.issue_id"). sess := e.Where("issue.repo_id = ?", repoID).Join("INNER", "issue", "issue.id = comment.issue_id").Asc("comment.created_unix")
Where("issue.repo_id = ?", repoID).
Order("comment.created_unix ASC")
if since > 0 { if since > 0 {
query = query.Where("comment.updated_unix >= ?", since) sess.And("comment.updated_unix >= ?", since)
} }
if err := query.Find(&comments).Error; err != nil { if err := sess.Find(&comments); err != nil {
return nil, err return nil, err
} }
return comments, loadCommentsAttributes(tx, comments) return comments, loadCommentsAttributes(e, comments)
} }
func getCommentsByIssueID(tx *gorm.DB, issueID int64) ([]*Comment, error) { func getCommentsByIssueID(e Engine, issueID int64) ([]*Comment, error) {
return getCommentsByIssueIDSince(tx, issueID, -1) return getCommentsByIssueIDSince(e, issueID, -1)
} }
// GetCommentsByIssueID returns all comments of an issue. // GetCommentsByIssueID returns all comments of an issue.
func GetCommentsByIssueID(issueID int64) ([]*Comment, error) { func GetCommentsByIssueID(issueID int64) ([]*Comment, error) {
return getCommentsByIssueID(db, issueID) return getCommentsByIssueID(x, issueID)
} }
// GetCommentsByIssueIDSince returns a list of comments of an issue since a given time point. // GetCommentsByIssueIDSince returns a list of comments of an issue since a given time point.
func GetCommentsByIssueIDSince(issueID, since int64) ([]*Comment, error) { func GetCommentsByIssueIDSince(issueID, since int64) ([]*Comment, error) {
return getCommentsByIssueIDSince(db, issueID, since) return getCommentsByIssueIDSince(x, issueID, since)
} }
// GetCommentsByRepoIDSince returns a list of comments for all issues in a repo since a given time point. // GetCommentsByRepoIDSince returns a list of comments for all issues in a repo since a given time point.
func GetCommentsByRepoIDSince(repoID, since int64) ([]*Comment, error) { func GetCommentsByRepoIDSince(repoID, since int64) ([]*Comment, error) {
return getCommentsByRepoIDSince(db, repoID, since) return getCommentsByRepoIDSince(x, repoID, since)
} }
// UpdateComment updates information of comment. // UpdateComment updates information of comment.
func UpdateComment(doer *User, c *Comment, oldContent string) (err error) { func UpdateComment(doer *User, c *Comment, oldContent string) (err error) {
if err = db.Model(c).Where("id = ?", c.ID).Updates(c).Error; err != nil { if _, err = x.Id(c.ID).AllCols().Update(c); err != nil {
return err return err
} }
@@ -510,21 +511,24 @@ func DeleteCommentByID(doer *User, id int64) error {
return err return err
} }
err = db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
if err := tx.Where("id = ?", comment.ID).Delete(new(Comment)).Error; err != nil { defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.ID(comment.ID).Delete(new(Comment)); err != nil {
return err return err
} }
if comment.Type == CommentTypeComment { if comment.Type == CommentTypeComment {
if err := tx.Exec("UPDATE `issue` SET num_comments = num_comments - 1 WHERE id = ?", comment.IssueID).Error; err != nil { if _, err = sess.Exec("UPDATE `issue` SET num_comments = num_comments - 1 WHERE id = ?", comment.IssueID); err != nil {
return err return err
} }
} }
return nil if err = sess.Commit(); err != nil {
}) return errors.Newf("commit: %v", err)
if err != nil {
return errors.Newf("transaction: %v", err)
} }
_, err = DeleteAttachmentsByComment(comment.ID, true) _, err = DeleteAttachmentsByComment(comment.ID, true)

File diff suppressed because it is too large Load Diff

View File

@@ -6,12 +6,14 @@ import (
"strconv" "strconv"
"strings" "strings"
"xorm.io/xorm"
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
api "github.com/gogs/go-gogs-client" api "github.com/gogs/go-gogs-client"
"gorm.io/gorm"
"gogs.io/gogs/internal/errutil" "gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/lazyregexp" "gogs.io/gogs/internal/lazyregexp"
"gogs.io/gogs/internal/tool"
) )
var labelColorPattern = lazyregexp.New("#([a-fA-F0-9]{6})") var labelColorPattern = lazyregexp.New("#([a-fA-F0-9]{6})")
@@ -51,13 +53,13 @@ func GetLabelTemplateFile(name string) ([][2]string, error) {
// Label represents a label of repository for issues. // Label represents a label of repository for issues.
type Label struct { type Label struct {
ID int64 ID int64
RepoID int64 `gorm:"index"` RepoID int64 `xorm:"INDEX"`
Name string Name string
Color string `gorm:"type:varchar(7)"` Color string `xorm:"VARCHAR(7)"`
NumIssues int NumIssues int
NumClosedIssues int NumClosedIssues int
NumOpenIssues int `gorm:"-" json:"-"` NumOpenIssues int `xorm:"-" json:"-" gorm:"-"`
IsChecked bool `gorm:"-" json:"-"` IsChecked bool `xorm:"-" json:"-" gorm:"-"`
} }
func (l *Label) APIFormat() *api.Label { func (l *Label) APIFormat() *api.Label {
@@ -95,7 +97,8 @@ func (l *Label) ForegroundColor() template.CSS {
// NewLabels creates new label(s) for a repository. // NewLabels creates new label(s) for a repository.
func NewLabels(labels ...*Label) error { func NewLabels(labels ...*Label) error {
return db.Create(labels).Error _, err := x.Insert(labels)
return err
} }
var _ errutil.NotFound = (*ErrLabelNotExist)(nil) var _ errutil.NotFound = (*ErrLabelNotExist)(nil)
@@ -120,22 +123,20 @@ func (ErrLabelNotExist) NotFound() bool {
// getLabelOfRepoByName returns a label by Name in given repository. // getLabelOfRepoByName returns a label by Name in given repository.
// If pass repoID as 0, then ORM will ignore limitation of repository // If pass repoID as 0, then ORM will ignore limitation of repository
// and can return arbitrary label with any valid ID. // and can return arbitrary label with any valid ID.
func getLabelOfRepoByName(tx *gorm.DB, repoID int64, labelName string) (*Label, error) { func getLabelOfRepoByName(e Engine, repoID int64, labelName string) (*Label, error) {
if len(labelName) <= 0 { if len(labelName) <= 0 {
return nil, ErrLabelNotExist{args: map[string]any{"repoID": repoID}} return nil, ErrLabelNotExist{args: map[string]any{"repoID": repoID}}
} }
l := &Label{} l := &Label{
query := tx.Where("name = ?", labelName) Name: labelName,
if repoID > 0 { RepoID: repoID,
query = query.Where("repo_id = ?", repoID)
} }
err := query.First(l).Error has, err := e.Get(l)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrLabelNotExist{args: map[string]any{"repoID": repoID}}
}
return nil, err return nil, err
} else if !has {
return nil, ErrLabelNotExist{args: map[string]any{"repoID": repoID}}
} }
return l, nil return l, nil
} }
@@ -143,56 +144,54 @@ func getLabelOfRepoByName(tx *gorm.DB, repoID int64, labelName string) (*Label,
// getLabelInRepoByID returns a label by ID in given repository. // getLabelInRepoByID returns a label by ID in given repository.
// If pass repoID as 0, then ORM will ignore limitation of repository // If pass repoID as 0, then ORM will ignore limitation of repository
// and can return arbitrary label with any valid ID. // and can return arbitrary label with any valid ID.
func getLabelOfRepoByID(tx *gorm.DB, repoID, labelID int64) (*Label, error) { func getLabelOfRepoByID(e Engine, repoID, labelID int64) (*Label, error) {
if labelID <= 0 { if labelID <= 0 {
return nil, ErrLabelNotExist{args: map[string]any{"repoID": repoID, "labelID": labelID}} return nil, ErrLabelNotExist{args: map[string]any{"repoID": repoID, "labelID": labelID}}
} }
l := &Label{} l := &Label{
query := tx.Where("id = ?", labelID) ID: labelID,
if repoID > 0 { RepoID: repoID,
query = query.Where("repo_id = ?", repoID)
} }
err := query.First(l).Error has, err := e.Get(l)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrLabelNotExist{args: map[string]any{"repoID": repoID, "labelID": labelID}}
}
return nil, err return nil, err
} else if !has {
return nil, ErrLabelNotExist{args: map[string]any{"repoID": repoID, "labelID": labelID}}
} }
return l, nil return l, nil
} }
// GetLabelByID returns a label by given ID. // GetLabelByID returns a label by given ID.
func GetLabelByID(id int64) (*Label, error) { func GetLabelByID(id int64) (*Label, error) {
return getLabelOfRepoByID(db, 0, id) return getLabelOfRepoByID(x, 0, id)
} }
// GetLabelOfRepoByID returns a label by ID in given repository. // GetLabelOfRepoByID returns a label by ID in given repository.
func GetLabelOfRepoByID(repoID, labelID int64) (*Label, error) { func GetLabelOfRepoByID(repoID, labelID int64) (*Label, error) {
return getLabelOfRepoByID(db, repoID, labelID) return getLabelOfRepoByID(x, repoID, labelID)
} }
// GetLabelOfRepoByName returns a label by name in given repository. // GetLabelOfRepoByName returns a label by name in given repository.
func GetLabelOfRepoByName(repoID int64, labelName string) (*Label, error) { func GetLabelOfRepoByName(repoID int64, labelName string) (*Label, error) {
return getLabelOfRepoByName(db, repoID, labelName) return getLabelOfRepoByName(x, repoID, labelName)
} }
// GetLabelsInRepoByIDs returns a list of labels by IDs in given repository, // GetLabelsInRepoByIDs returns a list of labels by IDs in given repository,
// it silently ignores label IDs that are not belong to the repository. // it silently ignores label IDs that are not belong to the repository.
func GetLabelsInRepoByIDs(repoID int64, labelIDs []int64) ([]*Label, error) { func GetLabelsInRepoByIDs(repoID int64, labelIDs []int64) ([]*Label, error) {
labels := make([]*Label, 0, len(labelIDs)) labels := make([]*Label, 0, len(labelIDs))
return labels, db.Where("repo_id = ? AND id IN ?", repoID, labelIDs).Order("name ASC").Find(&labels).Error return labels, x.Where("repo_id = ?", repoID).In("id", tool.Int64sToStrings(labelIDs)).Asc("name").Find(&labels)
} }
// GetLabelsByRepoID returns all labels that belong to given repository by ID. // GetLabelsByRepoID returns all labels that belong to given repository by ID.
func GetLabelsByRepoID(repoID int64) ([]*Label, error) { func GetLabelsByRepoID(repoID int64) ([]*Label, error) {
labels := make([]*Label, 0, 10) labels := make([]*Label, 0, 10)
return labels, db.Where("repo_id = ?", repoID).Order("name ASC").Find(&labels).Error return labels, x.Where("repo_id = ?", repoID).Asc("name").Find(&labels)
} }
func getLabelsByIssueID(tx *gorm.DB, issueID int64) ([]*Label, error) { func getLabelsByIssueID(e Engine, issueID int64) ([]*Label, error) {
issueLabels, err := getIssueLabels(tx, issueID) issueLabels, err := getIssueLabels(e, issueID)
if err != nil { if err != nil {
return nil, errors.Newf("getIssueLabels: %v", err) return nil, errors.Newf("getIssueLabels: %v", err)
} else if len(issueLabels) == 0 { } else if len(issueLabels) == 0 {
@@ -205,21 +204,22 @@ func getLabelsByIssueID(tx *gorm.DB, issueID int64) ([]*Label, error) {
} }
labels := make([]*Label, 0, len(labelIDs)) labels := make([]*Label, 0, len(labelIDs))
return labels, tx.Where("id > 0 AND id IN ?", labelIDs).Order("name ASC").Find(&labels).Error return labels, e.Where("id > 0").In("id", tool.Int64sToStrings(labelIDs)).Asc("name").Find(&labels)
} }
// GetLabelsByIssueID returns all labels that belong to given issue by ID. // GetLabelsByIssueID returns all labels that belong to given issue by ID.
func GetLabelsByIssueID(issueID int64) ([]*Label, error) { func GetLabelsByIssueID(issueID int64) ([]*Label, error) {
return getLabelsByIssueID(db, issueID) return getLabelsByIssueID(x, issueID)
} }
func updateLabel(tx *gorm.DB, l *Label) error { func updateLabel(e Engine, l *Label) error {
return tx.Model(l).Where("id = ?", l.ID).Updates(l).Error _, err := e.ID(l.ID).AllCols().Update(l)
return err
} }
// UpdateLabel updates label information. // UpdateLabel updates label information.
func UpdateLabel(l *Label) error { func UpdateLabel(l *Label) error {
return updateLabel(db, l) return updateLabel(x, l)
} }
// DeleteLabel delete a label of given repository. // DeleteLabel delete a label of given repository.
@@ -232,15 +232,19 @@ func DeleteLabel(repoID, labelID int64) error {
return err return err
} }
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
if err := tx.Where("id = ?", labelID).Delete(new(Label)).Error; err != nil { defer sess.Close()
if err = sess.Begin(); err != nil {
return err return err
} }
if err := tx.Where("label_id = ?", labelID).Delete(new(IssueLabel)).Error; err != nil {
if _, err = sess.ID(labelID).Delete(new(Label)); err != nil {
return err
} else if _, err = sess.Where("label_id = ?", labelID).Delete(new(IssueLabel)); err != nil {
return err return err
} }
return nil
}) return sess.Commit()
} }
// .___ .____ ___. .__ // .___ .____ ___. .__
@@ -253,26 +257,25 @@ func DeleteLabel(repoID, labelID int64) error {
// IssueLabel represents an issue-lable relation. // IssueLabel represents an issue-lable relation.
type IssueLabel struct { type IssueLabel struct {
ID int64 ID int64
IssueID int64 `gorm:"uniqueIndex:issue_label_unique"` IssueID int64 `xorm:"UNIQUE(s)"`
LabelID int64 `gorm:"uniqueIndex:issue_label_unique"` LabelID int64 `xorm:"UNIQUE(s)"`
} }
func hasIssueLabel(tx *gorm.DB, issueID, labelID int64) bool { func hasIssueLabel(e Engine, issueID, labelID int64) bool {
var count int64 has, _ := e.Where("issue_id = ? AND label_id = ?", issueID, labelID).Get(new(IssueLabel))
tx.Model(new(IssueLabel)).Where("issue_id = ? AND label_id = ?", issueID, labelID).Count(&count) return has
return count > 0
} }
// HasIssueLabel returns true if issue has been labeled. // HasIssueLabel returns true if issue has been labeled.
func HasIssueLabel(issueID, labelID int64) bool { func HasIssueLabel(issueID, labelID int64) bool {
return hasIssueLabel(db, issueID, labelID) return hasIssueLabel(x, issueID, labelID)
} }
func newIssueLabel(tx *gorm.DB, issue *Issue, label *Label) (err error) { func newIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) {
if err = tx.Create(&IssueLabel{ if _, err = e.Insert(&IssueLabel{
IssueID: issue.ID, IssueID: issue.ID,
LabelID: label.ID, LabelID: label.ID,
}).Error; err != nil { }); err != nil {
return err return err
} }
@@ -281,7 +284,7 @@ func newIssueLabel(tx *gorm.DB, issue *Issue, label *Label) (err error) {
label.NumClosedIssues++ label.NumClosedIssues++
} }
if err = updateLabel(tx, label); err != nil { if err = updateLabel(e, label); err != nil {
return errors.Newf("updateLabel: %v", err) return errors.Newf("updateLabel: %v", err)
} }
@@ -295,18 +298,26 @@ func NewIssueLabel(issue *Issue, label *Label) (err error) {
return nil return nil
} }
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
return newIssueLabel(tx, issue, label) defer sess.Close()
}) if err = sess.Begin(); err != nil {
return err
} }
func newIssueLabels(tx *gorm.DB, issue *Issue, labels []*Label) (err error) { if err = newIssueLabel(sess, issue, label); err != nil {
return err
}
return sess.Commit()
}
func newIssueLabels(e *xorm.Session, issue *Issue, labels []*Label) (err error) {
for i := range labels { for i := range labels {
if hasIssueLabel(tx, issue.ID, labels[i].ID) { if hasIssueLabel(e, issue.ID, labels[i].ID) {
continue continue
} }
if err = newIssueLabel(tx, issue, labels[i]); err != nil { if err = newIssueLabel(e, issue, labels[i]); err != nil {
return errors.Newf("newIssueLabel: %v", err) return errors.Newf("newIssueLabel: %v", err)
} }
} }
@@ -316,23 +327,34 @@ func newIssueLabels(tx *gorm.DB, issue *Issue, labels []*Label) (err error) {
// NewIssueLabels creates a list of issue-label relations. // NewIssueLabels creates a list of issue-label relations.
func NewIssueLabels(issue *Issue, labels []*Label) (err error) { func NewIssueLabels(issue *Issue, labels []*Label) (err error) {
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
return newIssueLabels(tx, issue, labels) defer sess.Close()
}) if err = sess.Begin(); err != nil {
return err
} }
func getIssueLabels(tx *gorm.DB, issueID int64) ([]*IssueLabel, error) { if err = newIssueLabels(sess, issue, labels); err != nil {
return err
}
return sess.Commit()
}
func getIssueLabels(e Engine, issueID int64) ([]*IssueLabel, error) {
issueLabels := make([]*IssueLabel, 0, 10) issueLabels := make([]*IssueLabel, 0, 10)
return issueLabels, tx.Where("issue_id = ?", issueID).Order("label_id ASC").Find(&issueLabels).Error return issueLabels, e.Where("issue_id=?", issueID).Asc("label_id").Find(&issueLabels)
} }
// GetIssueLabels returns all issue-label relations of given issue by ID. // GetIssueLabels returns all issue-label relations of given issue by ID.
func GetIssueLabels(issueID int64) ([]*IssueLabel, error) { func GetIssueLabels(issueID int64) ([]*IssueLabel, error) {
return getIssueLabels(db, issueID) return getIssueLabels(x, issueID)
} }
func deleteIssueLabel(tx *gorm.DB, issue *Issue, label *Label) (err error) { func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) {
if err = tx.Where("issue_id = ? AND label_id = ?", issue.ID, label.ID).Delete(&IssueLabel{}).Error; err != nil { if _, err = e.Delete(&IssueLabel{
IssueID: issue.ID,
LabelID: label.ID,
}); err != nil {
return err return err
} }
@@ -340,7 +362,7 @@ func deleteIssueLabel(tx *gorm.DB, issue *Issue, label *Label) (err error) {
if issue.IsClosed { if issue.IsClosed {
label.NumClosedIssues-- label.NumClosedIssues--
} }
if err = updateLabel(tx, label); err != nil { if err = updateLabel(e, label); err != nil {
return errors.Newf("updateLabel: %v", err) return errors.Newf("updateLabel: %v", err)
} }
@@ -355,7 +377,15 @@ func deleteIssueLabel(tx *gorm.DB, issue *Issue, label *Label) (err error) {
// DeleteIssueLabel deletes issue-label relation. // DeleteIssueLabel deletes issue-label relation.
func DeleteIssueLabel(issue *Issue, label *Label) (err error) { func DeleteIssueLabel(issue *Issue, label *Label) (err error) {
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
return deleteIssueLabel(tx, issue, label) defer sess.Close()
}) if err = sess.Begin(); err != nil {
return err
}
if err = deleteIssueLabel(sess, issue, label); err != nil {
return err
}
return sess.Commit()
} }

View File

@@ -176,7 +176,7 @@ func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string)
// and mentioned people. // and mentioned people.
func (issue *Issue) MailParticipants() (err error) { func (issue *Issue) MailParticipants() (err error) {
mentions := markup.FindAllMentions(issue.Content) mentions := markup.FindAllMentions(issue.Content)
if err = updateIssueMentions(db, issue.ID, mentions); err != nil { if err = updateIssueMentions(x, issue.ID, mentions); err != nil {
return errors.Newf("UpdateIssueMentions [%d]: %v", issue.ID, err) return errors.Newf("UpdateIssueMentions [%d]: %v", issue.ID, err)
} }

View File

@@ -8,7 +8,6 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/logger" "gorm.io/gorm/logger"
_ "modernc.org/sqlite"
log "unknwon.dev/clog/v2" log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/conf"

View File

@@ -7,7 +7,6 @@ import (
"testing" "testing"
"gorm.io/gorm/logger" "gorm.io/gorm/logger"
_ "modernc.org/sqlite"
log "unknwon.dev/clog/v2" log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/testutil" "gogs.io/gogs/internal/testutil"

View File

@@ -4,10 +4,11 @@ import (
"fmt" "fmt"
"time" "time"
log "unknwon.dev/clog/v2"
"xorm.io/xorm"
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
api "github.com/gogs/go-gogs-client" api "github.com/gogs/go-gogs-client"
"gorm.io/gorm"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/errutil" "gogs.io/gogs/internal/errutil"
@@ -16,30 +17,29 @@ import (
// Milestone represents a milestone of repository. // Milestone represents a milestone of repository.
type Milestone struct { type Milestone struct {
ID int64 ID int64
RepoID int64 `gorm:"index"` RepoID int64 `xorm:"INDEX"`
Name string Name string
Content string `gorm:"type:text"` Content string `xorm:"TEXT"`
RenderedContent string `gorm:"-" json:"-"` RenderedContent string `xorm:"-" json:"-" gorm:"-"`
IsClosed bool IsClosed bool
NumIssues int NumIssues int
NumClosedIssues int NumClosedIssues int
NumOpenIssues int `gorm:"-" json:"-"` NumOpenIssues int `xorm:"-" json:"-" gorm:"-"`
Completeness int // Percentage(1-100). Completeness int // Percentage(1-100).
IsOverDue bool `gorm:"-" json:"-"` IsOverDue bool `xorm:"-" json:"-" gorm:"-"`
DeadlineString string `gorm:"-" json:"-"` DeadlineString string `xorm:"-" json:"-" gorm:"-"`
Deadline time.Time `gorm:"-" json:"-"` Deadline time.Time `xorm:"-" json:"-" gorm:"-"`
DeadlineUnix int64 DeadlineUnix int64
ClosedDate time.Time `gorm:"-" json:"-"` ClosedDate time.Time `xorm:"-" json:"-" gorm:"-"`
ClosedDateUnix int64 ClosedDateUnix int64
} }
func (m *Milestone) BeforeCreate(tx *gorm.DB) error { func (m *Milestone) BeforeInsert() {
m.DeadlineUnix = m.Deadline.Unix() m.DeadlineUnix = m.Deadline.Unix()
return nil
} }
func (m *Milestone) BeforeUpdate(tx *gorm.DB) error { func (m *Milestone) BeforeUpdate() {
if m.NumIssues > 0 { if m.NumIssues > 0 {
m.Completeness = m.NumClosedIssues * 100 / m.NumIssues m.Completeness = m.NumClosedIssues * 100 / m.NumIssues
} else { } else {
@@ -48,22 +48,27 @@ func (m *Milestone) BeforeUpdate(tx *gorm.DB) error {
m.DeadlineUnix = m.Deadline.Unix() m.DeadlineUnix = m.Deadline.Unix()
m.ClosedDateUnix = m.ClosedDate.Unix() m.ClosedDateUnix = m.ClosedDate.Unix()
return nil
} }
func (m *Milestone) AfterFind(tx *gorm.DB) error { func (m *Milestone) AfterSet(colName string, _ xorm.Cell) {
switch colName {
case "num_closed_issues":
m.NumOpenIssues = m.NumIssues - m.NumClosedIssues m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
case "deadline_unix":
m.Deadline = time.Unix(m.DeadlineUnix, 0).Local() m.Deadline = time.Unix(m.DeadlineUnix, 0).Local()
if m.Deadline.Year() != 9999 { if m.Deadline.Year() == 9999 {
return
}
m.DeadlineString = m.Deadline.Format("2006-01-02") m.DeadlineString = m.Deadline.Format("2006-01-02")
if time.Now().Local().After(m.Deadline) { if time.Now().Local().After(m.Deadline) {
m.IsOverDue = true m.IsOverDue = true
} }
}
case "closed_date_unix":
m.ClosedDate = time.Unix(m.ClosedDateUnix, 0).Local() m.ClosedDate = time.Unix(m.ClosedDateUnix, 0).Local()
return nil }
} }
// State returns string representation of milestone status. // State returns string representation of milestone status.
@@ -97,24 +102,30 @@ func (m *Milestone) APIFormat() *api.Milestone {
} }
func (m *Milestone) CountIssues(isClosed, includePulls bool) int64 { func (m *Milestone) CountIssues(isClosed, includePulls bool) int64 {
query := db.Model(new(Issue)).Where("milestone_id = ? AND is_closed = ?", m.ID, isClosed) sess := x.Where("milestone_id = ?", m.ID).And("is_closed = ?", isClosed)
if !includePulls { if !includePulls {
query = query.Where("is_pull = ?", false) sess.And("is_pull = ?", false)
} }
var count int64 count, _ := sess.Count(new(Issue))
query.Count(&count)
return count return count
} }
// NewMilestone creates new milestone of repository. // NewMilestone creates new milestone of repository.
func NewMilestone(m *Milestone) (err error) { func NewMilestone(m *Milestone) (err error) {
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
if err := tx.Create(m).Error; err != nil { defer sess.Close()
if err = sess.Begin(); err != nil {
return err return err
} }
return tx.Exec("UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?", m.RepoID).Error if _, err = sess.Insert(m); err != nil {
}) return err
}
if _, err = sess.Exec("UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?", m.RepoID); err != nil {
return err
}
return sess.Commit()
} }
var _ errutil.NotFound = (*ErrMilestoneNotExist)(nil) var _ errutil.NotFound = (*ErrMilestoneNotExist)(nil)
@@ -136,73 +147,74 @@ func (ErrMilestoneNotExist) NotFound() bool {
return true return true
} }
func getMilestoneByRepoID(e *gorm.DB, repoID, id int64) (*Milestone, error) { func getMilestoneByRepoID(e Engine, repoID, id int64) (*Milestone, error) {
m := &Milestone{} m := &Milestone{
err := e.Where("id = ? AND repo_id = ?", id, repoID).First(m).Error ID: id,
if err != nil { RepoID: repoID,
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrMilestoneNotExist{args: map[string]any{"repoID": repoID, "milestoneID": id}}
} }
has, err := e.Get(m)
if err != nil {
return nil, err return nil, err
} else if !has {
return nil, ErrMilestoneNotExist{args: map[string]any{"repoID": repoID, "milestoneID": id}}
} }
return m, nil return m, nil
} }
// GetWebhookByRepoID returns the milestone in a repository. // GetWebhookByRepoID returns the milestone in a repository.
func GetMilestoneByRepoID(repoID, id int64) (*Milestone, error) { func GetMilestoneByRepoID(repoID, id int64) (*Milestone, error) {
return getMilestoneByRepoID(db, repoID, id) return getMilestoneByRepoID(x, repoID, id)
} }
// GetMilestonesByRepoID returns all milestones of a repository. // GetMilestonesByRepoID returns all milestones of a repository.
func GetMilestonesByRepoID(repoID int64) ([]*Milestone, error) { func GetMilestonesByRepoID(repoID int64) ([]*Milestone, error) {
miles := make([]*Milestone, 0, 10) miles := make([]*Milestone, 0, 10)
return miles, db.Where("repo_id = ?", repoID).Find(&miles).Error return miles, x.Where("repo_id = ?", repoID).Find(&miles)
} }
// GetMilestones returns a list of milestones of given repository and status. // GetMilestones returns a list of milestones of given repository and status.
func GetMilestones(repoID int64, page int, isClosed bool) ([]*Milestone, error) { func GetMilestones(repoID int64, page int, isClosed bool) ([]*Milestone, error) {
miles := make([]*Milestone, 0, conf.UI.IssuePagingNum) miles := make([]*Milestone, 0, conf.UI.IssuePagingNum)
query := db.Where("repo_id = ? AND is_closed = ?", repoID, isClosed) sess := x.Where("repo_id = ? AND is_closed = ?", repoID, isClosed)
if page > 0 { if page > 0 {
query = query.Limit(conf.UI.IssuePagingNum).Offset((page - 1) * conf.UI.IssuePagingNum) sess = sess.Limit(conf.UI.IssuePagingNum, (page-1)*conf.UI.IssuePagingNum)
} }
return miles, query.Find(&miles).Error return miles, sess.Find(&miles)
} }
func updateMilestone(e *gorm.DB, m *Milestone) error { func updateMilestone(e Engine, m *Milestone) error {
return e.Model(m).Where("id = ?", m.ID).Updates(m).Error _, err := e.ID(m.ID).AllCols().Update(m)
return err
} }
// UpdateMilestone updates information of given milestone. // UpdateMilestone updates information of given milestone.
func UpdateMilestone(m *Milestone) error { func UpdateMilestone(m *Milestone) error {
return updateMilestone(db, m) return updateMilestone(x, m)
} }
func countRepoMilestones(e *gorm.DB, repoID int64) int64 { func countRepoMilestones(e Engine, repoID int64) int64 {
var count int64 count, _ := e.Where("repo_id=?", repoID).Count(new(Milestone))
e.Model(new(Milestone)).Where("repo_id = ?", repoID).Count(&count)
return count return count
} }
// CountRepoMilestones returns number of milestones in given repository. // CountRepoMilestones returns number of milestones in given repository.
func CountRepoMilestones(repoID int64) int64 { func CountRepoMilestones(repoID int64) int64 {
return countRepoMilestones(db, repoID) return countRepoMilestones(x, repoID)
} }
func countRepoClosedMilestones(e *gorm.DB, repoID int64) int64 { func countRepoClosedMilestones(e Engine, repoID int64) int64 {
var count int64 closed, _ := e.Where("repo_id=? AND is_closed=?", repoID, true).Count(new(Milestone))
e.Model(new(Milestone)).Where("repo_id = ? AND is_closed = ?", repoID, true).Count(&count) return closed
return count
} }
// CountRepoClosedMilestones returns number of closed milestones in given repository. // CountRepoClosedMilestones returns number of closed milestones in given repository.
func CountRepoClosedMilestones(repoID int64) int64 { func CountRepoClosedMilestones(repoID int64) int64 {
return countRepoClosedMilestones(db, repoID) return countRepoClosedMilestones(x, repoID)
} }
// MilestoneStats returns number of open and closed milestones of given repository. // MilestoneStats returns number of open and closed milestones of given repository.
func MilestoneStats(repoID int64) (open, closed int64) { func MilestoneStats(repoID int64) (open, closed int64) {
db.Model(new(Milestone)).Where("repo_id = ? AND is_closed = ?", repoID, false).Count(&open) open, _ = x.Where("repo_id=? AND is_closed=?", repoID, false).Count(new(Milestone))
return open, CountRepoClosedMilestones(repoID) return open, CountRepoClosedMilestones(repoID)
} }
@@ -215,19 +227,26 @@ func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) {
return err return err
} }
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
m.IsClosed = isClosed defer sess.Close()
if err := updateMilestone(tx, m); err != nil { if err = sess.Begin(); err != nil {
return err return err
} }
repo.NumMilestones = int(countRepoMilestones(tx, repo.ID)) m.IsClosed = isClosed
repo.NumClosedMilestones = int(countRepoClosedMilestones(tx, repo.ID)) if err = updateMilestone(sess, m); err != nil {
return tx.Model(repo).Where("id = ?", repo.ID).Updates(repo).Error return err
})
} }
func changeMilestoneIssueStats(e *gorm.DB, issue *Issue) error { repo.NumMilestones = int(countRepoMilestones(sess, repo.ID))
repo.NumClosedMilestones = int(countRepoClosedMilestones(sess, repo.ID))
if _, err = sess.ID(repo.ID).AllCols().Update(repo); err != nil {
return err
}
return sess.Commit()
}
func changeMilestoneIssueStats(e *xorm.Session, issue *Issue) error {
if issue.MilestoneID == 0 { if issue.MilestoneID == 0 {
return nil return nil
} }
@@ -251,12 +270,20 @@ func changeMilestoneIssueStats(e *gorm.DB, issue *Issue) error {
// ChangeMilestoneIssueStats updates the open/closed issues counter and progress // ChangeMilestoneIssueStats updates the open/closed issues counter and progress
// for the milestone associated with the given issue. // for the milestone associated with the given issue.
func ChangeMilestoneIssueStats(issue *Issue) (err error) { func ChangeMilestoneIssueStats(issue *Issue) (err error) {
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
return changeMilestoneIssueStats(tx, issue) defer sess.Close()
}) if err = sess.Begin(); err != nil {
return err
} }
func changeMilestoneAssign(e *gorm.DB, issue *Issue, oldMilestoneID int64) error { if err = changeMilestoneIssueStats(sess, issue); err != nil {
return err
}
return sess.Commit()
}
func changeMilestoneAssign(e *xorm.Session, issue *Issue, oldMilestoneID int64) error {
if oldMilestoneID > 0 { if oldMilestoneID > 0 {
m, err := getMilestoneByRepoID(e, issue.RepoID, oldMilestoneID) m, err := getMilestoneByRepoID(e, issue.RepoID, oldMilestoneID)
if err != nil { if err != nil {
@@ -270,9 +297,7 @@ func changeMilestoneAssign(e *gorm.DB, issue *Issue, oldMilestoneID int64) error
if err = updateMilestone(e, m); err != nil { if err = updateMilestone(e, m); err != nil {
return err return err
} } else if _, err = e.Exec("UPDATE `issue_user` SET milestone_id = 0 WHERE issue_id = ?", issue.ID); err != nil {
if err = e.Exec("UPDATE `issue_user` SET milestone_id = 0 WHERE issue_id = ?", issue.ID).Error; err != nil {
return err return err
} }
@@ -292,9 +317,7 @@ func changeMilestoneAssign(e *gorm.DB, issue *Issue, oldMilestoneID int64) error
if err = updateMilestone(e, m); err != nil { if err = updateMilestone(e, m); err != nil {
return err return err
} } else if _, err = e.Exec("UPDATE `issue_user` SET milestone_id = ? WHERE issue_id = ?", m.ID, issue.ID); err != nil {
if err = e.Exec("UPDATE `issue_user` SET milestone_id = ? WHERE issue_id = ?", m.ID, issue.ID).Error; err != nil {
return err return err
} }
@@ -306,11 +329,18 @@ func changeMilestoneAssign(e *gorm.DB, issue *Issue, oldMilestoneID int64) error
// ChangeMilestoneAssign changes assignment of milestone for issue. // ChangeMilestoneAssign changes assignment of milestone for issue.
func ChangeMilestoneAssign(doer *User, issue *Issue, oldMilestoneID int64) (err error) { func ChangeMilestoneAssign(doer *User, issue *Issue, oldMilestoneID int64) (err error) {
err = db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
return changeMilestoneAssign(tx, issue, oldMilestoneID) defer sess.Close()
}) if err = sess.Begin(); err != nil {
if err != nil { return err
return errors.Newf("transaction: %v", err) }
if err = changeMilestoneAssign(sess, issue, oldMilestoneID); err != nil {
return err
}
if err = sess.Commit(); err != nil {
return errors.Newf("commit: %v", err)
} }
var hookAction api.HookIssueAction var hookAction api.HookIssueAction
@@ -364,21 +394,26 @@ func DeleteMilestoneOfRepoByID(repoID, id int64) error {
return err return err
} }
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
if err := tx.Where("id = ?", m.ID).Delete(new(Milestone)).Error; err != nil { defer sess.Close()
if err = sess.Begin(); err != nil {
return err return err
} }
repo.NumMilestones = int(countRepoMilestones(tx, repo.ID)) if _, err = sess.ID(m.ID).Delete(new(Milestone)); err != nil {
repo.NumClosedMilestones = int(countRepoClosedMilestones(tx, repo.ID))
if err := tx.Model(repo).Where("id = ?", repo.ID).Updates(repo).Error; err != nil {
return err return err
} }
if err := tx.Exec("UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?", m.ID).Error; err != nil { repo.NumMilestones = int(countRepoMilestones(sess, repo.ID))
repo.NumClosedMilestones = int(countRepoClosedMilestones(sess, repo.ID))
if _, err = sess.ID(repo.ID).AllCols().Update(repo); err != nil {
return err return err
} }
return tx.Exec("UPDATE `issue_user` SET milestone_id = 0 WHERE milestone_id = ?", m.ID).Error if _, err = sess.Exec("UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?", m.ID); err != nil {
}) return err
} else if _, err = sess.Exec("UPDATE `issue_user` SET milestone_id = 0 WHERE milestone_id = ?", m.ID); err != nil {
return err
}
return sess.Commit()
} }

View File

@@ -8,11 +8,12 @@ import (
"time" "time"
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
"github.com/gogs/git-module"
"github.com/unknwon/com" "github.com/unknwon/com"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
"gorm.io/gorm"
log "unknwon.dev/clog/v2" log "unknwon.dev/clog/v2"
"xorm.io/xorm"
"github.com/gogs/git-module"
"gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/process" "gogs.io/gogs/internal/process"
@@ -40,41 +41,41 @@ func (err MirrorNotExist) Error() string {
type Mirror struct { type Mirror struct {
ID int64 ID int64
RepoID int64 RepoID int64
Repo *Repository `gorm:"-" json:"-"` Repo *Repository `xorm:"-" json:"-" gorm:"-"`
Interval int // Hour. Interval int // Hour.
EnablePrune bool `gorm:"not null;default:true"` EnablePrune bool `xorm:"NOT NULL DEFAULT true"`
// Last and next sync time of Git data from upstream // Last and next sync time of Git data from upstream
LastSync time.Time `gorm:"-" json:"-"` LastSync time.Time `xorm:"-" json:"-" gorm:"-"`
LastSyncUnix int64 `gorm:"column:updated_unix"` LastSyncUnix int64 `xorm:"updated_unix"`
NextSync time.Time `gorm:"-" json:"-"` NextSync time.Time `xorm:"-" json:"-" gorm:"-"`
NextSyncUnix int64 `gorm:"column:next_update_unix"` NextSyncUnix int64 `xorm:"next_update_unix"`
address string `gorm:"-"` address string `xorm:"-"`
} }
func (m *Mirror) BeforeCreate(tx *gorm.DB) error { func (m *Mirror) BeforeInsert() {
m.NextSyncUnix = m.NextSync.Unix() m.NextSyncUnix = m.NextSync.Unix()
return nil
} }
func (m *Mirror) BeforeUpdate(tx *gorm.DB) error { func (m *Mirror) BeforeUpdate() {
m.LastSyncUnix = m.LastSync.Unix() m.LastSyncUnix = m.LastSync.Unix()
m.NextSyncUnix = m.NextSync.Unix() m.NextSyncUnix = m.NextSync.Unix()
return nil
} }
func (m *Mirror) AfterFind(tx *gorm.DB) error { func (m *Mirror) AfterSet(colName string, _ xorm.Cell) {
if m.RepoID > 0 {
var err error var err error
switch colName {
case "repo_id":
m.Repo, err = GetRepositoryByID(m.RepoID) m.Repo, err = GetRepositoryByID(m.RepoID)
if err != nil { if err != nil {
log.Error("GetRepositoryByID [%d]: %v", m.ID, err) log.Error("GetRepositoryByID [%d]: %v", m.ID, err)
} }
} case "updated_unix":
m.LastSync = time.Unix(m.LastSyncUnix, 0).Local() m.LastSync = time.Unix(m.LastSyncUnix, 0).Local()
case "next_update_unix":
m.NextSync = time.Unix(m.NextSyncUnix, 0).Local() m.NextSync = time.Unix(m.NextSyncUnix, 0).Local()
return nil }
} }
// ScheduleNextSync calculates and sets next sync time based on repository mirror setting. // ScheduleNextSync calculates and sets next sync time based on repository mirror setting.
@@ -276,33 +277,34 @@ func (m *Mirror) runSync() ([]*mirrorSyncResult, bool) {
return parseRemoteUpdateOutput(output), true return parseRemoteUpdateOutput(output), true
} }
func getMirrorByRepoID(e *gorm.DB, repoID int64) (*Mirror, error) { func getMirrorByRepoID(e Engine, repoID int64) (*Mirror, error) {
m := &Mirror{} m := &Mirror{RepoID: repoID}
err := e.Where("repo_id = ?", repoID).First(m).Error has, err := e.Get(m)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, MirrorNotExist{RepoID: repoID}
}
return nil, err return nil, err
} else if !has {
return nil, MirrorNotExist{RepoID: repoID}
} }
return m, nil return m, nil
} }
// GetMirrorByRepoID returns mirror information of a repository. // GetMirrorByRepoID returns mirror information of a repository.
func GetMirrorByRepoID(repoID int64) (*Mirror, error) { func GetMirrorByRepoID(repoID int64) (*Mirror, error) {
return getMirrorByRepoID(db, repoID) return getMirrorByRepoID(x, repoID)
} }
func updateMirror(e *gorm.DB, m *Mirror) error { func updateMirror(e Engine, m *Mirror) error {
return e.Model(m).Where("id = ?", m.ID).Updates(m).Error _, err := e.ID(m.ID).AllCols().Update(m)
return err
} }
func UpdateMirror(m *Mirror) error { func UpdateMirror(m *Mirror) error {
return updateMirror(db, m) return updateMirror(x, m)
} }
func DeleteMirrorByRepoID(repoID int64) error { func DeleteMirrorByRepoID(repoID int64) error {
return db.Where("repo_id = ?", repoID).Delete(&Mirror{}).Error _, err := x.Delete(&Mirror{RepoID: repoID})
return err
} }
// MirrorUpdate checks and updates mirror repositories. // MirrorUpdate checks and updates mirror repositories.
@@ -315,18 +317,17 @@ func MirrorUpdate() {
log.Trace("Doing: MirrorUpdate") log.Trace("Doing: MirrorUpdate")
var mirrors []*Mirror if err := x.Where("next_update_unix<=?", time.Now().Unix()).Iterate(new(Mirror), func(idx int, bean any) error {
if err := db.Where("next_update_unix <= ?", time.Now().Unix()).Find(&mirrors).Error; err != nil { m := bean.(*Mirror)
log.Error("MirrorUpdate: find mirrors: %v", err)
return
}
for _, m := range mirrors {
if m.Repo == nil { if m.Repo == nil {
log.Error("Disconnected mirror repository found: %d", m.ID) log.Error("Disconnected mirror repository found: %d", m.ID)
continue return nil
} }
MirrorQueue.Add(m.RepoID) MirrorQueue.Add(m.RepoID)
return nil
}); err != nil {
log.Error("MirrorUpdate: %v", err)
} }
} }
@@ -453,7 +454,7 @@ func SyncMirrors() {
} }
} }
if err = db.Exec("UPDATE mirror SET updated_unix = ? WHERE repo_id = ?", time.Now().Unix(), m.RepoID).Error; err != nil { if _, err = x.Exec("UPDATE mirror SET updated_unix = ? WHERE repo_id = ?", time.Now().Unix(), m.RepoID); err != nil {
log.Error("Update 'mirror.updated_unix' [%d]: %v", m.RepoID, err) log.Error("Update 'mirror.updated_unix' [%d]: %v", m.RepoID, err)
continue continue
} }
@@ -468,7 +469,7 @@ func SyncMirrors() {
continue continue
} }
if err = db.Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", latestCommitTime.Unix(), m.RepoID).Error; err != nil { if _, err = x.Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", latestCommitTime.Unix(), m.RepoID); err != nil {
log.Error("Update 'repository.updated_unix' [%d]: %v", m.RepoID, err) log.Error("Update 'repository.updated_unix' [%d]: %v", m.RepoID, err)
continue continue
} }

View File

@@ -2,6 +2,7 @@ package database
import ( import (
"context" "context"
"database/sql"
"fmt" "fmt"
"os" "os"
"path" "path"
@@ -10,23 +11,44 @@ import (
"time" "time"
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
"github.com/glebarez/go-sqlite"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/logger" "gorm.io/gorm/logger"
"gorm.io/gorm/schema"
log "unknwon.dev/clog/v2" log "unknwon.dev/clog/v2"
"xorm.io/core"
"xorm.io/xorm"
"gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/database/migrations" "gogs.io/gogs/internal/database/migrations"
"gogs.io/gogs/internal/dbutil" "gogs.io/gogs/internal/dbutil"
) )
// Engine represents a XORM engine or session.
type Engine interface {
Delete(any) (int64, error)
Exec(...any) (sql.Result, error)
Find(any, ...any) error
Get(any) (bool, error)
ID(any) *xorm.Session
In(string, ...any) *xorm.Session
Insert(...any) (int64, error)
InsertOne(any) (int64, error)
Iterate(any, xorm.IterFunc) error
Sql(string, ...any) *xorm.Session
Table(any) *xorm.Session
Where(any, ...any) *xorm.Session
}
var ( var (
db *gorm.DB x *xorm.Engine
legacyTables []any legacyTables []any
HasEngine bool HasEngine bool
) )
func init() { func init() {
// Register the pure-Go SQLite driver as "sqlite3" for XORM compatibility.
sql.Register("sqlite3", &sqlite.Driver{})
legacyTables = append(legacyTables, legacyTables = append(legacyTables,
new(User), new(PublicKey), new(TwoFactor), new(TwoFactorRecoveryCode), new(User), new(PublicKey), new(TwoFactor), new(TwoFactorRecoveryCode),
new(Repository), new(DeployKey), new(Collaboration), new(Upload), new(Repository), new(DeployKey), new(Collaboration), new(Upload),
@@ -37,90 +59,93 @@ func init() {
new(ProtectBranch), new(ProtectBranchWhitelist), new(ProtectBranch), new(ProtectBranchWhitelist),
new(Team), new(OrgUser), new(TeamUser), new(TeamRepo), new(Team), new(OrgUser), new(TeamUser), new(TeamRepo),
) )
gonicNames := []string{"SSL"}
for _, name := range gonicNames {
core.LintGonicMapper[name] = true
}
} }
func getGormDB(gormLogger logger.Writer) (*gorm.DB, error) { func getEngine() (*xorm.Engine, error) {
if conf.Database.Type == "sqlite3" { Param := "?"
if strings.Contains(conf.Database.Name, Param) {
Param = "&"
}
driver := conf.Database.Type
connStr := ""
switch conf.Database.Type {
case "mysql":
conf.UseMySQL = true
if conf.Database.Host[0] == '/' { // looks like a unix socket
connStr = fmt.Sprintf("%s:%s@unix(%s)/%s%scharset=utf8mb4&parseTime=true",
conf.Database.User, conf.Database.Password, conf.Database.Host, conf.Database.Name, Param)
} else {
connStr = fmt.Sprintf("%s:%s@tcp(%s)/%s%scharset=utf8mb4&parseTime=true",
conf.Database.User, conf.Database.Password, conf.Database.Host, conf.Database.Name, Param)
}
engineParams := map[string]string{"rowFormat": "DYNAMIC"}
return xorm.NewEngineWithParams(conf.Database.Type, connStr, engineParams)
case "postgres":
conf.UsePostgreSQL = true
host, port := dbutil.ParsePostgreSQLHostPort(conf.Database.Host)
connStr = fmt.Sprintf("user='%s' password='%s' host='%s' port='%s' dbname='%s' sslmode='%s' search_path='%s'",
conf.Database.User, conf.Database.Password, host, port, conf.Database.Name, conf.Database.SSLMode, conf.Database.Schema)
driver = "pgx"
case "mssql":
conf.UseMSSQL = true
host, port := dbutil.ParseMSSQLHostPort(conf.Database.Host)
connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, conf.Database.Name, conf.Database.User, conf.Database.Password)
case "sqlite3":
if err := os.MkdirAll(path.Dir(conf.Database.Path), os.ModePerm); err != nil { if err := os.MkdirAll(path.Dir(conf.Database.Path), os.ModePerm); err != nil {
return nil, errors.Newf("create directories: %v", err) return nil, errors.Newf("create directories: %v", err)
} }
}
level := logger.Info
if conf.IsProdMode() {
level = logger.Warn
}
logger.Default = logger.New(gormLogger, logger.Config{
SlowThreshold: 100 * time.Millisecond,
LogLevel: level,
})
gormDB, err := dbutil.OpenDB(
conf.Database,
&gorm.Config{
SkipDefaultTransaction: true,
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
NowFunc: func() time.Time {
return time.Now().UTC().Truncate(time.Microsecond)
},
},
)
if err != nil {
return nil, errors.Wrap(err, "open database")
}
sqlDB, err := gormDB.DB()
if err != nil {
return nil, errors.Wrap(err, "get underlying *sql.DB")
}
sqlDB.SetMaxOpenConns(conf.Database.MaxOpenConns)
sqlDB.SetMaxIdleConns(conf.Database.MaxIdleConns)
sqlDB.SetConnMaxLifetime(time.Minute)
switch conf.Database.Type {
case "postgres":
conf.UsePostgreSQL = true
case "mysql":
conf.UseMySQL = true
gormDB = gormDB.Set("gorm:table_options", "ENGINE=InnoDB").Session(&gorm.Session{})
case "sqlite3":
conf.UseSQLite3 = true conf.UseSQLite3 = true
case "mssql": connStr = "file:" + conf.Database.Path + "?cache=shared&mode=rwc"
conf.UseMSSQL = true
}
return gormDB, nil default:
return nil, errors.Newf("unknown database type: %s", conf.Database.Type)
}
return xorm.NewEngine(driver, connStr)
} }
func NewTestEngine() error { func NewTestEngine() error {
var err error x, err := getEngine()
db, err = getGormDB(&dbutil.Logger{Writer: os.Stdout})
if err != nil { if err != nil {
return errors.Newf("connect to database: %v", err) return errors.Newf("connect to database: %v", err)
} }
for _, table := range legacyTables { if conf.UsePostgreSQL {
if db.Migrator().HasTable(table) { x.SetSchema(conf.Database.Schema)
continue
} }
if err = db.Migrator().AutoMigrate(table); err != nil {
return errors.Wrap(err, "auto migrate") x.SetMapper(core.GonicMapper{})
} return x.StoreEngine("InnoDB").Sync2(legacyTables...)
}
return nil
} }
func SetEngine() (*gorm.DB, error) { func SetEngine() (*gorm.DB, error) {
var err error
x, err = getEngine()
if err != nil {
return nil, errors.Newf("connect to database: %v", err)
}
if conf.UsePostgreSQL {
x.SetSchema(conf.Database.Schema)
}
x.SetMapper(core.GonicMapper{})
var logPath string var logPath string
if conf.HookMode { if conf.HookMode {
logPath = filepath.Join(conf.Log.RootPath, "hooks", "gorm.log") logPath = filepath.Join(conf.Log.RootPath, "hooks", "xorm.log")
} else { } else {
logPath = filepath.Join(conf.Log.RootPath, "gorm.log") logPath = filepath.Join(conf.Log.RootPath, "xorm.log")
} }
sec := conf.File.Section("log.gorm") sec := conf.File.Section("log.xorm")
fileWriter, err := log.NewFileWriter(logPath, fileWriter, err := log.NewFileWriter(logPath,
log.FileRotationConfig{ log.FileRotationConfig{
Rotate: sec.Key("ROTATE").MustBool(true), Rotate: sec.Key("ROTATE").MustBool(true),
@@ -130,9 +155,20 @@ func SetEngine() (*gorm.DB, error) {
}, },
) )
if err != nil { if err != nil {
return nil, errors.Newf("create 'gorm.log': %v", err) return nil, errors.Newf("create 'xorm.log': %v", err)
} }
x.SetMaxOpenConns(conf.Database.MaxOpenConns)
x.SetMaxIdleConns(conf.Database.MaxIdleConns)
x.SetConnMaxLifetime(time.Second)
if conf.IsProdMode() {
x.SetLogger(xorm.NewSimpleLogger3(fileWriter, xorm.DEFAULT_LOG_PREFIX, xorm.DEFAULT_LOG_FLAG, core.LOG_ERR))
} else {
x.SetLogger(xorm.NewSimpleLogger(fileWriter))
}
x.ShowSQL(true)
var gormLogger logger.Writer var gormLogger logger.Writer
if conf.HookMode { if conf.HookMode {
gormLogger = &dbutil.Logger{Writer: fileWriter} gormLogger = &dbutil.Logger{Writer: fileWriter}
@@ -142,37 +178,23 @@ func SetEngine() (*gorm.DB, error) {
return nil, errors.Wrap(err, "new log writer") return nil, errors.Wrap(err, "new log writer")
} }
} }
db, err = getGormDB(gormLogger)
if err != nil {
return nil, err
}
return NewConnection(gormLogger) return NewConnection(gormLogger)
} }
func NewEngine() error { func NewEngine() error {
gormDB, err := SetEngine() db, err := SetEngine()
if err != nil { if err != nil {
return err return err
} }
if err = migrations.Migrate(gormDB); err != nil { if err = migrations.Migrate(db); err != nil {
return errors.Newf("migrate: %v", err) return errors.Newf("migrate: %v", err)
} }
for _, table := range legacyTables { if err = x.StoreEngine("InnoDB").Sync2(legacyTables...); err != nil {
if gormDB.Migrator().HasTable(table) { return errors.Wrap(err, "sync tables")
continue
}
name := strings.TrimPrefix(fmt.Sprintf("%T", table), "*database.")
if err = gormDB.Migrator().AutoMigrate(table); err != nil {
return errors.Wrapf(err, "auto migrate %q", name)
}
log.Trace("Auto migrated %q", name)
} }
HasEngine = true
return nil return nil
} }
@@ -190,54 +212,33 @@ type Statistic struct {
func GetStatistic(ctx context.Context) (stats Statistic) { func GetStatistic(ctx context.Context) (stats Statistic) {
stats.Counter.User = Handle.Users().Count(ctx) stats.Counter.User = Handle.Users().Count(ctx)
stats.Counter.Org = CountOrganizations() stats.Counter.Org = CountOrganizations()
var count int64 stats.Counter.PublicKey, _ = x.Count(new(PublicKey))
db.Model(new(PublicKey)).Count(&count)
stats.Counter.PublicKey = count
stats.Counter.Repo = CountRepositories(true) stats.Counter.Repo = CountRepositories(true)
db.Model(new(Watch)).Count(&count) stats.Counter.Watch, _ = x.Count(new(Watch))
stats.Counter.Watch = count stats.Counter.Star, _ = x.Count(new(Star))
db.Model(new(Star)).Count(&count) stats.Counter.Action, _ = x.Count(new(Action))
stats.Counter.Star = count stats.Counter.Access, _ = x.Count(new(Access))
db.Model(new(Action)).Count(&count) stats.Counter.Issue, _ = x.Count(new(Issue))
stats.Counter.Action = count stats.Counter.Comment, _ = x.Count(new(Comment))
db.Model(new(Access)).Count(&count)
stats.Counter.Access = count
db.Model(new(Issue)).Count(&count)
stats.Counter.Issue = count
db.Model(new(Comment)).Count(&count)
stats.Counter.Comment = count
stats.Counter.Oauth = 0 stats.Counter.Oauth = 0
db.Model(new(Follow)).Count(&count) stats.Counter.Follow, _ = x.Count(new(Follow))
stats.Counter.Follow = count stats.Counter.Mirror, _ = x.Count(new(Mirror))
db.Model(new(Mirror)).Count(&count) stats.Counter.Release, _ = x.Count(new(Release))
stats.Counter.Mirror = count
db.Model(new(Release)).Count(&count)
stats.Counter.Release = count
stats.Counter.LoginSource = Handle.LoginSources().Count(ctx) stats.Counter.LoginSource = Handle.LoginSources().Count(ctx)
db.Model(new(Webhook)).Count(&count) stats.Counter.Webhook, _ = x.Count(new(Webhook))
stats.Counter.Webhook = count stats.Counter.Milestone, _ = x.Count(new(Milestone))
db.Model(new(Milestone)).Count(&count) stats.Counter.Label, _ = x.Count(new(Label))
stats.Counter.Milestone = count stats.Counter.HookTask, _ = x.Count(new(HookTask))
db.Model(new(Label)).Count(&count) stats.Counter.Team, _ = x.Count(new(Team))
stats.Counter.Label = count stats.Counter.Attachment, _ = x.Count(new(Attachment))
db.Model(new(HookTask)).Count(&count)
stats.Counter.HookTask = count
db.Model(new(Team)).Count(&count)
stats.Counter.Team = count
db.Model(new(Attachment)).Count(&count)
stats.Counter.Attachment = count
return stats return stats
} }
func Ping() error { func Ping() error {
if db == nil { if x == nil {
return errors.New("database not available") return errors.New("database not available")
} }
sqlDB, err := db.DB() return x.Ping()
if err != nil {
return err
}
return sqlDB.Ping()
} }
// The version table. Should have only one row with id==1 // The version table. Should have only one row with id==1

View File

@@ -6,7 +6,8 @@ import (
"strings" "strings"
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
"gorm.io/gorm" "xorm.io/builder"
"xorm.io/xorm"
"gogs.io/gogs/internal/errutil" "gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/repoutil" "gogs.io/gogs/internal/repoutil"
@@ -25,32 +26,32 @@ func (org *User) IsOrgMember(uid int64) bool {
return org.IsOrganization() && IsOrganizationMember(org.ID, uid) return org.IsOrganization() && IsOrganizationMember(org.ID, uid)
} }
func (org *User) getTeam(tx *gorm.DB, name string) (*Team, error) { func (org *User) getTeam(e Engine, name string) (*Team, error) {
return getTeamOfOrgByName(tx, org.ID, name) return getTeamOfOrgByName(e, org.ID, name)
} }
// GetTeamOfOrgByName returns named team of organization. // GetTeamOfOrgByName returns named team of organization.
func (org *User) GetTeam(name string) (*Team, error) { func (org *User) GetTeam(name string) (*Team, error) {
return org.getTeam(db, name) return org.getTeam(x, name)
} }
func (org *User) getOwnerTeam(tx *gorm.DB) (*Team, error) { func (org *User) getOwnerTeam(e Engine) (*Team, error) {
return org.getTeam(tx, ownerTeamName) return org.getTeam(e, ownerTeamName)
} }
// GetOwnerTeam returns owner team of organization. // GetOwnerTeam returns owner team of organization.
func (org *User) GetOwnerTeam() (*Team, error) { func (org *User) GetOwnerTeam() (*Team, error) {
return org.getOwnerTeam(db) return org.getOwnerTeam(x)
} }
func (org *User) getTeams(tx *gorm.DB) (err error) { func (org *User) getTeams(e Engine) (err error) {
org.Teams, err = getTeamsByOrgID(tx, org.ID) org.Teams, err = getTeamsByOrgID(e, org.ID)
return err return err
} }
// GetTeams returns all teams that belong to organization. // GetTeams returns all teams that belong to organization.
func (org *User) GetTeams() error { func (org *User) GetTeams() error {
return org.getTeams(db) return org.getTeams(x)
} }
// TeamsHaveAccessToRepo returns all teams that have given access level to the repository. // TeamsHaveAccessToRepo returns all teams that have given access level to the repository.
@@ -85,13 +86,13 @@ func (org *User) RemoveMember(uid int64) error {
return RemoveOrgUser(org.ID, uid) return RemoveOrgUser(org.ID, uid)
} }
func (org *User) removeOrgRepo(tx *gorm.DB, repoID int64) error { func (org *User) removeOrgRepo(e Engine, repoID int64) error {
return removeOrgRepo(tx, org.ID, repoID) return removeOrgRepo(e, org.ID, repoID)
} }
// RemoveOrgRepo removes all team-repository relations of organization. // RemoveOrgRepo removes all team-repository relations of organization.
func (org *User) RemoveOrgRepo(repoID int64) error { func (org *User) RemoveOrgRepo(repoID int64) error {
return org.removeOrgRepo(db, repoID) return org.removeOrgRepo(x, repoID)
} }
// CreateOrganization creates record of a new organization. // CreateOrganization creates record of a new organization.
@@ -120,19 +121,24 @@ func CreateOrganization(org, owner *User) (err error) {
org.NumTeams = 1 org.NumTeams = 1
org.NumMembers = 1 org.NumMembers = 1
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
if err := tx.Create(org).Error; err != nil { defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Insert(org); err != nil {
return errors.Newf("insert organization: %v", err) return errors.Newf("insert organization: %v", err)
} }
_ = userutil.GenerateRandomAvatar(org.ID, org.Name, org.Email) _ = userutil.GenerateRandomAvatar(org.ID, org.Name, org.Email)
// Add initial creator to organization and owner team. // Add initial creator to organization and owner team.
if err := tx.Create(&OrgUser{ if _, err = sess.Insert(&OrgUser{
UID: owner.ID, UID: owner.ID,
OrgID: org.ID, OrgID: org.ID,
IsOwner: true, IsOwner: true,
NumTeams: 1, NumTeams: 1,
}).Error; err != nil { }); err != nil {
return errors.Newf("insert org-user relation: %v", err) return errors.Newf("insert org-user relation: %v", err)
} }
@@ -144,24 +150,23 @@ func CreateOrganization(org, owner *User) (err error) {
Authorize: AccessModeOwner, Authorize: AccessModeOwner,
NumMembers: 1, NumMembers: 1,
} }
if err := tx.Create(t).Error; err != nil { if _, err = sess.Insert(t); err != nil {
return errors.Newf("insert owner team: %v", err) return errors.Newf("insert owner team: %v", err)
} }
if err := tx.Create(&TeamUser{ if _, err = sess.Insert(&TeamUser{
UID: owner.ID, UID: owner.ID,
OrgID: org.ID, OrgID: org.ID,
TeamID: t.ID, TeamID: t.ID,
}).Error; err != nil { }); err != nil {
return errors.Newf("insert team-user relation: %v", err) return errors.Newf("insert team-user relation: %v", err)
} }
if err := os.MkdirAll(repoutil.UserPath(org.Name), os.ModePerm); err != nil { if err = os.MkdirAll(repoutil.UserPath(org.Name), os.ModePerm); err != nil {
return errors.Newf("create directory: %v", err) return errors.Newf("create directory: %v", err)
} }
return nil return sess.Commit()
})
} }
// GetOrgByName returns organization by given name. // GetOrgByName returns organization by given name.
@@ -169,36 +174,35 @@ func GetOrgByName(name string) (*User, error) {
if name == "" { if name == "" {
return nil, ErrOrgNotExist return nil, ErrOrgNotExist
} }
u := &User{} u := &User{
err := db.Where("lower_name = ? AND type = ?", strings.ToLower(name), UserTypeOrganization).First(u).Error LowerName: strings.ToLower(name),
if err != nil { Type: UserTypeOrganization,
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrOrgNotExist
} }
has, err := x.Get(u)
if err != nil {
return nil, err return nil, err
} else if !has {
return nil, ErrOrgNotExist
} }
return u, nil return u, nil
} }
// CountOrganizations returns number of organizations. // CountOrganizations returns number of organizations.
func CountOrganizations() int64 { func CountOrganizations() int64 {
var count int64 count, _ := x.Where("type=1").Count(new(User))
db.Model(new(User)).Where("type = ?", UserTypeOrganization).Count(&count)
return count return count
} }
// Organizations returns number of organizations in given page. // Organizations returns number of organizations in given page.
func Organizations(page, pageSize int) ([]*User, error) { func Organizations(page, pageSize int) ([]*User, error) {
orgs := make([]*User, 0, pageSize) orgs := make([]*User, 0, pageSize)
return orgs, db.Where("type = ?", UserTypeOrganization). return orgs, x.Limit(pageSize, (page-1)*pageSize).Where("type=1").Asc("id").Find(&orgs)
Offset((page - 1) * pageSize).Limit(pageSize).
Order("id ASC").Find(&orgs).Error
} }
// deleteBeans deletes all given beans, beans should contain delete conditions. // deleteBeans deletes all given beans, beans should contain delete conditions.
func deleteBeans(tx *gorm.DB, beans ...any) (err error) { func deleteBeans(e Engine, beans ...any) (err error) {
for i := range beans { for i := range beans {
if err = tx.Delete(beans[i]).Error; err != nil { if _, err = e.Delete(beans[i]); err != nil {
return err return err
} }
} }
@@ -212,13 +216,20 @@ func DeleteOrganization(org *User) error {
return err return err
} }
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
return deleteBeans(tx, defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
if err = deleteBeans(sess,
&Team{OrgID: org.ID}, &Team{OrgID: org.ID},
&OrgUser{OrgID: org.ID}, &OrgUser{OrgID: org.ID},
&TeamUser{OrgID: org.ID}, &TeamUser{OrgID: org.ID},
) ); err != nil {
}) return errors.Newf("deleteBeans: %v", err)
}
return sess.Commit()
} }
// ________ ____ ___ // ________ ____ ___
@@ -240,94 +251,84 @@ type OrgUser struct {
// IsOrganizationOwner returns true if given user is in the owner team. // IsOrganizationOwner returns true if given user is in the owner team.
func IsOrganizationOwner(orgID, userID int64) bool { func IsOrganizationOwner(orgID, userID int64) bool {
var count int64 has, _ := x.Where("is_owner = ?", true).And("uid = ?", userID).And("org_id = ?", orgID).Get(new(OrgUser))
db.Model(new(OrgUser)).Where("is_owner = ? AND uid = ? AND org_id = ?", true, userID, orgID).Count(&count) return has
return count > 0
} }
// IsOrganizationMember returns true if given user is member of organization. // IsOrganizationMember returns true if given user is member of organization.
func IsOrganizationMember(orgID, uid int64) bool { func IsOrganizationMember(orgID, uid int64) bool {
var count int64 has, _ := x.Where("uid=?", uid).And("org_id=?", orgID).Get(new(OrgUser))
db.Model(new(OrgUser)).Where("uid = ? AND org_id = ?", uid, orgID).Count(&count) return has
return count > 0
} }
// IsPublicMembership returns true if given user public his/her membership. // IsPublicMembership returns true if given user public his/her membership.
func IsPublicMembership(orgID, uid int64) bool { func IsPublicMembership(orgID, uid int64) bool {
var count int64 has, _ := x.Where("uid=?", uid).And("org_id=?", orgID).And("is_public=?", true).Get(new(OrgUser))
db.Model(new(OrgUser)).Where("uid = ? AND org_id = ? AND is_public = ?", uid, orgID, true).Count(&count) return has
return count > 0
} }
func getOrgsByUserID(tx *gorm.DB, userID int64, showAll bool) ([]*User, error) { func getOrgsByUserID(sess *xorm.Session, userID int64, showAll bool) ([]*User, error) {
orgs := make([]*User, 0, 10) orgs := make([]*User, 0, 10)
query := tx.Table("`user`").
Joins("INNER JOIN `org_user` ON `org_user`.org_id = `user`.id").
Where("`org_user`.uid = ?", userID)
if !showAll { if !showAll {
query = query.Where("`org_user`.is_public = ?", true) sess.And("`org_user`.is_public=?", true)
} }
return orgs, query.Find(&orgs).Error return orgs, sess.And("`org_user`.uid=?", userID).
Join("INNER", "`org_user`", "`org_user`.org_id=`user`.id").Find(&orgs)
} }
// GetOrgsByUserID returns a list of organizations that the given user ID // GetOrgsByUserID returns a list of organizations that the given user ID
// has joined. // has joined.
func GetOrgsByUserID(userID int64, showAll bool) ([]*User, error) { func GetOrgsByUserID(userID int64, showAll bool) ([]*User, error) {
return getOrgsByUserID(db, userID, showAll) return getOrgsByUserID(x.NewSession(), userID, showAll)
} }
func getOwnedOrgsByUserID(tx *gorm.DB, userID int64) ([]*User, error) { func getOwnedOrgsByUserID(sess *xorm.Session, userID int64) ([]*User, error) {
orgs := make([]*User, 0, 10) orgs := make([]*User, 0, 10)
return orgs, tx.Table("`user`"). return orgs, sess.Where("`org_user`.uid=?", userID).And("`org_user`.is_owner=?", true).
Joins("INNER JOIN `org_user` ON `org_user`.org_id = `user`.id"). Join("INNER", "`org_user`", "`org_user`.org_id=`user`.id").Find(&orgs)
Where("`org_user`.uid = ? AND `org_user`.is_owner = ?", userID, true).
Find(&orgs).Error
} }
// GetOwnedOrgsByUserID returns a list of organizations are owned by given user ID. // GetOwnedOrgsByUserID returns a list of organizations are owned by given user ID.
func GetOwnedOrgsByUserID(userID int64) ([]*User, error) { func GetOwnedOrgsByUserID(userID int64) ([]*User, error) {
return getOwnedOrgsByUserID(db, userID) sess := x.NewSession()
return getOwnedOrgsByUserID(sess, userID)
} }
// GetOwnedOrganizationsByUserIDDesc returns a list of organizations are owned by // GetOwnedOrganizationsByUserIDDesc returns a list of organizations are owned by
// given user ID, ordered descending by the given condition. // given user ID, ordered descending by the given condition.
func GetOwnedOrgsByUserIDDesc(userID int64, desc string) ([]*User, error) { func GetOwnedOrgsByUserIDDesc(userID int64, desc string) ([]*User, error) {
orgs := make([]*User, 0, 10) sess := x.NewSession()
return orgs, db.Table("`user`"). return getOwnedOrgsByUserID(sess.Desc(desc), userID)
Joins("INNER JOIN `org_user` ON `org_user`.org_id = `user`.id").
Where("`org_user`.uid = ? AND `org_user`.is_owner = ?", userID, true).
Order(desc + " DESC").
Find(&orgs).Error
} }
func getOrgUsersByOrgID(tx *gorm.DB, orgID int64, limit int) ([]*OrgUser, error) { func getOrgUsersByOrgID(e Engine, orgID int64, limit int) ([]*OrgUser, error) {
orgUsers := make([]*OrgUser, 0, 10) orgUsers := make([]*OrgUser, 0, 10)
query := tx.Where("org_id = ?", orgID) sess := e.Where("org_id=?", orgID)
if limit > 0 { if limit > 0 {
query = query.Limit(limit) sess = sess.Limit(limit)
} }
return orgUsers, query.Find(&orgUsers).Error return orgUsers, sess.Find(&orgUsers)
} }
// GetOrgUsersByOrgID returns all organization-user relations by organization ID. // GetOrgUsersByOrgID returns all organization-user relations by organization ID.
func GetOrgUsersByOrgID(orgID int64, limit int) ([]*OrgUser, error) { func GetOrgUsersByOrgID(orgID int64, limit int) ([]*OrgUser, error) {
return getOrgUsersByOrgID(db, orgID, limit) return getOrgUsersByOrgID(x, orgID, limit)
} }
// ChangeOrgUserStatus changes public or private membership status. // ChangeOrgUserStatus changes public or private membership status.
func ChangeOrgUserStatus(orgID, uid int64, public bool) error { func ChangeOrgUserStatus(orgID, uid int64, public bool) error {
ou := new(OrgUser) ou := new(OrgUser)
err := db.Where("uid = ? AND org_id = ?", uid, orgID).First(ou).Error has, err := x.Where("uid=?", uid).And("org_id=?", orgID).Get(ou)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
return err return err
} else if !has {
return nil
} }
ou.IsPublic = public ou.IsPublic = public
return db.Model(ou).Where("id = ?", ou.ID).Updates(ou).Error _, err = x.Id(ou.ID).AllCols().Update(ou)
return err
} }
// AddOrgUser adds new user to given organization. // AddOrgUser adds new user to given organization.
@@ -336,33 +337,35 @@ func AddOrgUser(orgID, uid int64) error {
return nil return nil
} }
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
ou := &OrgUser{ ou := &OrgUser{
UID: uid, UID: uid,
OrgID: orgID, OrgID: orgID,
} }
if err := tx.Create(ou).Error; err != nil { if _, err := sess.Insert(ou); err != nil {
return err return err
} } else if _, err = sess.Exec("UPDATE `user` SET num_members = num_members + 1 WHERE id = ?", orgID); err != nil {
if err := tx.Exec("UPDATE `user` SET num_members = num_members + 1 WHERE id = ?", orgID).Error; err != nil {
return err return err
} }
return nil return sess.Commit()
})
} }
// RemoveOrgUser removes user from given organization. // RemoveOrgUser removes user from given organization.
func RemoveOrgUser(orgID, userID int64) error { func RemoveOrgUser(orgID, userID int64) error {
ou := new(OrgUser) ou := new(OrgUser)
err := db.Where("uid = ? AND org_id = ?", userID, orgID).First(ou).Error has, err := x.Where("uid=?", userID).And("org_id=?", orgID).Get(ou)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
return errors.Newf("get org-user: %v", err) return errors.Newf("get org-user: %v", err)
} else if !has {
return nil
} }
user, err := Handle.Users().GetByID(context.TODO(), userID) user, err := Handle.Users().GetByID(context.TODO(), userID)
@@ -391,11 +394,15 @@ func RemoveOrgUser(orgID, userID int64) error {
} }
} }
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
if err := tx.Where("id = ?", ou.ID).Delete(ou).Error; err != nil { defer sess.Close()
if err := sess.Begin(); err != nil {
return err return err
} }
if err := tx.Exec("UPDATE `user` SET num_members = num_members - 1 WHERE id = ?", orgID).Error; err != nil {
if _, err := sess.ID(ou.ID).Delete(ou); err != nil {
return err
} else if _, err = sess.Exec("UPDATE `user` SET num_members=num_members-1 WHERE id=?", orgID); err != nil {
return err return err
} }
@@ -403,57 +410,55 @@ func RemoveOrgUser(orgID, userID int64) error {
repoIDs := make([]int64, 0, len(repos)) repoIDs := make([]int64, 0, len(repos))
for i := range repos { for i := range repos {
repoIDs = append(repoIDs, repos[i].ID) repoIDs = append(repoIDs, repos[i].ID)
if err = watchRepo(tx, user.ID, repos[i].ID, false); err != nil { if err = watchRepo(sess, user.ID, repos[i].ID, false); err != nil {
return err return err
} }
} }
if len(repoIDs) > 0 { if len(repoIDs) > 0 {
if err := tx.Where("user_id = ? AND repo_id IN ?", user.ID, repoIDs).Delete(new(Access)).Error; err != nil { if _, err = sess.Where("user_id = ?", user.ID).In("repo_id", repoIDs).Delete(new(Access)); err != nil {
return err return err
} }
} }
// Delete member in his/her teams. // Delete member in his/her teams.
teams, err := getUserTeams(tx, org.ID, user.ID) teams, err := getUserTeams(sess, org.ID, user.ID)
if err != nil { if err != nil {
return err return err
} }
for _, t := range teams { for _, t := range teams {
if err = removeTeamMember(tx, org.ID, t.ID, user.ID); err != nil { if err = removeTeamMember(sess, org.ID, t.ID, user.ID); err != nil {
return err return err
} }
} }
return nil return sess.Commit()
})
} }
func removeOrgRepo(tx *gorm.DB, orgID, repoID int64) error { func removeOrgRepo(e Engine, orgID, repoID int64) error {
return tx.Where("org_id = ? AND repo_id = ?", orgID, repoID).Delete(&TeamRepo{}).Error _, err := e.Delete(&TeamRepo{
OrgID: orgID,
RepoID: repoID,
})
return err
} }
// RemoveOrgRepo removes all team-repository relations of given organization. // RemoveOrgRepo removes all team-repository relations of given organization.
func RemoveOrgRepo(orgID, repoID int64) error { func RemoveOrgRepo(orgID, repoID int64) error {
return removeOrgRepo(db, orgID, repoID) return removeOrgRepo(x, orgID, repoID)
} }
func (org *User) getUserTeams(tx *gorm.DB, userID int64, cols ...string) ([]*Team, error) { func (org *User) getUserTeams(e Engine, userID int64, cols ...string) ([]*Team, error) {
teams := make([]*Team, 0, org.NumTeams) teams := make([]*Team, 0, org.NumTeams)
query := tx.Table("team"). return teams, e.Where("team_user.org_id = ?", org.ID).
Joins("INNER JOIN team_user ON team_user.team_id = team.id"). And("team_user.uid = ?", userID).
Where("team_user.org_id = ? AND team_user.uid = ?", org.ID, userID) Join("INNER", "team_user", "team_user.team_id = team.id").
Cols(cols...).Find(&teams)
if len(cols) > 0 {
query = query.Select(cols)
}
return teams, query.Find(&teams).Error
} }
// GetUserTeamIDs returns of all team IDs of the organization that user is member of. // GetUserTeamIDs returns of all team IDs of the organization that user is member of.
func (org *User) GetUserTeamIDs(userID int64) ([]int64, error) { func (org *User) GetUserTeamIDs(userID int64) ([]int64, error) {
teams, err := org.getUserTeams(db, userID, "team.id") teams, err := org.getUserTeams(x, userID, "team.id")
if err != nil { if err != nil {
return nil, errors.Newf("getUserTeams [%d]: %v", userID, err) return nil, errors.Newf("getUserTeams [%d]: %v", userID, err)
} }
@@ -468,7 +473,7 @@ func (org *User) GetUserTeamIDs(userID int64) ([]int64, error) {
// GetTeams returns all teams that belong to organization, // GetTeams returns all teams that belong to organization,
// and that the user has joined. // and that the user has joined.
func (org *User) GetUserTeams(userID int64) ([]*Team, error) { func (org *User) GetUserTeams(userID int64) ([]*Team, error) {
return org.getUserTeams(db, userID) return org.getUserTeams(x, userID)
} }
// GetUserRepositories returns a range of repositories in organization which the user has access to, // GetUserRepositories returns a range of repositories in organization which the user has access to,
@@ -484,7 +489,7 @@ func (org *User) GetUserRepositories(userID int64, page, pageSize int) ([]*Repos
} }
var teamRepoIDs []int64 var teamRepoIDs []int64
if err = db.Table("team_repo").Where("team_id IN ?", teamIDs).Distinct("repo_id").Find(&teamRepoIDs).Error; err != nil { if err = x.Table("team_repo").In("team_id", teamIDs).Distinct("repo_id").Find(&teamRepoIDs); err != nil {
return nil, 0, errors.Newf("get team repository IDs: %v", err) return nil, 0, errors.Newf("get team repository IDs: %v", err)
} }
if len(teamRepoIDs) == 0 { if len(teamRepoIDs) == 0 {
@@ -496,18 +501,22 @@ func (org *User) GetUserRepositories(userID int64, page, pageSize int) ([]*Repos
page = 1 page = 1
} }
repos := make([]*Repository, 0, pageSize) repos := make([]*Repository, 0, pageSize)
if err = db.Where("owner_id = ?", org.ID). if err = x.Where("owner_id = ?", org.ID).
Where(db.Where("is_private = ? AND is_unlisted = ?", false, false).Or("id IN ?", teamRepoIDs)). And(builder.Or(
Order("updated_unix DESC"). builder.And(builder.Expr("is_private = ?", false), builder.Expr("is_unlisted = ?", false)),
Limit(pageSize).Offset((page - 1) * pageSize). builder.In("id", teamRepoIDs))).
Find(&repos).Error; err != nil { Desc("updated_unix").
Limit(pageSize, (page-1)*pageSize).
Find(&repos); err != nil {
return nil, 0, errors.Newf("get user repositories: %v", err) return nil, 0, errors.Newf("get user repositories: %v", err)
} }
var repoCount int64 repoCount, err := x.Where("owner_id = ?", org.ID).
if err = db.Model(&Repository{}).Where("owner_id = ?", org.ID). And(builder.Or(
Where(db.Where("is_private = ?", false).Or("id IN ?", teamRepoIDs)). builder.Expr("is_private = ?", false),
Count(&repoCount).Error; err != nil { builder.In("id", teamRepoIDs))).
Count(new(Repository))
if err != nil {
return nil, 0, errors.Newf("count user repositories: %v", err) return nil, 0, errors.Newf("count user repositories: %v", err)
} }
@@ -525,7 +534,7 @@ func (org *User) GetUserMirrorRepositories(userID int64) ([]*Repository, error)
} }
var teamRepoIDs []int64 var teamRepoIDs []int64
err = db.Table("team_repo").Where("team_id IN ?", teamIDs).Distinct("repo_id").Find(&teamRepoIDs).Error err = x.Table("team_repo").In("team_id", teamIDs).Distinct("repo_id").Find(&teamRepoIDs)
if err != nil { if err != nil {
return nil, errors.Newf("get team repository ids: %v", err) return nil, errors.Newf("get team repository ids: %v", err)
} }
@@ -535,12 +544,12 @@ func (org *User) GetUserMirrorRepositories(userID int64) ([]*Repository, error)
} }
repos := make([]*Repository, 0, 10) repos := make([]*Repository, 0, 10)
if err = db.Where("owner_id = ?", org.ID). if err = x.Where("owner_id = ?", org.ID).
Where("is_private = ?", false). And("is_private = ?", false).
Or("id IN ?", teamRepoIDs). Or(builder.In("id", teamRepoIDs)).
Where("is_mirror = ?", true). // Don't move up because it's an independent condition And("is_mirror = ?", true). // Don't move up because it's an independent condition
Order("updated_unix DESC"). Desc("updated_unix").
Find(&repos).Error; err != nil { Find(&repos); err != nil {
return nil, errors.Newf("get user repositories: %v", err) return nil, errors.Newf("get user repositories: %v", err)
} }
return repos, nil return repos, nil

View File

@@ -6,7 +6,7 @@ import (
"strings" "strings"
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
"gorm.io/gorm" "xorm.io/xorm"
"gogs.io/gogs/internal/errutil" "gogs.io/gogs/internal/errutil"
) )
@@ -16,7 +16,7 @@ const ownerTeamName = "Owners"
// Team represents a organization team. // Team represents a organization team.
type Team struct { type Team struct {
ID int64 ID int64
OrgID int64 `gorm:"index"` OrgID int64 `xorm:"INDEX"`
LowerName string LowerName string
Name string Name string
Description string Description string
@@ -27,12 +27,14 @@ type Team struct {
NumMembers int NumMembers int
} }
func (t *Team) AfterFind(tx *gorm.DB) error { func (t *Team) AfterSet(colName string, _ xorm.Cell) {
switch colName {
case "num_repos":
// LEGACY [1.0]: this is backward compatibility bug fix for https://gogs.io/gogs/issues/3671 // LEGACY [1.0]: this is backward compatibility bug fix for https://gogs.io/gogs/issues/3671
if t.NumRepos < 0 { if t.NumRepos < 0 {
t.NumRepos = 0 t.NumRepos = 0
} }
return nil }
} }
// IsOwnerTeam returns true if team is owner team. // IsOwnerTeam returns true if team is owner team.
@@ -50,15 +52,15 @@ func (t *Team) IsMember(userID int64) bool {
return IsTeamMember(t.OrgID, t.ID, userID) return IsTeamMember(t.OrgID, t.ID, userID)
} }
func (t *Team) getRepositories(tx *gorm.DB) (err error) { func (t *Team) getRepositories(e Engine) (err error) {
teamRepos := make([]*TeamRepo, 0, t.NumRepos) teamRepos := make([]*TeamRepo, 0, t.NumRepos)
if err = tx.Where("team_id = ?", t.ID).Find(&teamRepos).Error; err != nil { if err = x.Where("team_id=?", t.ID).Find(&teamRepos); err != nil {
return errors.Newf("get team-repos: %v", err) return errors.Newf("get team-repos: %v", err)
} }
t.Repos = make([]*Repository, 0, len(teamRepos)) t.Repos = make([]*Repository, 0, len(teamRepos))
for i := range teamRepos { for i := range teamRepos {
repo, err := getRepositoryByID(tx, teamRepos[i].RepoID) repo, err := getRepositoryByID(e, teamRepos[i].RepoID)
if err != nil { if err != nil {
return errors.Newf("getRepositoryById(%d): %v", teamRepos[i].RepoID, err) return errors.Newf("getRepositoryById(%d): %v", teamRepos[i].RepoID, err)
} }
@@ -69,17 +71,17 @@ func (t *Team) getRepositories(tx *gorm.DB) (err error) {
// GetRepositories returns all repositories in team of organization. // GetRepositories returns all repositories in team of organization.
func (t *Team) GetRepositories() error { func (t *Team) GetRepositories() error {
return t.getRepositories(db) return t.getRepositories(x)
} }
func (t *Team) getMembers(tx *gorm.DB) (err error) { func (t *Team) getMembers(e Engine) (err error) {
t.Members, err = getTeamMembers(tx, t.ID) t.Members, err = getTeamMembers(e, t.ID)
return err return err
} }
// GetMembers returns all members in team of organization. // GetMembers returns all members in team of organization.
func (t *Team) GetMembers() (err error) { func (t *Team) GetMembers() (err error) {
return t.getMembers(db) return t.getMembers(x)
} }
// AddMember adds new membership of the team to the organization, // AddMember adds new membership of the team to the organization,
@@ -93,34 +95,34 @@ func (t *Team) RemoveMember(uid int64) error {
return RemoveTeamMember(t.OrgID, t.ID, uid) return RemoveTeamMember(t.OrgID, t.ID, uid)
} }
func (t *Team) hasRepository(tx *gorm.DB, repoID int64) bool { func (t *Team) hasRepository(e Engine, repoID int64) bool {
return hasTeamRepo(tx, t.OrgID, t.ID, repoID) return hasTeamRepo(e, t.OrgID, t.ID, repoID)
} }
// HasRepository returns true if given repository belong to team. // HasRepository returns true if given repository belong to team.
func (t *Team) HasRepository(repoID int64) bool { func (t *Team) HasRepository(repoID int64) bool {
return t.hasRepository(db, repoID) return t.hasRepository(x, repoID)
} }
func (t *Team) addRepository(tx *gorm.DB, repo *Repository) (err error) { func (t *Team) addRepository(e Engine, repo *Repository) (err error) {
if err = addTeamRepo(tx, t.OrgID, t.ID, repo.ID); err != nil { if err = addTeamRepo(e, t.OrgID, t.ID, repo.ID); err != nil {
return err return err
} }
t.NumRepos++ t.NumRepos++
if err = tx.Model(&Team{}).Where("id = ?", t.ID).Updates(t).Error; err != nil { if _, err = e.ID(t.ID).AllCols().Update(t); err != nil {
return errors.Newf("update team: %v", err) return errors.Newf("update team: %v", err)
} }
if err = repo.recalculateTeamAccesses(tx, 0); err != nil { if err = repo.recalculateTeamAccesses(e, 0); err != nil {
return errors.Newf("recalculateAccesses: %v", err) return errors.Newf("recalculateAccesses: %v", err)
} }
if err = t.getMembers(tx); err != nil { if err = t.getMembers(e); err != nil {
return errors.Newf("getMembers: %v", err) return errors.Newf("getMembers: %v", err)
} }
for _, u := range t.Members { for _, u := range t.Members {
if err = watchRepo(tx, u.ID, repo.ID, true); err != nil { if err = watchRepo(e, u.ID, repo.ID, true); err != nil {
return errors.Newf("watchRepo: %v", err) return errors.Newf("watchRepo: %v", err)
} }
} }
@@ -135,34 +137,42 @@ func (t *Team) AddRepository(repo *Repository) (err error) {
return nil return nil
} }
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
return t.addRepository(tx, repo) defer sess.Close()
}) if err = sess.Begin(); err != nil {
return err
} }
func (t *Team) removeRepository(tx *gorm.DB, repo *Repository, recalculate bool) (err error) { if err = t.addRepository(sess, repo); err != nil {
if err = removeTeamRepo(tx, t.ID, repo.ID); err != nil { return err
}
return sess.Commit()
}
func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (err error) {
if err = removeTeamRepo(e, t.ID, repo.ID); err != nil {
return err return err
} }
t.NumRepos-- t.NumRepos--
if err = tx.Model(&Team{}).Where("id = ?", t.ID).Updates(t).Error; err != nil { if _, err = e.ID(t.ID).AllCols().Update(t); err != nil {
return err return err
} }
// Don't need to recalculate when delete a repository from organization. // Don't need to recalculate when delete a repository from organization.
if recalculate { if recalculate {
if err = repo.recalculateTeamAccesses(tx, t.ID); err != nil { if err = repo.recalculateTeamAccesses(e, t.ID); err != nil {
return err return err
} }
} }
if err = t.getMembers(tx); err != nil { if err = t.getMembers(e); err != nil {
return errors.Newf("get team members: %v", err) return errors.Newf("get team members: %v", err)
} }
// TODO: Delete me when this method is migrated to use GORM. // TODO: Delete me when this method is migrated to use GORM.
userAccessMode := func(tx *gorm.DB, userID int64, repo *Repository) (AccessMode, error) { userAccessMode := func(e Engine, userID int64, repo *Repository) (AccessMode, error) {
mode := AccessModeNone mode := AccessModeNone
// Everyone has read access to public repository // Everyone has read access to public repository
if !repo.IsPrivate { if !repo.IsPrivate {
@@ -181,29 +191,26 @@ func (t *Team) removeRepository(tx *gorm.DB, repo *Repository, recalculate bool)
UserID: userID, UserID: userID,
RepoID: repo.ID, RepoID: repo.ID,
} }
err := tx.Where("user_id = ? AND repo_id = ?", userID, repo.ID).First(access).Error if has, err := e.Get(access); !has || err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return mode, nil
} else if err != nil {
return mode, err return mode, err
} }
return access.Mode, nil return access.Mode, nil
} }
hasAccess := func(tx *gorm.DB, userID int64, repo *Repository, testMode AccessMode) (bool, error) { hasAccess := func(e Engine, userID int64, repo *Repository, testMode AccessMode) (bool, error) {
mode, err := userAccessMode(tx, userID, repo) mode, err := userAccessMode(e, userID, repo)
return mode >= testMode, err return mode >= testMode, err
} }
for _, member := range t.Members { for _, member := range t.Members {
has, err := hasAccess(tx, member.ID, repo, AccessModeRead) has, err := hasAccess(e, member.ID, repo, AccessModeRead)
if err != nil { if err != nil {
return err return err
} else if has { } else if has {
continue continue
} }
if err = watchRepo(tx, member.ID, repo.ID, false); err != nil { if err = watchRepo(e, member.ID, repo.ID, false); err != nil {
return err return err
} }
} }
@@ -222,9 +229,17 @@ func (t *Team) RemoveRepository(repoID int64) error {
return err return err
} }
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
return t.removeRepository(tx, repo, true) defer sess.Close()
}) if err = sess.Begin(); err != nil {
return err
}
if err = t.removeRepository(sess, repo, true); err != nil {
return err
}
return sess.Commit()
} }
var reservedTeamNames = map[string]struct{}{ var reservedTeamNames = map[string]struct{}{
@@ -249,30 +264,37 @@ func NewTeam(t *Team) error {
return err return err
} }
err := db.Where("id = ?", t.OrgID).First(new(User)).Error has, err := x.Id(t.OrgID).Get(new(User))
if errors.Is(err, gorm.ErrRecordNotFound) { if err != nil {
return ErrOrgNotExist
} else if err != nil {
return err return err
} else if !has {
return ErrOrgNotExist
} }
t.LowerName = strings.ToLower(t.Name) t.LowerName = strings.ToLower(t.Name)
existingTeam := Team{} existingTeam := Team{}
err = db.Where("org_id = ? AND lower_name = ?", t.OrgID, t.LowerName).First(&existingTeam).Error has, err = x.Where("org_id=?", t.OrgID).And("lower_name=?", t.LowerName).Get(&existingTeam)
if err == nil { if err != nil {
return err
} else if has {
return ErrTeamAlreadyExist{existingTeam.ID, t.OrgID, t.LowerName} return ErrTeamAlreadyExist{existingTeam.ID, t.OrgID, t.LowerName}
} else if !errors.Is(err, gorm.ErrRecordNotFound) { }
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err return err
} }
return db.Transaction(func(tx *gorm.DB) error { if _, err = sess.Insert(t); err != nil {
if err := tx.Create(t).Error; err != nil {
return err return err
} }
// Update organization number of teams. // Update organization number of teams.
return tx.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID).Error if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
}) return err
}
return sess.Commit()
} }
var _ errutil.NotFound = (*ErrTeamNotExist)(nil) var _ errutil.NotFound = (*ErrTeamNotExist)(nil)
@@ -294,46 +316,49 @@ func (ErrTeamNotExist) NotFound() bool {
return true return true
} }
func getTeamOfOrgByName(tx *gorm.DB, orgID int64, name string) (*Team, error) { func getTeamOfOrgByName(e Engine, orgID int64, name string) (*Team, error) {
t := new(Team) t := &Team{
err := tx.Where("org_id = ? AND lower_name = ?", orgID, strings.ToLower(name)).First(t).Error OrgID: orgID,
if errors.Is(err, gorm.ErrRecordNotFound) { LowerName: strings.ToLower(name),
return nil, ErrTeamNotExist{args: map[string]any{"orgID": orgID, "name": name}} }
} else if err != nil { has, err := e.Get(t)
if err != nil {
return nil, err return nil, err
} else if !has {
return nil, ErrTeamNotExist{args: map[string]any{"orgID": orgID, "name": name}}
} }
return t, nil return t, nil
} }
// GetTeamOfOrgByName returns team by given team name and organization. // GetTeamOfOrgByName returns team by given team name and organization.
func GetTeamOfOrgByName(orgID int64, name string) (*Team, error) { func GetTeamOfOrgByName(orgID int64, name string) (*Team, error) {
return getTeamOfOrgByName(db, orgID, name) return getTeamOfOrgByName(x, orgID, name)
} }
func getTeamByID(tx *gorm.DB, teamID int64) (*Team, error) { func getTeamByID(e Engine, teamID int64) (*Team, error) {
t := new(Team) t := new(Team)
err := tx.Where("id = ?", teamID).First(t).Error has, err := e.ID(teamID).Get(t)
if errors.Is(err, gorm.ErrRecordNotFound) { if err != nil {
return nil, ErrTeamNotExist{args: map[string]any{"teamID": teamID}}
} else if err != nil {
return nil, err return nil, err
} else if !has {
return nil, ErrTeamNotExist{args: map[string]any{"teamID": teamID}}
} }
return t, nil return t, nil
} }
// GetTeamByID returns team by given ID. // GetTeamByID returns team by given ID.
func GetTeamByID(teamID int64) (*Team, error) { func GetTeamByID(teamID int64) (*Team, error) {
return getTeamByID(db, teamID) return getTeamByID(x, teamID)
} }
func getTeamsByOrgID(tx *gorm.DB, orgID int64) ([]*Team, error) { func getTeamsByOrgID(e Engine, orgID int64) ([]*Team, error) {
teams := make([]*Team, 0, 3) teams := make([]*Team, 0, 3)
return teams, tx.Where("org_id = ?", orgID).Find(&teams).Error return teams, e.Where("org_id = ?", orgID).Find(&teams)
} }
// GetTeamsByOrgID returns all teams belong to given organization. // GetTeamsByOrgID returns all teams belong to given organization.
func GetTeamsByOrgID(orgID int64) ([]*Team, error) { func GetTeamsByOrgID(orgID int64) ([]*Team, error) {
return getTeamsByOrgID(db, orgID) return getTeamsByOrgID(x, orgID)
} }
// UpdateTeam updates information of team. // UpdateTeam updates information of team.
@@ -346,35 +371,39 @@ func UpdateTeam(t *Team, authChanged bool) (err error) {
t.Description = t.Description[:255] t.Description = t.Description[:255]
} }
t.LowerName = strings.ToLower(t.Name) sess := x.NewSession()
existingTeam := new(Team) defer sess.Close()
err = db.Where("org_id = ? AND lower_name = ? AND id != ?", t.OrgID, t.LowerName, t.ID).First(existingTeam).Error if err = sess.Begin(); err != nil {
if err == nil {
return ErrTeamAlreadyExist{existingTeam.ID, t.OrgID, t.LowerName}
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
return err return err
} }
return db.Transaction(func(tx *gorm.DB) error { t.LowerName = strings.ToLower(t.Name)
if err := tx.Model(&Team{}).Where("id = ?", t.ID).Updates(t).Error; err != nil { existingTeam := new(Team)
has, err := x.Where("org_id=?", t.OrgID).And("lower_name=?", t.LowerName).And("id!=?", t.ID).Get(existingTeam)
if err != nil {
return err
} else if has {
return ErrTeamAlreadyExist{existingTeam.ID, t.OrgID, t.LowerName}
}
if _, err = sess.ID(t.ID).AllCols().Update(t); err != nil {
return errors.Newf("update: %v", err) return errors.Newf("update: %v", err)
} }
// Update access for team members if needed. // Update access for team members if needed.
if authChanged { if authChanged {
if err := t.getRepositories(tx); err != nil { if err = t.getRepositories(sess); err != nil {
return errors.Newf("getRepositories:%v", err) return errors.Newf("getRepositories:%v", err)
} }
for _, repo := range t.Repos { for _, repo := range t.Repos {
if err := repo.recalculateTeamAccesses(tx, 0); err != nil { if err = repo.recalculateTeamAccesses(sess, 0); err != nil {
return errors.Newf("recalculateTeamAccesses: %v", err) return errors.Newf("recalculateTeamAccesses: %v", err)
} }
} }
} }
return nil return sess.Commit()
})
} }
// DeleteTeam deletes given team. // DeleteTeam deletes given team.
@@ -390,26 +419,34 @@ func DeleteTeam(t *Team) error {
return err return err
} }
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
// Delete all accesses. // Delete all accesses.
for _, repo := range t.Repos { for _, repo := range t.Repos {
if err := repo.recalculateTeamAccesses(tx, t.ID); err != nil { if err = repo.recalculateTeamAccesses(sess, t.ID); err != nil {
return err return err
} }
} }
// Delete team-user. // Delete team-user.
if err := tx.Where("org_id = ? AND team_id = ?", org.ID, t.ID).Delete(new(TeamUser)).Error; err != nil { if _, err = sess.Where("org_id=?", org.ID).Where("team_id=?", t.ID).Delete(new(TeamUser)); err != nil {
return err return err
} }
// Delete team. // Delete team.
if err := tx.Where("id = ?", t.ID).Delete(new(Team)).Error; err != nil { if _, err = sess.ID(t.ID).Delete(new(Team)); err != nil {
return err return err
} }
// Update organization number of teams. // Update organization number of teams.
return tx.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id = ?", t.OrgID).Error if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil {
}) return err
}
return sess.Commit()
} }
// ___________ ____ ___ // ___________ ____ ___
@@ -422,30 +459,31 @@ func DeleteTeam(t *Team) error {
// TeamUser represents an team-user relation. // TeamUser represents an team-user relation.
type TeamUser struct { type TeamUser struct {
ID int64 ID int64
OrgID int64 `gorm:"index"` OrgID int64 `xorm:"INDEX"`
TeamID int64 `gorm:"uniqueIndex:team_user_team_id_uid"` TeamID int64 `xorm:"UNIQUE(s)"`
UID int64 `gorm:"uniqueIndex:team_user_team_id_uid"` UID int64 `xorm:"UNIQUE(s)"`
} }
func isTeamMember(tx *gorm.DB, orgID, teamID, uid int64) bool { func isTeamMember(e Engine, orgID, teamID, uid int64) bool {
err := tx.Where("org_id = ? AND team_id = ? AND uid = ?", orgID, teamID, uid).First(new(TeamUser)).Error has, _ := e.Where("org_id=?", orgID).And("team_id=?", teamID).And("uid=?", uid).Get(new(TeamUser))
return err == nil return has
} }
// IsTeamMember returns true if given user is a member of team. // IsTeamMember returns true if given user is a member of team.
func IsTeamMember(orgID, teamID, uid int64) bool { func IsTeamMember(orgID, teamID, uid int64) bool {
return isTeamMember(db, orgID, teamID, uid) return isTeamMember(x, orgID, teamID, uid)
} }
func getTeamMembers(tx *gorm.DB, teamID int64) (_ []*User, err error) { func getTeamMembers(e Engine, teamID int64) (_ []*User, err error) {
teamUsers := make([]*TeamUser, 0, 10) teamUsers := make([]*TeamUser, 0, 10)
if err = tx.Select("id, org_id, team_id, uid").Where("team_id = ?", teamID).Find(&teamUsers).Error; err != nil { if err = e.Sql("SELECT `id`, `org_id`, `team_id`, `uid` FROM `team_user` WHERE team_id = ?", teamID).
Find(&teamUsers); err != nil {
return nil, errors.Newf("get team-users: %v", err) return nil, errors.Newf("get team-users: %v", err)
} }
members := make([]*User, 0, len(teamUsers)) members := make([]*User, 0, len(teamUsers))
for i := range teamUsers { for i := range teamUsers {
member := new(User) member := new(User)
if err = tx.Where("id = ?", teamUsers[i].UID).First(member).Error; err != nil { if _, err = e.ID(teamUsers[i].UID).Get(member); err != nil {
return nil, errors.Newf("get user '%d': %v", teamUsers[i].UID, err) return nil, errors.Newf("get user '%d': %v", teamUsers[i].UID, err)
} }
members = append(members, member) members = append(members, member)
@@ -455,12 +493,12 @@ func getTeamMembers(tx *gorm.DB, teamID int64) (_ []*User, err error) {
// GetTeamMembers returns all members in given team of organization. // GetTeamMembers returns all members in given team of organization.
func GetTeamMembers(teamID int64) ([]*User, error) { func GetTeamMembers(teamID int64) ([]*User, error) {
return getTeamMembers(db, teamID) return getTeamMembers(x, teamID)
} }
func getUserTeams(tx *gorm.DB, orgID, userID int64) ([]*Team, error) { func getUserTeams(e Engine, orgID, userID int64) ([]*Team, error) {
teamUsers := make([]*TeamUser, 0, 5) teamUsers := make([]*TeamUser, 0, 5)
if err := tx.Where("uid = ? AND org_id = ?", userID, orgID).Find(&teamUsers).Error; err != nil { if err := e.Where("uid = ?", userID).And("org_id = ?", orgID).Find(&teamUsers); err != nil {
return nil, err return nil, err
} }
@@ -471,12 +509,12 @@ func getUserTeams(tx *gorm.DB, orgID, userID int64) ([]*Team, error) {
teamIDs[len(teamUsers)] = -1 teamIDs[len(teamUsers)] = -1
teams := make([]*Team, 0, len(teamIDs)) teams := make([]*Team, 0, len(teamIDs))
return teams, tx.Where("org_id = ? AND id IN ?", orgID, teamIDs).Find(&teams).Error return teams, e.Where("org_id = ?", orgID).In("id", teamIDs).Find(&teams)
} }
// GetUserTeams returns all teams that user belongs to in given organization. // GetUserTeams returns all teams that user belongs to in given organization.
func GetUserTeams(orgID, userID int64) ([]*Team, error) { func GetUserTeams(orgID, userID int64) ([]*Team, error) {
return getUserTeams(db, orgID, userID) return getUserTeams(x, orgID, userID)
} }
// AddTeamMember adds new membership of given team to given organization, // AddTeamMember adds new membership of given team to given organization,
@@ -501,46 +539,53 @@ func AddTeamMember(orgID, teamID, userID int64) error {
return err return err
} }
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
tu := &TeamUser{ tu := &TeamUser{
UID: userID, UID: userID,
OrgID: orgID, OrgID: orgID,
TeamID: teamID, TeamID: teamID,
} }
if err := tx.Create(tu).Error; err != nil { if _, err = sess.Insert(tu); err != nil {
return err return err
} } else if _, err = sess.ID(t.ID).Update(t); err != nil {
if err := tx.Model(&Team{}).Where("id = ?", t.ID).Updates(t).Error; err != nil {
return err return err
} }
// Give access to team repositories. // Give access to team repositories.
for _, repo := range t.Repos { for _, repo := range t.Repos {
if err := repo.recalculateTeamAccesses(tx, 0); err != nil { if err = repo.recalculateTeamAccesses(sess, 0); err != nil {
return err return err
} }
} }
// We make sure it exists before. // We make sure it exists before.
ou := new(OrgUser) ou := new(OrgUser)
if err := tx.Where("uid = ? AND org_id = ?", userID, orgID).First(ou).Error; err != nil { if _, err = sess.Where("uid = ?", userID).And("org_id = ?", orgID).Get(ou); err != nil {
return err return err
} }
ou.NumTeams++ ou.NumTeams++
if t.IsOwnerTeam() { if t.IsOwnerTeam() {
ou.IsOwner = true ou.IsOwner = true
} }
return tx.Model(&OrgUser{}).Where("id = ?", ou.ID).Updates(ou).Error if _, err = sess.ID(ou.ID).AllCols().Update(ou); err != nil {
}) return err
} }
func removeTeamMember(tx *gorm.DB, orgID, teamID, uid int64) error { return sess.Commit()
if !isTeamMember(tx, orgID, teamID, uid) { }
func removeTeamMember(e Engine, orgID, teamID, uid int64) error {
if !isTeamMember(e, orgID, teamID, uid) {
return nil return nil
} }
// Get team and its repositories. // Get team and its repositories.
t, err := getTeamByID(tx, teamID) t, err := getTeamByID(e, teamID)
if err != nil { if err != nil {
return err return err
} }
@@ -552,12 +597,12 @@ func removeTeamMember(tx *gorm.DB, orgID, teamID, uid int64) error {
t.NumMembers-- t.NumMembers--
if err = t.getRepositories(tx); err != nil { if err = t.getRepositories(e); err != nil {
return err return err
} }
// Get organization. // Get organization.
org, err := getUserByID(tx, orgID) org, err := getUserByID(e, orgID)
if err != nil { if err != nil {
return err return err
} }
@@ -567,37 +612,46 @@ func removeTeamMember(tx *gorm.DB, orgID, teamID, uid int64) error {
OrgID: orgID, OrgID: orgID,
TeamID: teamID, TeamID: teamID,
} }
if err := tx.Where("uid = ? AND org_id = ? AND team_id = ?", uid, orgID, teamID).Delete(tu).Error; err != nil { if _, err := e.Delete(tu); err != nil {
return err return err
} } else if _, err = e.ID(t.ID).AllCols().Update(t); err != nil {
if err = tx.Model(&Team{}).Where("id = ?", t.ID).Updates(t).Error; err != nil {
return err return err
} }
// Delete access to team repositories. // Delete access to team repositories.
for _, repo := range t.Repos { for _, repo := range t.Repos {
if err = repo.recalculateTeamAccesses(tx, 0); err != nil { if err = repo.recalculateTeamAccesses(e, 0); err != nil {
return err return err
} }
} }
// This must exist. // This must exist.
ou := new(OrgUser) ou := new(OrgUser)
if err = tx.Where("uid = ? AND org_id = ?", uid, org.ID).First(ou).Error; err != nil { _, err = e.Where("uid = ?", uid).And("org_id = ?", org.ID).Get(ou)
if err != nil {
return err return err
} }
ou.NumTeams-- ou.NumTeams--
if t.IsOwnerTeam() { if t.IsOwnerTeam() {
ou.IsOwner = false ou.IsOwner = false
} }
return tx.Model(&OrgUser{}).Where("id = ?", ou.ID).Updates(ou).Error if _, err = e.ID(ou.ID).AllCols().Update(ou); err != nil {
return err
}
return nil
} }
// RemoveTeamMember removes member from given team of given organization. // RemoveTeamMember removes member from given team of given organization.
func RemoveTeamMember(orgID, teamID, uid int64) error { func RemoveTeamMember(orgID, teamID, uid int64) error {
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
return removeTeamMember(tx, orgID, teamID, uid) defer sess.Close()
}) if err := sess.Begin(); err != nil {
return err
}
if err := removeTeamMember(sess, orgID, teamID, uid); err != nil {
return err
}
return sess.Commit()
} }
// ___________ __________ // ___________ __________
@@ -610,49 +664,54 @@ func RemoveTeamMember(orgID, teamID, uid int64) error {
// TeamRepo represents an team-repository relation. // TeamRepo represents an team-repository relation.
type TeamRepo struct { type TeamRepo struct {
ID int64 ID int64
OrgID int64 `gorm:"index"` OrgID int64 `xorm:"INDEX"`
TeamID int64 `gorm:"uniqueIndex:team_repo_team_id_repo_id"` TeamID int64 `xorm:"UNIQUE(s)"`
RepoID int64 `gorm:"uniqueIndex:team_repo_team_id_repo_id"` RepoID int64 `xorm:"UNIQUE(s)"`
} }
func hasTeamRepo(tx *gorm.DB, orgID, teamID, repoID int64) bool { func hasTeamRepo(e Engine, orgID, teamID, repoID int64) bool {
err := tx.Where("org_id = ? AND team_id = ? AND repo_id = ?", orgID, teamID, repoID).First(new(TeamRepo)).Error has, _ := e.Where("org_id = ?", orgID).And("team_id = ?", teamID).And("repo_id = ?", repoID).Get(new(TeamRepo))
return err == nil return has
} }
// HasTeamRepo returns true if given team has access to the repository of the organization. // HasTeamRepo returns true if given team has access to the repository of the organization.
func HasTeamRepo(orgID, teamID, repoID int64) bool { func HasTeamRepo(orgID, teamID, repoID int64) bool {
return hasTeamRepo(db, orgID, teamID, repoID) return hasTeamRepo(x, orgID, teamID, repoID)
} }
func addTeamRepo(tx *gorm.DB, orgID, teamID, repoID int64) error { func addTeamRepo(e Engine, orgID, teamID, repoID int64) error {
return tx.Create(&TeamRepo{ _, err := e.InsertOne(&TeamRepo{
OrgID: orgID, OrgID: orgID,
TeamID: teamID, TeamID: teamID,
RepoID: repoID, RepoID: repoID,
}).Error })
return err
} }
// AddTeamRepo adds new repository relation to team. // AddTeamRepo adds new repository relation to team.
func AddTeamRepo(orgID, teamID, repoID int64) error { func AddTeamRepo(orgID, teamID, repoID int64) error {
return addTeamRepo(db, orgID, teamID, repoID) return addTeamRepo(x, orgID, teamID, repoID)
} }
func removeTeamRepo(tx *gorm.DB, teamID, repoID int64) error { func removeTeamRepo(e Engine, teamID, repoID int64) error {
return tx.Where("team_id = ? AND repo_id = ?", teamID, repoID).Delete(new(TeamRepo)).Error _, err := e.Delete(&TeamRepo{
TeamID: teamID,
RepoID: repoID,
})
return err
} }
// RemoveTeamRepo deletes repository relation to team. // RemoveTeamRepo deletes repository relation to team.
func RemoveTeamRepo(teamID, repoID int64) error { func RemoveTeamRepo(teamID, repoID int64) error {
return removeTeamRepo(db, teamID, repoID) return removeTeamRepo(x, teamID, repoID)
} }
// GetTeamsHaveAccessToRepo returns all teams in an organization that have given access level to the repository. // GetTeamsHaveAccessToRepo returns all teams in an organization that have given access level to the repository.
func GetTeamsHaveAccessToRepo(orgID, repoID int64, mode AccessMode) ([]*Team, error) { func GetTeamsHaveAccessToRepo(orgID, repoID int64, mode AccessMode) ([]*Team, error) {
teams := make([]*Team, 0, 5) teams := make([]*Team, 0, 5)
return teams, db.Table("team"). return teams, x.Where("team.authorize >= ?", mode).
Where("team.authorize >= ?", mode). Join("INNER", "team_repo", "team_repo.team_id = team.id").
Joins("INNER JOIN team_repo ON team_repo.team_id = team.id"). And("team_repo.org_id = ?", orgID).
Where("team_repo.org_id = ? AND team_repo.repo_id = ?", orgID, repoID). And("team_repo.repo_id = ?", repoID).
Find(&teams).Error Find(&teams)
} }

View File

@@ -9,8 +9,8 @@ import (
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
"github.com/unknwon/com" "github.com/unknwon/com"
"gorm.io/gorm"
log "unknwon.dev/clog/v2" log "unknwon.dev/clog/v2"
"xorm.io/xorm"
"github.com/gogs/git-module" "github.com/gogs/git-module"
api "github.com/gogs/go-gogs-client" api "github.com/gogs/go-gogs-client"
@@ -71,31 +71,35 @@ func (pr *PullRequest) BeforeUpdate() {
} }
// Note: don't try to get Issue because will end up recursive querying. // Note: don't try to get Issue because will end up recursive querying.
func (pr *PullRequest) AfterFind(tx *gorm.DB) error { func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) {
if pr.HasMerged { switch colName {
case "merged_unix":
if !pr.HasMerged {
return
}
pr.Merged = time.Unix(pr.MergedUnix, 0).Local() pr.Merged = time.Unix(pr.MergedUnix, 0).Local()
} }
return nil
} }
// Note: don't try to get Issue because will end up recursive querying. // Note: don't try to get Issue because will end up recursive querying.
func (pr *PullRequest) loadAttributes(db *gorm.DB) (err error) { func (pr *PullRequest) loadAttributes(e Engine) (err error) {
if pr.HeadRepo == nil { if pr.HeadRepo == nil {
pr.HeadRepo, err = getRepositoryByID(db, pr.HeadRepoID) pr.HeadRepo, err = getRepositoryByID(e, pr.HeadRepoID)
if err != nil && !IsErrRepoNotExist(err) { if err != nil && !IsErrRepoNotExist(err) {
return errors.Newf("get head repository by ID: %v", err) return errors.Newf("get head repository by ID: %v", err)
} }
} }
if pr.BaseRepo == nil { if pr.BaseRepo == nil {
pr.BaseRepo, err = getRepositoryByID(db, pr.BaseRepoID) pr.BaseRepo, err = getRepositoryByID(e, pr.BaseRepoID)
if err != nil { if err != nil {
return errors.Newf("get base repository by ID: %v", err) return errors.Newf("get base repository by ID: %v", err)
} }
} }
if pr.HasMerged && pr.Merger == nil { if pr.HasMerged && pr.Merger == nil {
pr.Merger, err = getUserByID(db, pr.MergerID) pr.Merger, err = getUserByID(e, pr.MergerID)
if IsErrUserNotExist(err) { if IsErrUserNotExist(err) {
pr.MergerID = -1 pr.MergerID = -1
pr.Merger = NewGhostUser() pr.Merger = NewGhostUser()
@@ -108,7 +112,7 @@ func (pr *PullRequest) loadAttributes(db *gorm.DB) (err error) {
} }
func (pr *PullRequest) LoadAttributes() error { func (pr *PullRequest) LoadAttributes() error {
return pr.loadAttributes(db) return pr.loadAttributes(x)
} }
func (pr *PullRequest) LoadIssue() (err error) { func (pr *PullRequest) LoadIssue() (err error) {
@@ -195,8 +199,13 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false) go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false)
}() }()
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
if err := pr.Issue.changeStatus(tx, doer, pr.Issue.Repo, true); err != nil { defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
if err = pr.Issue.changeStatus(sess, doer, pr.Issue.Repo, true); err != nil {
return errors.Newf("Issue.changeStatus: %v", err) return errors.Newf("Issue.changeStatus: %v", err)
} }
@@ -315,10 +324,14 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
pr.HasMerged = true pr.HasMerged = true
pr.Merged = time.Now() pr.Merged = time.Now()
pr.MergerID = doer.ID pr.MergerID = doer.ID
if err := tx.Model(&PullRequest{}).Where("id = ?", pr.ID).Updates(pr).Error; err != nil { if _, err = sess.ID(pr.ID).AllCols().Update(pr); err != nil {
return errors.Newf("update pull request: %v", err) return errors.Newf("update pull request: %v", err)
} }
if err = sess.Commit(); err != nil {
return errors.Newf("commit: %v", err)
}
if err = Handle.Actions().MergePullRequest(ctx, doer, pr.Issue.Repo.Owner, pr.Issue.Repo, pr.Issue); err != nil { if err = Handle.Actions().MergePullRequest(ctx, doer, pr.Issue.Repo.Owner, pr.Issue.Repo, pr.Issue); err != nil {
log.Error("Failed to create action for merge pull request, pull_request_id: %d, error: %v", pr.ID, err) log.Error("Failed to create action for merge pull request, pull_request_id: %d, error: %v", pr.ID, err)
} }
@@ -378,7 +391,6 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
return nil return nil
} }
return nil return nil
})
} }
// testPatch checks if patch can be merged to base repository without conflict. // testPatch checks if patch can be merged to base repository without conflict.
@@ -431,8 +443,13 @@ func (pr *PullRequest) testPatch() (err error) {
// NewPullRequest creates new pull request with labels for repository. // NewPullRequest creates new pull request with labels for repository.
func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte) (err error) { func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte) (err error) {
err = db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
if err := newIssue(tx, NewIssueOptions{ defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
if err = newIssue(sess, NewIssueOptions{
Repo: repo, Repo: repo,
Issue: pull, Issue: pull,
LableIDs: labelIDs, LableIDs: labelIDs,
@@ -443,12 +460,12 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
} }
pr.Index = pull.Index pr.Index = pull.Index
if err := repo.SavePatch(pr.Index, patch); err != nil { if err = repo.SavePatch(pr.Index, patch); err != nil {
return errors.Newf("SavePatch: %v", err) return errors.Newf("SavePatch: %v", err)
} }
pr.BaseRepo = repo pr.BaseRepo = repo
if err := pr.testPatch(); err != nil { if err = pr.testPatch(); err != nil {
return errors.Newf("testPatch: %v", err) return errors.Newf("testPatch: %v", err)
} }
// No conflict appears after test means mergeable. // No conflict appears after test means mergeable.
@@ -457,14 +474,12 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
} }
pr.IssueID = pull.ID pr.IssueID = pull.ID
if err := tx.Create(pr).Error; err != nil { if _, err = sess.Insert(pr); err != nil {
return errors.Newf("insert pull repo: %v", err) return errors.Newf("insert pull repo: %v", err)
} }
return nil if err = sess.Commit(); err != nil {
}) return errors.Newf("commit: %v", err)
if err != nil {
return err
} }
if err = NotifyWatchers(&Action{ if err = NotifyWatchers(&Action{
@@ -502,12 +517,12 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
// by given head/base and repo/branch. // by given head/base and repo/branch.
func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) { func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) {
pr := new(PullRequest) pr := new(PullRequest)
err := db.Joins("INNER JOIN issue ON issue.id = pull_request.issue_id"). has, err := x.Where("head_repo_id=? AND head_branch=? AND base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
Where("pull_request.head_repo_id = ? AND pull_request.head_branch = ? AND pull_request.base_repo_id = ? AND pull_request.base_branch = ? AND pull_request.has_merged = ? AND issue.is_closed = ?",
headRepoID, headBranch, baseRepoID, baseBranch, false, false). headRepoID, headBranch, baseRepoID, baseBranch, false, false).
First(pr).Error Join("INNER", "issue", "issue.id=pull_request.issue_id").Get(pr)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { return nil, err
} else if !has {
return nil, ErrPullRequestNotExist{args: map[string]any{ return nil, ErrPullRequestNotExist{args: map[string]any{
"headRepoID": headRepoID, "headRepoID": headRepoID,
"baseRepoID": baseRepoID, "baseRepoID": baseRepoID,
@@ -515,8 +530,6 @@ func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch
"baseBranch": baseBranch, "baseBranch": baseBranch,
}} }}
} }
return nil, err
}
return pr, nil return pr, nil
} }
@@ -525,22 +538,18 @@ func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch
// by given head information (repo and branch). // by given head information (repo and branch).
func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequest, error) { func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequest, error) {
prs := make([]*PullRequest, 0, 2) prs := make([]*PullRequest, 0, 2)
err := db.Joins("INNER JOIN issue ON issue.id = pull_request.issue_id"). return prs, x.Where("head_repo_id = ? AND head_branch = ? AND has_merged = ? AND issue.is_closed = ?",
Where("pull_request.head_repo_id = ? AND pull_request.head_branch = ? AND pull_request.has_merged = ? AND issue.is_closed = ?",
repoID, branch, false, false). repoID, branch, false, false).
Find(&prs).Error Join("INNER", "issue", "issue.id = pull_request.issue_id").Find(&prs)
return prs, err
} }
// GetUnmergedPullRequestsByBaseInfo returns all pull requests that are open and has not been merged // GetUnmergedPullRequestsByBaseInfo returns all pull requests that are open and has not been merged
// by given base information (repo and branch). // by given base information (repo and branch).
func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequest, error) { func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequest, error) {
prs := make([]*PullRequest, 0, 2) prs := make([]*PullRequest, 0, 2)
err := db.Joins("INNER JOIN issue ON issue.id = pull_request.issue_id"). return prs, x.Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
Where("pull_request.base_repo_id = ? AND pull_request.base_branch = ? AND pull_request.has_merged = ? AND issue.is_closed = ?",
repoID, branch, false, false). repoID, branch, false, false).
Find(&prs).Error Join("INNER", "issue", "issue.id=pull_request.issue_id").Find(&prs)
return prs, err
} }
var _ errutil.NotFound = (*ErrPullRequestNotExist)(nil) var _ errutil.NotFound = (*ErrPullRequestNotExist)(nil)
@@ -562,65 +571,50 @@ func (ErrPullRequestNotExist) NotFound() bool {
return true return true
} }
func getPullRequestByID(db *gorm.DB, id int64) (*PullRequest, error) { func getPullRequestByID(e Engine, id int64) (*PullRequest, error) {
pr := new(PullRequest) pr := new(PullRequest)
err := db.First(pr, id).Error has, err := e.ID(id).Get(pr)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { return nil, err
} else if !has {
return nil, ErrPullRequestNotExist{args: map[string]any{"pullRequestID": id}} return nil, ErrPullRequestNotExist{args: map[string]any{"pullRequestID": id}}
} }
return nil, err return pr, pr.loadAttributes(e)
}
return pr, pr.loadAttributes(db)
} }
// GetPullRequestByID returns a pull request by given ID. // GetPullRequestByID returns a pull request by given ID.
func GetPullRequestByID(id int64) (*PullRequest, error) { func GetPullRequestByID(id int64) (*PullRequest, error) {
return getPullRequestByID(db, id) return getPullRequestByID(x, id)
} }
func getPullRequestByIssueID(db *gorm.DB, issueID int64) (*PullRequest, error) { func getPullRequestByIssueID(e Engine, issueID int64) (*PullRequest, error) {
pr := &PullRequest{} pr := &PullRequest{
err := db.Where("issue_id = ?", issueID).First(pr).Error IssueID: issueID,
}
has, err := e.Get(pr)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { return nil, err
} else if !has {
return nil, ErrPullRequestNotExist{args: map[string]any{"issueID": issueID}} return nil, ErrPullRequestNotExist{args: map[string]any{"issueID": issueID}}
} }
return nil, err return pr, pr.loadAttributes(e)
}
return pr, pr.loadAttributes(db)
} }
// GetPullRequestByIssueID returns pull request by given issue ID. // GetPullRequestByIssueID returns pull request by given issue ID.
func GetPullRequestByIssueID(issueID int64) (*PullRequest, error) { func GetPullRequestByIssueID(issueID int64) (*PullRequest, error) {
return getPullRequestByIssueID(db, issueID) return getPullRequestByIssueID(x, issueID)
} }
// Update updates all fields of pull request. // Update updates all fields of pull request.
func (pr *PullRequest) Update() error { func (pr *PullRequest) Update() error {
return db.Model(&PullRequest{}).Where("id = ?", pr.ID).Updates(pr).Error _, err := x.Id(pr.ID).AllCols().Update(pr)
return err
} }
// Update updates specific fields of pull request. // Update updates specific fields of pull request.
func (pr *PullRequest) UpdateCols(cols ...string) error { func (pr *PullRequest) UpdateCols(cols ...string) error {
updates := make(map[string]any) _, err := x.Id(pr.ID).Cols(cols...).Update(pr)
for _, col := range cols { return err
switch col {
case "status":
updates["status"] = pr.Status
case "merge_base":
updates["merge_base"] = pr.MergeBase
case "has_merged":
updates["has_merged"] = pr.HasMerged
case "merged_commit_id":
updates["merged_commit_id"] = pr.MergedCommitID
case "merger_id":
updates["merger_id"] = pr.MergerID
case "merged_unix":
updates["merged_unix"] = pr.MergedUnix
}
}
return db.Model(&PullRequest{}).Where("id = ?", pr.ID).Updates(updates).Error
} }
// UpdatePatch generates and saves a new patch. // UpdatePatch generates and saves a new patch.
@@ -717,7 +711,7 @@ func (pr *PullRequest) AddToTaskQueue() {
type PullRequestList []*PullRequest type PullRequestList []*PullRequest
func (prs PullRequestList) loadAttributes(db *gorm.DB) (err error) { func (prs PullRequestList) loadAttributes(e Engine) (err error) {
if len(prs) == 0 { if len(prs) == 0 {
return nil return nil
} }
@@ -732,7 +726,7 @@ func (prs PullRequestList) loadAttributes(db *gorm.DB) (err error) {
issueIDs = append(issueIDs, issueID) issueIDs = append(issueIDs, issueID)
} }
issues := make([]*Issue, 0, len(issueIDs)) issues := make([]*Issue, 0, len(issueIDs))
if err = db.Where("id IN ?", issueIDs).Find(&issues).Error; err != nil { if err = e.Where("id > 0").In("id", issueIDs).Find(&issues); err != nil {
return errors.Newf("find issues: %v", err) return errors.Newf("find issues: %v", err)
} }
for i := range issues { for i := range issues {
@@ -744,7 +738,7 @@ func (prs PullRequestList) loadAttributes(db *gorm.DB) (err error) {
// Load attributes // Load attributes
for i := range prs { for i := range prs {
if err = prs[i].loadAttributes(db); err != nil { if err = prs[i].loadAttributes(e); err != nil {
return errors.Newf("loadAttributes [%d]: %v", prs[i].ID, err) return errors.Newf("loadAttributes [%d]: %v", prs[i].ID, err)
} }
} }
@@ -753,7 +747,7 @@ func (prs PullRequestList) loadAttributes(db *gorm.DB) (err error) {
} }
func (prs PullRequestList) LoadAttributes() error { func (prs PullRequestList) LoadAttributes() error {
return prs.loadAttributes(db) return prs.loadAttributes(x)
} }
func addHeadRepoTasks(prs []*PullRequest) { func addHeadRepoTasks(prs []*PullRequest) {
@@ -844,21 +838,23 @@ func (pr *PullRequest) checkAndUpdateStatus() {
// TestPullRequests checks and tests untested patches of pull requests. // TestPullRequests checks and tests untested patches of pull requests.
// TODO: test more pull requests at same time. // TODO: test more pull requests at same time.
func TestPullRequests() { func TestPullRequests() {
var prs []*PullRequest prs := make([]*PullRequest, 0, 10)
_ = db.Where("status = ?", PullRequestStatusChecking).FindInBatches(&prs, 100, func(tx *gorm.DB, batch int) error { _ = x.Iterate(PullRequest{
for i := range prs { Status: PullRequestStatusChecking,
pr := prs[i] },
func(idx int, bean any) error {
pr := bean.(*PullRequest)
if err := pr.LoadAttributes(); err != nil { if err := pr.LoadAttributes(); err != nil {
log.Error("LoadAttributes: %v", err) log.Error("LoadAttributes: %v", err)
continue return nil
} }
if err := pr.testPatch(); err != nil { if err := pr.testPatch(); err != nil {
log.Error("testPatch: %v", err) log.Error("testPatch: %v", err)
continue return nil
}
} }
prs = append(prs, pr)
return nil return nil
}) })

View File

@@ -6,11 +6,12 @@ import (
"strings" "strings"
"time" "time"
log "unknwon.dev/clog/v2"
"xorm.io/xorm"
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
"github.com/gogs/git-module" "github.com/gogs/git-module"
api "github.com/gogs/go-gogs-client" api "github.com/gogs/go-gogs-client"
"gorm.io/gorm"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/errutil" "gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/process" "gogs.io/gogs/internal/process"
@@ -20,39 +21,40 @@ import (
type Release struct { type Release struct {
ID int64 ID int64
RepoID int64 RepoID int64
Repo *Repository `gorm:"-" json:"-"` Repo *Repository `xorm:"-" json:"-" gorm:"-"`
PublisherID int64 PublisherID int64
Publisher *User `gorm:"-" json:"-"` Publisher *User `xorm:"-" json:"-" gorm:"-"`
TagName string TagName string
LowerTagName string LowerTagName string
Target string Target string
Title string Title string
Sha1 string `gorm:"type:varchar(40)"` Sha1 string `xorm:"VARCHAR(40)"`
NumCommits int64 NumCommits int64
NumCommitsBehind int64 `gorm:"-" json:"-"` NumCommitsBehind int64 `xorm:"-" json:"-" gorm:"-"`
Note string `gorm:"type:text"` Note string `xorm:"TEXT"`
IsDraft bool `gorm:"not null;default:false"` IsDraft bool `xorm:"NOT NULL DEFAULT false"`
IsPrerelease bool IsPrerelease bool
Created time.Time `gorm:"-" json:"-"` Created time.Time `xorm:"-" json:"-" gorm:"-"`
CreatedUnix int64 CreatedUnix int64
Attachments []*Attachment `gorm:"-" json:"-"` Attachments []*Attachment `xorm:"-" json:"-" gorm:"-"`
} }
func (r *Release) BeforeCreate(tx *gorm.DB) error { func (r *Release) BeforeInsert() {
if r.CreatedUnix == 0 { if r.CreatedUnix == 0 {
r.CreatedUnix = tx.NowFunc().Unix() r.CreatedUnix = time.Now().Unix()
} }
return nil
} }
func (r *Release) AfterFind(tx *gorm.DB) error { func (r *Release) AfterSet(colName string, _ xorm.Cell) {
switch colName {
case "created_unix":
r.Created = time.Unix(r.CreatedUnix, 0).Local() r.Created = time.Unix(r.CreatedUnix, 0).Local()
return nil }
} }
func (r *Release) loadAttributes(e *gorm.DB) (err error) { func (r *Release) loadAttributes(e Engine) (err error) {
if r.Repo == nil { if r.Repo == nil {
r.Repo, err = getRepositoryByID(e, r.RepoID) r.Repo, err = getRepositoryByID(e, r.RepoID)
if err != nil { if err != nil {
@@ -83,7 +85,7 @@ func (r *Release) loadAttributes(e *gorm.DB) (err error) {
} }
func (r *Release) LoadAttributes() error { func (r *Release) LoadAttributes() error {
return r.loadAttributes(db) return r.loadAttributes(x)
} }
// This method assumes some fields assigned with values: // This method assumes some fields assigned with values:
@@ -108,9 +110,7 @@ func IsReleaseExist(repoID int64, tagName string) (bool, error) {
return false, nil return false, nil
} }
var count int64 return x.Get(&Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)})
err := db.Model(&Release{}).Where("repo_id = ? AND lower_tag_name = ?", repoID, strings.ToLower(tagName)).Count(&count).Error
return count > 0, err
} }
func createTag(gitRepo *git.Repository, r *Release) error { func createTag(gitRepo *git.Repository, r *Release) error {
@@ -171,21 +171,24 @@ func NewRelease(gitRepo *git.Repository, r *Release, uuids []string) error {
} }
r.LowerTagName = strings.ToLower(r.TagName) r.LowerTagName = strings.ToLower(r.TagName)
err = db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
if err := tx.Create(r).Error; err != nil { defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Insert(r); err != nil {
return errors.Newf("insert: %v", err) return errors.Newf("insert: %v", err)
} }
if len(uuids) > 0 { if len(uuids) > 0 {
if err := tx.Model(&Attachment{}).Where("uuid IN ?", uuids).Update("release_id", r.ID).Error; err != nil { if _, err = sess.In("uuid", uuids).Cols("release_id").Update(&Attachment{ReleaseID: r.ID}); err != nil {
return errors.Newf("link attachments: %v", err) return errors.Newf("link attachments: %v", err)
} }
} }
return nil if err = sess.Commit(); err != nil {
}) return errors.Newf("commit: %v", err)
if err != nil {
return err
} }
// Only send webhook when actually published, skip drafts // Only send webhook when actually published, skip drafts
@@ -228,8 +231,8 @@ func GetRelease(repoID int64, tagName string) (*Release, error) {
return nil, ErrReleaseNotExist{args: map[string]any{"tag": tagName}} return nil, ErrReleaseNotExist{args: map[string]any{"tag": tagName}}
} }
r := &Release{} r := &Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)}
if err = db.Where("repo_id = ? AND lower_tag_name = ?", repoID, strings.ToLower(tagName)).First(r).Error; err != nil { if _, err = x.Get(r); err != nil {
return nil, errors.Newf("get: %v", err) return nil, errors.Newf("get: %v", err)
} }
@@ -239,12 +242,11 @@ func GetRelease(repoID int64, tagName string) (*Release, error) {
// GetReleaseByID returns release with given ID. // GetReleaseByID returns release with given ID.
func GetReleaseByID(id int64) (*Release, error) { func GetReleaseByID(id int64) (*Release, error) {
r := new(Release) r := new(Release)
err := db.Where("id = ?", id).First(r).Error has, err := x.Id(id).Get(r)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrReleaseNotExist{args: map[string]any{"releaseID": id}}
}
return nil, err return nil, err
} else if !has {
return nil, ErrReleaseNotExist{args: map[string]any{"releaseID": id}}
} }
return r, r.LoadAttributes() return r, r.LoadAttributes()
@@ -254,24 +256,24 @@ func GetReleaseByID(id int64) (*Release, error) {
// If matches is not empty, only published releases in matches will be returned. // If matches is not empty, only published releases in matches will be returned.
// In any case, drafts won't be returned by this function. // In any case, drafts won't be returned by this function.
func GetPublishedReleasesByRepoID(repoID int64, matches ...string) ([]*Release, error) { func GetPublishedReleasesByRepoID(repoID int64, matches ...string) ([]*Release, error) {
query := db.Where("repo_id = ? AND is_draft = ?", repoID, false).Order("created_unix DESC") sess := x.Where("repo_id = ?", repoID).And("is_draft = ?", false).Desc("created_unix")
if len(matches) > 0 { if len(matches) > 0 {
query = query.Where("tag_name IN ?", matches) sess.In("tag_name", matches)
} }
releases := make([]*Release, 0, 5) releases := make([]*Release, 0, 5)
return releases, query.Find(&releases).Error return releases, sess.Find(&releases, new(Release))
} }
// GetReleasesByRepoID returns a list of all releases (including drafts) of given repository. // GetReleasesByRepoID returns a list of all releases (including drafts) of given repository.
func GetReleasesByRepoID(repoID int64) ([]*Release, error) { func GetReleasesByRepoID(repoID int64) ([]*Release, error) {
releases := make([]*Release, 0) releases := make([]*Release, 0)
return releases, db.Where("repo_id = ?", repoID).Find(&releases).Error return releases, x.Where("repo_id = ?", repoID).Find(&releases)
} }
// GetDraftReleasesByRepoID returns all draft releases of repository. // GetDraftReleasesByRepoID returns all draft releases of repository.
func GetDraftReleasesByRepoID(repoID int64) ([]*Release, error) { func GetDraftReleasesByRepoID(repoID int64) ([]*Release, error) {
releases := make([]*Release, 0) releases := make([]*Release, 0)
return releases, db.Where("repo_id = ? AND is_draft = ?", repoID, true).Find(&releases).Error return releases, x.Where("repo_id = ?", repoID).And("is_draft = ?", true).Find(&releases)
} }
type ReleaseSorter struct { type ReleaseSorter struct {
@@ -308,26 +310,28 @@ func UpdateRelease(doer *User, gitRepo *git.Repository, r *Release, isPublish bo
r.PublisherID = doer.ID r.PublisherID = doer.ID
err = db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
if err := tx.Model(r).Where("id = ?", r.ID).Updates(r).Error; err != nil { defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.ID(r.ID).AllCols().Update(r); err != nil {
return errors.Newf("Update: %v", err) return errors.Newf("Update: %v", err)
} }
// Unlink all current attachments and link back later if still valid // Unlink all current attachments and link back later if still valid
if err := tx.Exec("UPDATE attachment SET release_id = 0 WHERE release_id = ?", r.ID).Error; err != nil { if _, err = sess.Exec("UPDATE attachment SET release_id = 0 WHERE release_id = ?", r.ID); err != nil {
return errors.Newf("unlink current attachments: %v", err) return errors.Newf("unlink current attachments: %v", err)
} }
if len(uuids) > 0 { if len(uuids) > 0 {
if err := tx.Model(&Attachment{}).Where("uuid IN ?", uuids).Update("release_id", r.ID).Error; err != nil { if _, err = sess.In("uuid", uuids).Cols("release_id").Update(&Attachment{ReleaseID: r.ID}); err != nil {
return errors.Newf("link attachments: %v", err) return errors.Newf("link attachments: %v", err)
} }
} }
return nil if err = sess.Commit(); err != nil {
}) return errors.Newf("commit: %v", err)
if err != nil {
return err
} }
if !isPublish { if !isPublish {
@@ -362,7 +366,7 @@ func DeleteReleaseOfRepoByID(repoID, id int64) error {
return errors.Newf("git tag -d: %v - %s", err, stderr) return errors.Newf("git tag -d: %v - %s", err, stderr)
} }
if err = db.Where("id = ?", rel.ID).Delete(new(Release)).Error; err != nil { if _, err = x.Id(rel.ID).Delete(new(Release)); err != nil {
return errors.Newf("delete: %v", err) return errors.Newf("delete: %v", err)
} }

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,6 @@ import (
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
"github.com/gogs/git-module" "github.com/gogs/git-module"
"github.com/unknwon/com" "github.com/unknwon/com"
"gorm.io/gorm"
"gogs.io/gogs/internal/errutil" "gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/tool" "gogs.io/gogs/internal/tool"
@@ -94,9 +93,8 @@ type ProtectBranchWhitelist struct {
// IsUserInProtectBranchWhitelist returns true if given user is in the whitelist of a branch in a repository. // IsUserInProtectBranchWhitelist returns true if given user is in the whitelist of a branch in a repository.
func IsUserInProtectBranchWhitelist(repoID, userID int64, branch string) bool { func IsUserInProtectBranchWhitelist(repoID, userID int64, branch string) bool {
var whitelist ProtectBranchWhitelist has, err := x.Where("repo_id = ?", repoID).And("user_id = ?", userID).And("name = ?", branch).Get(new(ProtectBranchWhitelist))
err := db.Where("repo_id = ?", repoID).Where("user_id = ?", userID).Where("name = ?", branch).First(&whitelist).Error return has && err == nil
return err == nil
} }
// ProtectBranch contains options of a protected branch. // ProtectBranch contains options of a protected branch.
@@ -117,11 +115,11 @@ func GetProtectBranchOfRepoByName(repoID int64, name string) (*ProtectBranch, er
RepoID: repoID, RepoID: repoID,
Name: name, Name: name,
} }
err := db.Where("repo_id = ? AND name = ?", repoID, name).First(protectBranch).Error has, err := x.Get(protectBranch)
if errors.Is(err, gorm.ErrRecordNotFound) { if err != nil {
return nil, ErrBranchNotExist{args: map[string]any{"name": name}}
} else if err != nil {
return nil, err return nil, err
} else if !has {
return nil, ErrBranchNotExist{args: map[string]any{"name": name}}
} }
return protectBranch, nil return protectBranch, nil
} }
@@ -138,19 +136,23 @@ func IsBranchOfRepoRequirePullRequest(repoID int64, name string) bool {
// UpdateProtectBranch saves branch protection options. // UpdateProtectBranch saves branch protection options.
// If ID is 0, it creates a new record. Otherwise, updates existing record. // If ID is 0, it creates a new record. Otherwise, updates existing record.
func UpdateProtectBranch(protectBranch *ProtectBranch) (err error) { func UpdateProtectBranch(protectBranch *ProtectBranch) (err error) {
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
if protectBranch.ID == 0 { if protectBranch.ID == 0 {
if err := tx.Create(protectBranch).Error; err != nil { if _, err = sess.Insert(protectBranch); err != nil {
return errors.Newf("insert: %v", err) return errors.Newf("insert: %v", err)
} }
} }
if err := tx.Model(&ProtectBranch{}).Where("id = ?", protectBranch.ID).Updates(protectBranch).Error; err != nil { if _, err = sess.ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil {
return errors.Newf("update: %v", err) return errors.Newf("update: %v", err)
} }
return nil return sess.Commit()
})
} }
// UpdateOrgProtectBranch saves branch protection options of organizational repository. // UpdateOrgProtectBranch saves branch protection options of organizational repository.
@@ -207,7 +209,7 @@ func UpdateOrgProtectBranch(repo *Repository, protectBranch *ProtectBranch, whit
// Make sure protectBranch.ID is not 0 for whitelists // Make sure protectBranch.ID is not 0 for whitelists
if protectBranch.ID == 0 { if protectBranch.ID == 0 {
if err = db.Create(protectBranch).Error; err != nil { if _, err = x.Insert(protectBranch); err != nil {
return errors.Newf("insert: %v", err) return errors.Newf("insert: %v", err)
} }
} }
@@ -245,29 +247,30 @@ func UpdateOrgProtectBranch(repo *Repository, protectBranch *ProtectBranch, whit
} }
} }
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
if err := tx.Model(&ProtectBranch{}).Where("id = ?", protectBranch.ID).Updates(protectBranch).Error; err != nil { defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil {
return errors.Newf("Update: %v", err) return errors.Newf("Update: %v", err)
} }
// Refresh whitelists // Refresh whitelists
if hasUsersChanged || hasTeamsChanged { if hasUsersChanged || hasTeamsChanged {
if err := tx.Delete(&ProtectBranchWhitelist{}, "protect_branch_id = ?", protectBranch.ID).Error; err != nil { if _, err = sess.Delete(&ProtectBranchWhitelist{ProtectBranchID: protectBranch.ID}); err != nil {
return errors.Newf("delete old protect branch whitelists: %v", err) return errors.Newf("delete old protect branch whitelists: %v", err)
} } else if _, err = sess.Insert(whitelists); err != nil {
if len(whitelists) > 0 {
if err := tx.Create(&whitelists).Error; err != nil {
return errors.Newf("insert new protect branch whitelists: %v", err) return errors.Newf("insert new protect branch whitelists: %v", err)
} }
} }
}
return nil return sess.Commit()
})
} }
// GetProtectBranchesByRepoID returns a list of *ProtectBranch in given repository. // GetProtectBranchesByRepoID returns a list of *ProtectBranch in given repository.
func GetProtectBranchesByRepoID(repoID int64) ([]*ProtectBranch, error) { func GetProtectBranchesByRepoID(repoID int64) ([]*ProtectBranch, error) {
protectBranches := make([]*ProtectBranch, 0, 2) protectBranches := make([]*ProtectBranch, 0, 2)
return protectBranches, db.Where("repo_id = ? AND protected = ?", repoID, true).Order("name ASC").Find(&protectBranches).Error return protectBranches, x.Where("repo_id = ? and protected = ?", repoID, true).Asc("name").Find(&protectBranches)
} }

View File

@@ -5,7 +5,6 @@ import (
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
api "github.com/gogs/go-gogs-client" api "github.com/gogs/go-gogs-client"
"gorm.io/gorm"
) )
// Collaboration represent the relation between an individual and a repository. // Collaboration represent the relation between an individual and a repository.
@@ -35,12 +34,12 @@ func IsCollaborator(repoID, userID int64) bool {
RepoID: repoID, RepoID: repoID,
UserID: userID, UserID: userID,
} }
err := db.Where("repo_id = ? AND user_id = ?", repoID, userID).First(collaboration).Error has, err := x.Get(collaboration)
if err != nil { if err != nil {
log.Error("get collaboration [repo_id: %d, user_id: %d]: %v", repoID, userID, err) log.Error("get collaboration [repo_id: %d, user_id: %d]: %v", repoID, userID, err)
return false return false
} }
return true return has
} }
func (r *Repository) IsCollaborator(userID int64) bool { func (r *Repository) IsCollaborator(userID int64) bool {
@@ -54,29 +53,32 @@ func (r *Repository) AddCollaborator(u *User) error {
UserID: u.ID, UserID: u.ID,
} }
var existing Collaboration has, err := x.Get(collaboration)
err := db.Where("repo_id = ? AND user_id = ?", r.ID, u.ID).First(&existing).Error if err != nil {
if err == nil {
return nil
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
return err return err
} else if has {
return nil
} }
collaboration.Mode = AccessModeWrite collaboration.Mode = AccessModeWrite
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
if err := tx.Create(collaboration).Error; err != nil { defer sess.Close()
if err = sess.Begin(); err != nil {
return err return err
} }
if err := r.recalculateAccesses(tx); err != nil {
if _, err = sess.Insert(collaboration); err != nil {
return err
} else if err = r.recalculateAccesses(sess); err != nil {
return errors.Newf("recalculateAccesses [repo_id: %v]: %v", r.ID, err) return errors.Newf("recalculateAccesses [repo_id: %v]: %v", r.ID, err)
} }
return nil
}) return sess.Commit()
} }
func (r *Repository) getCollaborations(e *gorm.DB) ([]*Collaboration, error) { func (r *Repository) getCollaborations(e Engine) ([]*Collaboration, error) {
collaborations := make([]*Collaboration, 0) collaborations := make([]*Collaboration, 0)
return collaborations, e.Where("repo_id = ?", r.ID).Find(&collaborations).Error return collaborations, e.Find(&collaborations, &Collaboration{RepoID: r.ID})
} }
// Collaborator represents a user with collaboration details. // Collaborator represents a user with collaboration details.
@@ -96,7 +98,7 @@ func (c *Collaborator) APIFormat() *api.Collaborator {
} }
} }
func (r *Repository) getCollaborators(e *gorm.DB) ([]*Collaborator, error) { func (r *Repository) getCollaborators(e Engine) ([]*Collaborator, error) {
collaborations, err := r.getCollaborations(e) collaborations, err := r.getCollaborations(e)
if err != nil { if err != nil {
return nil, errors.Newf("getCollaborations: %v", err) return nil, errors.Newf("getCollaborations: %v", err)
@@ -118,7 +120,7 @@ func (r *Repository) getCollaborators(e *gorm.DB) ([]*Collaborator, error) {
// GetCollaborators returns the collaborators for a repository // GetCollaborators returns the collaborators for a repository
func (r *Repository) GetCollaborators() ([]*Collaborator, error) { func (r *Repository) GetCollaborators() ([]*Collaborator, error) {
return r.getCollaborators(db) return r.getCollaborators(x)
} }
// ChangeCollaborationAccessMode sets new access mode for the collaboration. // ChangeCollaborationAccessMode sets new access mode for the collaboration.
@@ -132,11 +134,11 @@ func (r *Repository) ChangeCollaborationAccessMode(userID int64, mode AccessMode
RepoID: r.ID, RepoID: r.ID,
UserID: userID, UserID: userID,
} }
err := db.Where("repo_id = ? AND user_id = ?", r.ID, userID).First(collaboration).Error has, err := x.Get(collaboration)
if errors.Is(err, gorm.ErrRecordNotFound) { if err != nil {
return nil
} else if err != nil {
return errors.Newf("get collaboration: %v", err) return errors.Newf("get collaboration: %v", err)
} else if !has {
return nil
} }
if collaboration.Mode == mode { if collaboration.Mode == mode {
@@ -157,8 +159,13 @@ func (r *Repository) ChangeCollaborationAccessMode(userID int64, mode AccessMode
} }
} }
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
if err := tx.Model(&Collaboration{}).Where("id = ?", collaboration.ID).Updates(collaboration).Error; err != nil { defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.ID(collaboration.ID).AllCols().Update(collaboration); err != nil {
return errors.Newf("update collaboration: %v", err) return errors.Newf("update collaboration: %v", err)
} }
@@ -166,22 +173,21 @@ func (r *Repository) ChangeCollaborationAccessMode(userID int64, mode AccessMode
UserID: userID, UserID: userID,
RepoID: r.ID, RepoID: r.ID,
} }
err := tx.Where("user_id = ? AND repo_id = ?", userID, r.ID).First(access).Error has, err = sess.Get(access)
if err == nil { if err != nil {
if err := tx.Exec("UPDATE access SET mode = ? WHERE user_id = ? AND repo_id = ?", mode, userID, r.ID).Error; err != nil {
return errors.Newf("update access table: %v", err)
}
} else if errors.Is(err, gorm.ErrRecordNotFound) {
access.Mode = mode
if err := tx.Create(access).Error; err != nil {
return errors.Newf("insert access table: %v", err)
}
} else {
return errors.Newf("get access record: %v", err) return errors.Newf("get access record: %v", err)
} }
if has {
_, err = sess.Exec("UPDATE access SET mode = ? WHERE user_id = ? AND repo_id = ?", mode, userID, r.ID)
} else {
access.Mode = mode
_, err = sess.Insert(access)
}
if err != nil {
return errors.Newf("update/insert access table: %v", err)
}
return nil return sess.Commit()
})
} }
// DeleteCollaboration removes collaboration relation between the user and repository. // DeleteCollaboration removes collaboration relation between the user and repository.
@@ -195,20 +201,19 @@ func DeleteCollaboration(repo *Repository, userID int64) (err error) {
UserID: userID, UserID: userID,
} }
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
result := tx.Delete(collaboration, "repo_id = ? AND user_id = ?", repo.ID, userID) defer sess.Close()
if result.Error != nil { if err = sess.Begin(); err != nil {
return result.Error
} else if result.RowsAffected == 0 {
return nil
}
if err := repo.recalculateAccesses(tx); err != nil {
return err return err
} }
return nil if has, err := sess.Delete(collaboration); err != nil || has == 0 {
}) return err
} else if err = repo.recalculateAccesses(sess); err != nil {
return err
}
return sess.Commit()
} }
func (r *Repository) DeleteCollaboration(userID int64) error { func (r *Repository) DeleteCollaboration(userID int64) error {

View File

@@ -14,7 +14,6 @@ import (
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
gouuid "github.com/satori/go.uuid" gouuid "github.com/satori/go.uuid"
"github.com/unknwon/com" "github.com/unknwon/com"
"gorm.io/gorm"
"github.com/gogs/git-module" "github.com/gogs/git-module"
@@ -24,7 +23,6 @@ import (
"gogs.io/gogs/internal/osutil" "gogs.io/gogs/internal/osutil"
"gogs.io/gogs/internal/pathutil" "gogs.io/gogs/internal/pathutil"
"gogs.io/gogs/internal/process" "gogs.io/gogs/internal/process"
"gogs.io/gogs/internal/tool"
) )
// BranchAlreadyExists represents an error when branch already exists. // BranchAlreadyExists represents an error when branch already exists.
@@ -416,8 +414,10 @@ func (upload *Upload) LocalPath() string {
// NewUpload creates a new upload object. // NewUpload creates a new upload object.
func NewUpload(name string, buf []byte, file multipart.File) (_ *Upload, err error) { func NewUpload(name string, buf []byte, file multipart.File) (_ *Upload, err error) {
if tool.IsMaliciousPath(name) { // 🚨 SECURITY: Prevent path traversal.
return nil, errors.Newf("malicious path detected: %s", name) name = pathutil.Clean(name)
if name == "" {
return nil, errors.New("empty file name")
} }
upload := &Upload{ upload := &Upload{
@@ -442,7 +442,7 @@ func NewUpload(name string, buf []byte, file multipart.File) (_ *Upload, err err
return nil, errors.Newf("copy: %v", err) return nil, errors.Newf("copy: %v", err)
} }
if err := db.Create(upload).Error; err != nil { if _, err := x.Insert(upload); err != nil {
return nil, err return nil, err
} }
@@ -451,12 +451,11 @@ func NewUpload(name string, buf []byte, file multipart.File) (_ *Upload, err err
func GetUploadByUUID(uuid string) (*Upload, error) { func GetUploadByUUID(uuid string) (*Upload, error) {
upload := &Upload{UUID: uuid} upload := &Upload{UUID: uuid}
err := db.Where("uuid = ?", uuid).First(upload).Error has, err := x.Get(upload)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrUploadNotExist{0, uuid}
}
return nil, err return nil, err
} else if !has {
return nil, ErrUploadNotExist{0, uuid}
} }
return upload, nil return upload, nil
} }
@@ -468,7 +467,7 @@ func GetUploadsByUUIDs(uuids []string) ([]*Upload, error) {
// Silently drop invalid uuids. // Silently drop invalid uuids.
uploads := make([]*Upload, 0, len(uuids)) uploads := make([]*Upload, 0, len(uuids))
return uploads, db.Where("uuid IN ?", uuids).Find(&uploads).Error return uploads, x.In("uuid", uuids).Find(&uploads)
} }
func DeleteUploads(uploads ...*Upload) (err error) { func DeleteUploads(uploads ...*Upload) (err error) {
@@ -476,12 +475,17 @@ func DeleteUploads(uploads ...*Upload) (err error) {
return nil return nil
} }
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
ids := make([]int64, len(uploads)) ids := make([]int64, len(uploads))
for i := 0; i < len(uploads); i++ { for i := 0; i < len(uploads); i++ {
ids[i] = uploads[i].ID ids[i] = uploads[i].ID
} }
if err := tx.Where("id IN ?", ids).Delete(new(Upload)).Error; err != nil { if _, err = sess.In("id", ids).Delete(new(Upload)); err != nil {
return errors.Newf("delete uploads: %v", err) return errors.Newf("delete uploads: %v", err)
} }
@@ -496,8 +500,7 @@ func DeleteUploads(uploads ...*Upload) (err error) {
} }
} }
return nil return sess.Commit()
})
} }
func DeleteUpload(u *Upload) error { func DeleteUpload(u *Upload) error {

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
api "github.com/gogs/go-gogs-client" api "github.com/gogs/go-gogs-client"
@@ -27,6 +28,13 @@ func (r *Repository) BeforeUpdate(tx *gorm.DB) error {
return nil return nil
} }
// AfterFind implements the GORM query hook.
func (r *Repository) AfterFind(_ *gorm.DB) error {
r.Created = time.Unix(r.CreatedUnix, 0).Local()
r.Updated = time.Unix(r.UpdatedUnix, 0).Local()
return nil
}
type RepositoryAPIFormatOptions struct { type RepositoryAPIFormatOptions struct {
Permission *api.Permission Permission *api.Permission
Parent *api.Repository Parent *api.Repository

View File

@@ -8,11 +8,13 @@ import (
"strings" "strings"
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
"github.com/glebarez/sqlite"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/renderer"
"github.com/olekukonko/tablewriter/tw"
"gopkg.in/DATA-DOG/go-sqlmock.v2" "gopkg.in/DATA-DOG/go-sqlmock.v2"
"gorm.io/driver/mysql" "gorm.io/driver/mysql"
"gorm.io/driver/postgres" "gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
"gorm.io/gorm/schema" "gorm.io/gorm/schema"
@@ -61,18 +63,25 @@ func main() {
_, _ = w.WriteString("```\n") _, _ = w.WriteString("```\n")
table := tablewriter.NewWriter(w) table := tablewriter.NewTable(w,
table.SetHeader([]string{"Field", "Column", "PostgreSQL", "MySQL", "SQLite3"}) tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{
table.SetBorder(false) Borders: tw.BorderNone,
Symbols: tw.NewSymbols(tw.StyleASCII),
})),
tablewriter.WithHeaderAutoFormat(tw.Off),
)
table.Header("Field", "Column", "PostgreSQL", "MySQL", "SQLite3")
for j, f := range ti.Fields { for j, f := range ti.Fields {
table.Append([]string{ sqlite3Type := strings.ToUpper(collected[2][i].Fields[j].Type)
sqlite3Type = strings.ReplaceAll(sqlite3Type, "PRIMARY KEY ", "")
_ = table.Append([]string{
f.Name, f.Column, f.Name, f.Column,
strings.ToUpper(f.Type), // PostgreSQL strings.ToUpper(f.Type), // PostgreSQL
strings.ToUpper(collected[1][i].Fields[j].Type), // MySQL strings.ToUpper(collected[1][i].Fields[j].Type), // MySQL
strings.ToUpper(collected[2][i].Fields[j].Type), // SQLite3 sqlite3Type,
}) })
} }
table.Render() _ = table.Render()
_, _ = w.WriteString("\n") _, _ = w.WriteString("\n")
_, _ = w.WriteString("Primary keys: ") _, _ = w.WriteString("Primary keys: ")

View File

@@ -16,8 +16,8 @@ import (
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
"github.com/unknwon/com" "github.com/unknwon/com"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"gorm.io/gorm"
log "unknwon.dev/clog/v2" log "unknwon.dev/clog/v2"
"xorm.io/xorm"
"gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/errutil" "gogs.io/gogs/internal/errutil"
@@ -40,41 +40,38 @@ const (
// PublicKey represents a user or deploy SSH public key. // PublicKey represents a user or deploy SSH public key.
type PublicKey struct { type PublicKey struct {
ID int64 `gorm:"primaryKey"` ID int64 `gorm:"primaryKey"`
OwnerID int64 `gorm:"index;not null"` OwnerID int64 `xorm:"INDEX NOT NULL" gorm:"index;not null"`
Name string `gorm:"not null"` Name string `xorm:"NOT NULL" gorm:"not null"`
Fingerprint string `gorm:"not null"` Fingerprint string `xorm:"NOT NULL" gorm:"not null"`
Content string `gorm:"type:text;not null"` Content string `xorm:"TEXT NOT NULL" gorm:"type:TEXT;not null"`
Mode AccessMode `gorm:"not null;default:2"` Mode AccessMode `xorm:"NOT NULL DEFAULT 2" gorm:"not null;default:2"`
Type KeyType `gorm:"not null;default:1"` Type KeyType `xorm:"NOT NULL DEFAULT 1" gorm:"not null;default:1"`
Created time.Time `gorm:"-" json:"-"` Created time.Time `xorm:"-" json:"-" gorm:"-"`
CreatedUnix int64 CreatedUnix int64
Updated time.Time `gorm:"-" json:"-"` // Note: Updated must below Created for AfterFind. Updated time.Time `xorm:"-" json:"-" gorm:"-"` // Note: Updated must below Created for AfterSet.
UpdatedUnix int64 UpdatedUnix int64
HasRecentActivity bool `gorm:"-" json:"-"` HasRecentActivity bool `xorm:"-" json:"-" gorm:"-"`
HasUsed bool `gorm:"-" json:"-"` HasUsed bool `xorm:"-" json:"-" gorm:"-"`
} }
func (k *PublicKey) BeforeCreate(tx *gorm.DB) error { func (k *PublicKey) BeforeInsert() {
if k.CreatedUnix == 0 { k.CreatedUnix = time.Now().Unix()
k.CreatedUnix = tx.NowFunc().Unix()
}
return nil
} }
func (k *PublicKey) BeforeUpdate(tx *gorm.DB) error { func (k *PublicKey) BeforeUpdate() {
k.UpdatedUnix = tx.NowFunc().Unix() k.UpdatedUnix = time.Now().Unix()
return nil
} }
func (k *PublicKey) AfterFind(tx *gorm.DB) error { func (k *PublicKey) AfterSet(colName string, _ xorm.Cell) {
switch colName {
case "created_unix":
k.Created = time.Unix(k.CreatedUnix, 0).Local() k.Created = time.Unix(k.CreatedUnix, 0).Local()
if k.UpdatedUnix > 0 { case "updated_unix":
k.Updated = time.Unix(k.UpdatedUnix, 0).Local() k.Updated = time.Unix(k.UpdatedUnix, 0).Local()
k.HasUsed = k.Updated.After(k.Created) k.HasUsed = k.Updated.After(k.Created)
k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(tx.NowFunc()) k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(time.Now())
} }
return nil
} }
// OmitEmail returns content of public key without email address. // OmitEmail returns content of public key without email address.
@@ -178,8 +175,8 @@ func parseKeyString(content string) (string, error) {
// writeTmpKeyFile writes key content to a temporary file // writeTmpKeyFile writes key content to a temporary file
// and returns the name of that file, along with any possible errors. // and returns the name of that file, along with any possible errors.
func writeTmpKeyFile(content string) (string, error) { func writeTmpKeyFile(content, keyTestPath string) (string, error) {
tmpFile, err := os.CreateTemp(conf.SSH.KeyTestPath, "gogs_keytest") tmpFile, err := os.CreateTemp(keyTestPath, "gogs_keytest")
if err != nil { if err != nil {
return "", errors.Newf("TempFile: %v", err) return "", errors.Newf("TempFile: %v", err)
} }
@@ -191,15 +188,15 @@ func writeTmpKeyFile(content string) (string, error) {
return tmpFile.Name(), nil return tmpFile.Name(), nil
} }
// SSHKeyGenParsePublicKey extracts key type and length using ssh-keygen. // SSHKeygenParsePublicKey extracts key type and length using ssh-keygen.
func SSHKeyGenParsePublicKey(key string) (string, int, error) { func SSHKeygenParsePublicKey(key, keyTestPath, keygenPath string) (string, int, error) {
tmpName, err := writeTmpKeyFile(key) tmpName, err := writeTmpKeyFile(key, keyTestPath)
if err != nil { if err != nil {
return "", 0, errors.Newf("writeTmpKeyFile: %v", err) return "", 0, errors.Newf("writeTmpKeyFile: %v", err)
} }
defer os.Remove(tmpName) defer os.Remove(tmpName)
stdout, stderr, err := process.Exec("SSHKeyGenParsePublicKey", conf.SSH.KeygenPath, "-lf", tmpName) stdout, stderr, err := process.Exec("SSHKeygenParsePublicKey", keygenPath, "-lf", tmpName)
if err != nil { if err != nil {
return "", 0, errors.Newf("fail to parse public key: %s - %s", err, stderr) return "", 0, errors.Newf("fail to parse public key: %s - %s", err, stderr)
} }
@@ -304,8 +301,8 @@ func CheckPublicKeyString(content string) (_ string, err error) {
fnName = "SSHNativeParsePublicKey" fnName = "SSHNativeParsePublicKey"
keyType, length, err = SSHNativeParsePublicKey(content) keyType, length, err = SSHNativeParsePublicKey(content)
} else { } else {
fnName = "SSHKeyGenParsePublicKey" fnName = "SSHKeygenParsePublicKey"
keyType, length, err = SSHKeyGenParsePublicKey(content) keyType, length, err = SSHKeygenParsePublicKey(content, conf.SSH.KeyTestPath, conf.SSH.KeygenPath)
} }
if err != nil { if err != nil {
return "", errors.Newf("%s: %v", fnName, err) return "", errors.Newf("%s: %v", fnName, err)
@@ -359,16 +356,19 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
// checkKeyContent onlys checks if key content has been used as public key, // checkKeyContent onlys checks if key content has been used as public key,
// it is OK to use same key as deploy key for multiple repositories/users. // it is OK to use same key as deploy key for multiple repositories/users.
func checkKeyContent(content string) error { func checkKeyContent(content string) error {
err := db.Where("content = ? AND type = ?", content, KeyTypeUser).First(&PublicKey{}).Error has, err := x.Get(&PublicKey{
if err == nil { Content: content,
return ErrKeyAlreadyExist{0, content} Type: KeyTypeUser,
} else if !errors.Is(err, gorm.ErrRecordNotFound) { })
if err != nil {
return err return err
} else if has {
return ErrKeyAlreadyExist{0, content}
} }
return nil return nil
} }
func addKey(tx *gorm.DB, key *PublicKey) (err error) { func addKey(e Engine, key *PublicKey) (err error) {
// Calculate fingerprint. // Calculate fingerprint.
tmpPath := strings.ReplaceAll(path.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()), "id_rsa.pub"), "\\", "/") tmpPath := strings.ReplaceAll(path.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()), "id_rsa.pub"), "\\", "/")
_ = os.MkdirAll(path.Dir(tmpPath), os.ModePerm) _ = os.MkdirAll(path.Dir(tmpPath), os.ModePerm)
@@ -385,7 +385,7 @@ func addKey(tx *gorm.DB, key *PublicKey) (err error) {
key.Fingerprint = strings.Split(stdout, " ")[1] key.Fingerprint = strings.Split(stdout, " ")[1]
// Save SSH key. // Save SSH key.
if err = tx.Create(key).Error; err != nil { if _, err = e.Insert(key); err != nil {
return err return err
} }
@@ -404,10 +404,16 @@ func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) {
} }
// Key name of same user cannot be duplicated. // Key name of same user cannot be duplicated.
err := db.Where("owner_id = ? AND name = ?", ownerID, name).First(new(PublicKey)).Error has, err := x.Where("owner_id = ? AND name = ?", ownerID, name).Get(new(PublicKey))
if err == nil { if err != nil {
return nil, err
} else if has {
return nil, ErrKeyNameAlreadyUsed{ownerID, name} return nil, ErrKeyNameAlreadyUsed{ownerID, name}
} else if !errors.Is(err, gorm.ErrRecordNotFound) { }
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return nil, err return nil, err
} }
@@ -418,25 +424,21 @@ func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) {
Mode: AccessModeWrite, Mode: AccessModeWrite,
Type: KeyTypeUser, Type: KeyTypeUser,
} }
err = db.Transaction(func(tx *gorm.DB) error { if err = addKey(sess, key); err != nil {
return addKey(tx, key)
})
if err != nil {
return nil, errors.Newf("addKey: %v", err) return nil, errors.Newf("addKey: %v", err)
} }
return key, nil return key, sess.Commit()
} }
// GetPublicKeyByID returns public key by given ID. // GetPublicKeyByID returns public key by given ID.
func GetPublicKeyByID(keyID int64) (*PublicKey, error) { func GetPublicKeyByID(keyID int64) (*PublicKey, error) {
key := new(PublicKey) key := new(PublicKey)
err := db.Where("id = ?", keyID).First(key).Error has, err := x.Id(keyID).Get(key)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrKeyNotExist{keyID}
}
return nil, err return nil, err
} else if !has {
return nil, ErrKeyNotExist{keyID}
} }
return key, nil return key, nil
} }
@@ -446,12 +448,11 @@ func GetPublicKeyByID(keyID int64) (*PublicKey, error) {
// exists. // exists.
func SearchPublicKeyByContent(content string) (*PublicKey, error) { func SearchPublicKeyByContent(content string) (*PublicKey, error) {
key := new(PublicKey) key := new(PublicKey)
err := db.Where("content LIKE ?", content+"%").First(key).Error has, err := x.Where("content like ?", content+"%").Get(key)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrKeyNotExist{}
}
return nil, err return nil, err
} else if !has {
return nil, ErrKeyNotExist{}
} }
return key, nil return key, nil
} }
@@ -459,21 +460,23 @@ func SearchPublicKeyByContent(content string) (*PublicKey, error) {
// ListPublicKeys returns a list of public keys belongs to given user. // ListPublicKeys returns a list of public keys belongs to given user.
func ListPublicKeys(uid int64) ([]*PublicKey, error) { func ListPublicKeys(uid int64) ([]*PublicKey, error) {
keys := make([]*PublicKey, 0, 5) keys := make([]*PublicKey, 0, 5)
return keys, db.Where("owner_id = ?", uid).Find(&keys).Error return keys, x.Where("owner_id = ?", uid).Find(&keys)
} }
// UpdatePublicKey updates given public key. // UpdatePublicKey updates given public key.
func UpdatePublicKey(key *PublicKey) error { func UpdatePublicKey(key *PublicKey) error {
return db.Model(key).Where("id = ?", key.ID).Updates(key).Error _, err := x.Id(key.ID).AllCols().Update(key)
return err
} }
// deletePublicKeys does the actual key deletion but does not update authorized_keys file. // deletePublicKeys does the actual key deletion but does not update authorized_keys file.
func deletePublicKeys(tx *gorm.DB, keyIDs ...int64) error { func deletePublicKeys(e *xorm.Session, keyIDs ...int64) error {
if len(keyIDs) == 0 { if len(keyIDs) == 0 {
return nil return nil
} }
return tx.Where("id IN ?", keyIDs).Delete(new(PublicKey)).Error _, err := e.In("id", keyIDs).Delete(new(PublicKey))
return err
} }
// DeletePublicKey deletes SSH key information both in database and authorized_keys file. // DeletePublicKey deletes SSH key information both in database and authorized_keys file.
@@ -491,10 +494,17 @@ func DeletePublicKey(doer *User, id int64) (err error) {
return ErrKeyAccessDenied{doer.ID, key.ID, "public"} return ErrKeyAccessDenied{doer.ID, key.ID, "public"}
} }
err = db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
return deletePublicKeys(tx, id) defer sess.Close()
}) if err = sess.Begin(); err != nil {
if err != nil { return err
}
if err = deletePublicKeys(sess, id); err != nil {
return err
}
if err = sess.Commit(); err != nil {
return err return err
} }
@@ -521,24 +531,14 @@ func RewriteAuthorizedKeys() error {
} }
defer os.Remove(tmpPath) defer os.Remove(tmpPath)
// Use FindInBatches to process keys in chunks to avoid memory issues with large datasets err = x.Iterate(new(PublicKey), func(idx int, bean any) (err error) {
err = db.FindInBatches(&[]PublicKey{}, 100, func(tx *gorm.DB, batch int) error { _, err = f.WriteString((bean.(*PublicKey)).AuthorizedString())
var keys []PublicKey
if err := tx.Find(&keys).Error; err != nil {
return err return err
} })
for _, key := range keys { _ = f.Close()
if _, err := f.WriteString(key.AuthorizedString()); err != nil {
return err
}
}
return nil
}).Error
if err != nil { if err != nil {
_ = f.Close()
return err return err
} }
_ = f.Close()
if com.IsExist(fpath) { if com.IsExist(fpath) {
if err = os.Remove(fpath); err != nil { if err = os.Remove(fpath); err != nil {
@@ -562,40 +562,37 @@ func RewriteAuthorizedKeys() error {
// DeployKey represents deploy key information and its relation with repository. // DeployKey represents deploy key information and its relation with repository.
type DeployKey struct { type DeployKey struct {
ID int64 ID int64
KeyID int64 `gorm:"uniqueIndex:s;index"` KeyID int64 `xorm:"UNIQUE(s) INDEX"`
RepoID int64 `gorm:"uniqueIndex:s;index"` RepoID int64 `xorm:"UNIQUE(s) INDEX"`
Name string Name string
Fingerprint string Fingerprint string
Content string `gorm:"-" json:"-"` Content string `xorm:"-" json:"-" gorm:"-"`
Created time.Time `gorm:"-" json:"-"` Created time.Time `xorm:"-" json:"-" gorm:"-"`
CreatedUnix int64 CreatedUnix int64
Updated time.Time `gorm:"-" json:"-"` // Note: Updated must below Created for AfterFind. Updated time.Time `xorm:"-" json:"-" gorm:"-"` // Note: Updated must below Created for AfterSet.
UpdatedUnix int64 UpdatedUnix int64
HasRecentActivity bool `gorm:"-" json:"-"` HasRecentActivity bool `xorm:"-" json:"-" gorm:"-"`
HasUsed bool `gorm:"-" json:"-"` HasUsed bool `xorm:"-" json:"-" gorm:"-"`
} }
func (k *DeployKey) BeforeCreate(tx *gorm.DB) error { func (k *DeployKey) BeforeInsert() {
if k.CreatedUnix == 0 { k.CreatedUnix = time.Now().Unix()
k.CreatedUnix = tx.NowFunc().Unix()
}
return nil
} }
func (k *DeployKey) BeforeUpdate(tx *gorm.DB) error { func (k *DeployKey) BeforeUpdate() {
k.UpdatedUnix = tx.NowFunc().Unix() k.UpdatedUnix = time.Now().Unix()
return nil
} }
func (k *DeployKey) AfterFind(tx *gorm.DB) error { func (k *DeployKey) AfterSet(colName string, _ xorm.Cell) {
switch colName {
case "created_unix":
k.Created = time.Unix(k.CreatedUnix, 0).Local() k.Created = time.Unix(k.CreatedUnix, 0).Local()
if k.UpdatedUnix > 0 { case "updated_unix":
k.Updated = time.Unix(k.UpdatedUnix, 0).Local() k.Updated = time.Unix(k.UpdatedUnix, 0).Local()
k.HasUsed = k.Updated.After(k.Created) k.HasUsed = k.Updated.After(k.Created)
k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(tx.NowFunc()) k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(time.Now())
} }
return nil
} }
// GetContent gets associated public key content. // GetContent gets associated public key content.
@@ -608,28 +605,28 @@ func (k *DeployKey) GetContent() error {
return nil return nil
} }
func checkDeployKey(tx *gorm.DB, keyID, repoID int64, name string) error { func checkDeployKey(e Engine, keyID, repoID int64, name string) error {
// Note: We want error detail, not just true or false here. // Note: We want error detail, not just true or false here.
err := tx.Where("key_id = ? AND repo_id = ?", keyID, repoID).First(new(DeployKey)).Error has, err := e.Where("key_id = ? AND repo_id = ?", keyID, repoID).Get(new(DeployKey))
if err == nil { if err != nil {
return ErrDeployKeyAlreadyExist{keyID, repoID}
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
return err return err
} else if has {
return ErrDeployKeyAlreadyExist{keyID, repoID}
} }
err = tx.Where("repo_id = ? AND name = ?", repoID, name).First(new(DeployKey)).Error has, err = e.Where("repo_id = ? AND name = ?", repoID, name).Get(new(DeployKey))
if err == nil { if err != nil {
return ErrDeployKeyNameAlreadyUsed{repoID, name}
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
return err return err
} else if has {
return ErrDeployKeyNameAlreadyUsed{repoID, name}
} }
return nil return nil
} }
// addDeployKey adds new key-repo relation. // addDeployKey adds new key-repo relation.
func addDeployKey(tx *gorm.DB, keyID, repoID int64, name, fingerprint string) (*DeployKey, error) { func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string) (*DeployKey, error) {
if err := checkDeployKey(tx, keyID, repoID, name); err != nil { if err := checkDeployKey(e, keyID, repoID, name); err != nil {
return nil, err return nil, err
} }
@@ -639,14 +636,14 @@ func addDeployKey(tx *gorm.DB, keyID, repoID int64, name, fingerprint string) (*
Name: name, Name: name,
Fingerprint: fingerprint, Fingerprint: fingerprint,
} }
err := tx.Create(key).Error _, err := e.Insert(key)
return key, err return key, err
} }
// HasDeployKey returns true if public key is a deploy key of given repository. // HasDeployKey returns true if public key is a deploy key of given repository.
func HasDeployKey(keyID, repoID int64) bool { func HasDeployKey(keyID, repoID int64) bool {
err := db.Where("key_id = ? AND repo_id = ?", keyID, repoID).First(new(DeployKey)).Error has, _ := x.Where("key_id = ? AND repo_id = ?", keyID, repoID).Get(new(DeployKey))
return err == nil return has
} }
// AddDeployKey add new deploy key to database and authorized_keys file. // AddDeployKey add new deploy key to database and authorized_keys file.
@@ -660,34 +657,30 @@ func AddDeployKey(repoID int64, name, content string) (*DeployKey, error) {
Mode: AccessModeRead, Mode: AccessModeRead,
Type: KeyTypeDeploy, Type: KeyTypeDeploy,
} }
err := db.Where("content = ? AND mode = ? AND type = ?", content, AccessModeRead, KeyTypeDeploy).First(pkey).Error has, err := x.Get(pkey)
has := err == nil if err != nil {
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return nil, err
}
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return nil, err return nil, err
} }
var key *DeployKey
err = db.Transaction(func(tx *gorm.DB) error {
// First time use this deploy key. // First time use this deploy key.
if !has { if !has {
if err := addKey(tx, pkey); err != nil { if err = addKey(sess, pkey); err != nil {
return errors.Newf("addKey: %v", err) return nil, errors.Newf("addKey: %v", err)
} }
} }
var err error key, err := addDeployKey(sess, pkey.ID, repoID, name, pkey.Fingerprint)
key, err = addDeployKey(tx, pkey.ID, repoID, name, pkey.Fingerprint)
if err != nil { if err != nil {
return errors.Newf("addDeployKey: %v", err) return nil, errors.Newf("addDeployKey: %v", err)
} }
return nil return key, sess.Commit()
})
if err != nil {
return nil, err
}
return key, nil
} }
var _ errutil.NotFound = (*ErrDeployKeyNotExist)(nil) var _ errutil.NotFound = (*ErrDeployKeyNotExist)(nil)
@@ -712,12 +705,11 @@ func (ErrDeployKeyNotExist) NotFound() bool {
// GetDeployKeyByID returns deploy key by given ID. // GetDeployKeyByID returns deploy key by given ID.
func GetDeployKeyByID(id int64) (*DeployKey, error) { func GetDeployKeyByID(id int64) (*DeployKey, error) {
key := new(DeployKey) key := new(DeployKey)
err := db.Where("id = ?", id).First(key).Error has, err := x.Id(id).Get(key)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrDeployKeyNotExist{args: map[string]any{"deployKeyID": id}}
}
return nil, err return nil, err
} else if !has {
return nil, ErrDeployKeyNotExist{args: map[string]any{"deployKeyID": id}}
} }
return key, nil return key, nil
} }
@@ -728,19 +720,19 @@ func GetDeployKeyByRepo(keyID, repoID int64) (*DeployKey, error) {
KeyID: keyID, KeyID: keyID,
RepoID: repoID, RepoID: repoID,
} }
err := db.Where("key_id = ? AND repo_id = ?", keyID, repoID).First(key).Error has, err := x.Get(key)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrDeployKeyNotExist{args: map[string]any{"keyID": keyID, "repoID": repoID}}
}
return nil, err return nil, err
} else if !has {
return nil, ErrDeployKeyNotExist{args: map[string]any{"keyID": keyID, "repoID": repoID}}
} }
return key, nil return key, nil
} }
// UpdateDeployKey updates deploy key information. // UpdateDeployKey updates deploy key information.
func UpdateDeployKey(key *DeployKey) error { func UpdateDeployKey(key *DeployKey) error {
return db.Model(key).Where("id = ?", key.ID).Updates(key).Error _, err := x.Id(key.ID).AllCols().Update(key)
return err
} }
// DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed. // DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed.
@@ -769,27 +761,31 @@ func DeleteDeployKey(doer *User, id int64) error {
} }
} }
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
if err := tx.Where("id = ?", key.ID).Delete(new(DeployKey)).Error; err != nil { defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.ID(key.ID).Delete(new(DeployKey)); err != nil {
return errors.Newf("delete deploy key [%d]: %v", key.ID, err) return errors.Newf("delete deploy key [%d]: %v", key.ID, err)
} }
// Check if this is the last reference to same key content. // Check if this is the last reference to same key content.
err := tx.Where("key_id = ?", key.KeyID).First(new(DeployKey)).Error has, err := sess.Where("key_id = ?", key.KeyID).Get(new(DeployKey))
if errors.Is(err, gorm.ErrRecordNotFound) { if err != nil {
if err = deletePublicKeys(tx, key.KeyID); err != nil { return err
} else if !has {
if err = deletePublicKeys(sess, key.KeyID); err != nil {
return err return err
} }
} else if err != nil {
return err
} }
return nil return sess.Commit()
})
} }
// ListDeployKeys returns all deploy keys by given repository ID. // ListDeployKeys returns all deploy keys by given repository ID.
func ListDeployKeys(repoID int64) ([]*DeployKey, error) { func ListDeployKeys(repoID int64) ([]*DeployKey, error) {
keys := make([]*DeployKey, 0, 5) keys := make([]*DeployKey, 0, 5)
return keys, db.Where("repo_id = ?", repoID).Find(&keys).Error return keys, x.Where("repo_id = ?", repoID).Find(&keys)
} }

View File

@@ -4,13 +4,11 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gogs.io/gogs/internal/conf"
) )
func Test_SSHParsePublicKey(t *testing.T) { func TestSSHParsePublicKey(t *testing.T) {
// TODO: Refactor SSHKeyGenParsePublicKey to accept a tempPath and remove this init. tempPath := t.TempDir()
conf.MustInit("")
tests := []struct { tests := []struct {
name string name string
content string content string
@@ -53,20 +51,22 @@ func Test_SSHParsePublicKey(t *testing.T) {
expType: "ecdsa", expType: "ecdsa",
expLength: 521, expLength: 521,
}, },
{
name: "ed25519-256",
content: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICGYutovQfTewtcodVN1E1UUzMk4GQfiRI5ZoP/kTlDb nocomment",
expType: "ed25519",
expLength: 256,
},
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
typ, length, err := SSHNativeParsePublicKey(test.content) typ, length, err := SSHNativeParsePublicKey(test.content)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
assert.Equal(t, test.expType, typ) assert.Equal(t, test.expType, typ)
assert.Equal(t, test.expLength, length) assert.Equal(t, test.expLength, length)
typ, length, err = SSHKeyGenParsePublicKey(test.content) typ, length, err = SSHKeygenParsePublicKey(test.content, tempPath, "ssh-keygen")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
assert.Equal(t, test.expType, typ) assert.Equal(t, test.expType, typ)
assert.Equal(t, test.expLength, length) assert.Equal(t, test.expLength, length)
}) })

View File

@@ -8,7 +8,6 @@ import (
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
"github.com/pquerna/otp/totp" "github.com/pquerna/otp/totp"
"github.com/unknwon/com" "github.com/unknwon/com"
"gorm.io/gorm"
"gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/cryptoutil" "gogs.io/gogs/internal/cryptoutil"
@@ -38,16 +37,20 @@ func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) {
} }
// DeleteTwoFactor removes two-factor authentication token and recovery codes of given user. // DeleteTwoFactor removes two-factor authentication token and recovery codes of given user.
func DeleteTwoFactor(userID int64) error { func DeleteTwoFactor(userID int64) (err error) {
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
if err := tx.Where("user_id = ?", userID).Delete(new(TwoFactor)).Error; err != nil { defer sess.Close()
return errors.Newf("delete two-factor: %v", err) if err = sess.Begin(); err != nil {
return err
} }
if err := deleteRecoveryCodesByUserID(tx, userID); err != nil {
if _, err = sess.Where("user_id = ?", userID).Delete(new(TwoFactor)); err != nil {
return errors.Newf("delete two-factor: %v", err)
} else if err = deleteRecoveryCodesByUserID(sess, userID); err != nil {
return errors.Newf("deleteRecoveryCodesByUserID: %v", err) return errors.Newf("deleteRecoveryCodesByUserID: %v", err)
} }
return nil
}) return sess.Commit()
} }
// TwoFactorRecoveryCode represents a two-factor authentication recovery code. // TwoFactorRecoveryCode represents a two-factor authentication recovery code.
@@ -61,11 +64,12 @@ type TwoFactorRecoveryCode struct {
// GetRecoveryCodesByUserID returns all recovery codes of given user. // GetRecoveryCodesByUserID returns all recovery codes of given user.
func GetRecoveryCodesByUserID(userID int64) ([]*TwoFactorRecoveryCode, error) { func GetRecoveryCodesByUserID(userID int64) ([]*TwoFactorRecoveryCode, error) {
recoveryCodes := make([]*TwoFactorRecoveryCode, 0, 10) recoveryCodes := make([]*TwoFactorRecoveryCode, 0, 10)
return recoveryCodes, db.Where("user_id = ?", userID).Find(&recoveryCodes).Error return recoveryCodes, x.Where("user_id = ?", userID).Find(&recoveryCodes)
} }
func deleteRecoveryCodesByUserID(e *gorm.DB, userID int64) error { func deleteRecoveryCodesByUserID(e Engine, userID int64) error {
return e.Where("user_id = ?", userID).Delete(&TwoFactorRecoveryCode{}).Error _, err := e.Where("user_id = ?", userID).Delete(new(TwoFactorRecoveryCode))
return err
} }
// RegenerateRecoveryCodes regenerates new set of recovery codes for given user. // RegenerateRecoveryCodes regenerates new set of recovery codes for given user.
@@ -75,15 +79,19 @@ func RegenerateRecoveryCodes(userID int64) error {
return errors.Newf("generateRecoveryCodes: %v", err) return errors.Newf("generateRecoveryCodes: %v", err)
} }
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
if err := deleteRecoveryCodesByUserID(tx, userID); err != nil { defer sess.Close()
return errors.Newf("deleteRecoveryCodesByUserID: %v", err) if err = sess.Begin(); err != nil {
return err
} }
if err := tx.Create(recoveryCodes).Error; err != nil {
if err = deleteRecoveryCodesByUserID(sess, userID); err != nil {
return errors.Newf("deleteRecoveryCodesByUserID: %v", err)
} else if _, err = sess.Insert(recoveryCodes); err != nil {
return errors.Newf("insert new recovery codes: %v", err) return errors.Newf("insert new recovery codes: %v", err)
} }
return nil
}) return sess.Commit()
} }
type ErrTwoFactorRecoveryCodeNotFound struct { type ErrTwoFactorRecoveryCodeNotFound struct {

View File

@@ -465,7 +465,7 @@ func usersDeleteByID(t *testing.T, ctx context.Context, s *UsersStore) {
reposStore := newReposStore(s.db) reposStore := newReposStore(s.db)
t.Run("user still has repository ownership", func(t *testing.T) { t.Run("user still has repository ownership", func(t *testing.T) {
alice, err := s.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{}) alice, err := s.Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
_, err = reposStore.Create(ctx, alice.ID, CreateRepoOptions{Name: "repo1"}) _, err = reposStore.Create(ctx, alice.ID, CreateRepoOptions{Name: "repo1"})
@@ -477,7 +477,7 @@ func usersDeleteByID(t *testing.T, ctx context.Context, s *UsersStore) {
}) })
t.Run("user still has organization membership", func(t *testing.T) { t.Run("user still has organization membership", func(t *testing.T) {
bob, err := s.Create(ctx, "bob", "bob@exmaple.com", CreateUserOptions{}) bob, err := s.Create(ctx, "bob", "bob@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
// TODO: Use Orgs.Create to replace SQL hack when the method is available. // TODO: Use Orgs.Create to replace SQL hack when the method is available.
@@ -498,14 +498,14 @@ func usersDeleteByID(t *testing.T, ctx context.Context, s *UsersStore) {
assert.Equal(t, wantErr, err) assert.Equal(t, wantErr, err)
}) })
cindy, err := s.Create(ctx, "cindy", "cindy@exmaple.com", CreateUserOptions{}) cindy, err := s.Create(ctx, "cindy", "cindy@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
frank, err := s.Create(ctx, "frank", "frank@exmaple.com", CreateUserOptions{}) frank, err := s.Create(ctx, "frank", "frank@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
repo2, err := reposStore.Create(ctx, cindy.ID, CreateRepoOptions{Name: "repo2"}) repo2, err := reposStore.Create(ctx, cindy.ID, CreateRepoOptions{Name: "repo2"})
require.NoError(t, err) require.NoError(t, err)
testUser, err := s.Create(ctx, "testUser", "testUser@exmaple.com", CreateUserOptions{}) testUser, err := s.Create(ctx, "testUser", "testUser@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
// Mock watches, stars and follows // Mock watches, stars and follows
@@ -673,14 +673,14 @@ func usersDeleteByID(t *testing.T, ctx context.Context, s *UsersStore) {
func usersDeleteInactivated(t *testing.T, ctx context.Context, s *UsersStore) { func usersDeleteInactivated(t *testing.T, ctx context.Context, s *UsersStore) {
// User with repository ownership should be skipped // User with repository ownership should be skipped
alice, err := s.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{}) alice, err := s.Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
reposStore := newReposStore(s.db) reposStore := newReposStore(s.db)
_, err = reposStore.Create(ctx, alice.ID, CreateRepoOptions{Name: "repo1"}) _, err = reposStore.Create(ctx, alice.ID, CreateRepoOptions{Name: "repo1"})
require.NoError(t, err) require.NoError(t, err)
// User with organization membership should be skipped // User with organization membership should be skipped
bob, err := s.Create(ctx, "bob", "bob@exmaple.com", CreateUserOptions{}) bob, err := s.Create(ctx, "bob", "bob@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
// TODO: Use Orgs.Create to replace SQL hack when the method is available. // TODO: Use Orgs.Create to replace SQL hack when the method is available.
org1, err := s.Create(ctx, "org1", "org1@example.com", CreateUserOptions{}) org1, err := s.Create(ctx, "org1", "org1@example.com", CreateUserOptions{})
@@ -695,11 +695,11 @@ func usersDeleteInactivated(t *testing.T, ctx context.Context, s *UsersStore) {
require.NoError(t, err) require.NoError(t, err)
// User activated state should be skipped // User activated state should be skipped
_, err = s.Create(ctx, "cindy", "cindy@exmaple.com", CreateUserOptions{Activated: true}) _, err = s.Create(ctx, "cindy", "cindy@example.com", CreateUserOptions{Activated: true})
require.NoError(t, err) require.NoError(t, err)
// User meant to be deleted // User meant to be deleted
david, err := s.Create(ctx, "david", "david@exmaple.com", CreateUserOptions{}) david, err := s.Create(ctx, "david", "david@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
tempSSHRootPath := filepath.Join(os.TempDir(), "usersDeleteInactivated-tempSSHRootPath") tempSSHRootPath := filepath.Join(os.TempDir(), "usersDeleteInactivated-tempSSHRootPath")
@@ -726,7 +726,7 @@ func usersGetByEmail(t *testing.T, ctx context.Context, s *UsersStore) {
t.Run("ignore organization", func(t *testing.T) { t.Run("ignore organization", func(t *testing.T) {
// TODO: Use Orgs.Create to replace SQL hack when the method is available. // TODO: Use Orgs.Create to replace SQL hack when the method is available.
org, err := s.Create(ctx, "gogs", "gogs@exmaple.com", CreateUserOptions{}) org, err := s.Create(ctx, "gogs", "gogs@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
err = s.db.Model(&User{}).Where("id", org.ID).UpdateColumn("type", UserTypeOrganization).Error err = s.db.Model(&User{}).Where("id", org.ID).UpdateColumn("type", UserTypeOrganization).Error
@@ -738,7 +738,7 @@ func usersGetByEmail(t *testing.T, ctx context.Context, s *UsersStore) {
}) })
t.Run("by primary email", func(t *testing.T) { t.Run("by primary email", func(t *testing.T) {
alice, err := s.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{}) alice, err := s.Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
_, err = s.GetByEmail(ctx, alice.Email) _, err = s.GetByEmail(ctx, alice.Email)
@@ -760,7 +760,7 @@ func usersGetByEmail(t *testing.T, ctx context.Context, s *UsersStore) {
require.NoError(t, err) require.NoError(t, err)
// TODO: Use UserEmails.Create to replace SQL hack when the method is available. // TODO: Use UserEmails.Create to replace SQL hack when the method is available.
email2 := "bob2@exmaple.com" email2 := "bob2@example.com"
err = s.db.Exec(`INSERT INTO email_address (uid, email) VALUES (?, ?)`, bob.ID, email2).Error err = s.db.Exec(`INSERT INTO email_address (uid, email) VALUES (?, ?)`, bob.ID, email2).Error
require.NoError(t, err) require.NoError(t, err)
@@ -779,7 +779,7 @@ func usersGetByEmail(t *testing.T, ctx context.Context, s *UsersStore) {
} }
func usersGetByID(t *testing.T, ctx context.Context, s *UsersStore) { func usersGetByID(t *testing.T, ctx context.Context, s *UsersStore) {
alice, err := s.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{}) alice, err := s.Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
user, err := s.GetByID(ctx, alice.ID) user, err := s.GetByID(ctx, alice.ID)
@@ -792,7 +792,7 @@ func usersGetByID(t *testing.T, ctx context.Context, s *UsersStore) {
} }
func usersGetByUsername(t *testing.T, ctx context.Context, s *UsersStore) { func usersGetByUsername(t *testing.T, ctx context.Context, s *UsersStore) {
alice, err := s.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{}) alice, err := s.Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
user, err := s.GetByUsername(ctx, alice.Name) user, err := s.GetByUsername(ctx, alice.Name)
@@ -805,7 +805,7 @@ func usersGetByUsername(t *testing.T, ctx context.Context, s *UsersStore) {
} }
func usersGetByKeyID(t *testing.T, ctx context.Context, s *UsersStore) { func usersGetByKeyID(t *testing.T, ctx context.Context, s *UsersStore) {
alice, err := s.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{}) alice, err := s.Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
// TODO: Use PublicKeys.Create to replace SQL hack when the method is available. // TODO: Use PublicKeys.Create to replace SQL hack when the method is available.
@@ -830,11 +830,11 @@ func usersGetByKeyID(t *testing.T, ctx context.Context, s *UsersStore) {
} }
func usersGetMailableEmailsByUsernames(t *testing.T, ctx context.Context, s *UsersStore) { func usersGetMailableEmailsByUsernames(t *testing.T, ctx context.Context, s *UsersStore) {
alice, err := s.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{}) alice, err := s.Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
bob, err := s.Create(ctx, "bob", "bob@exmaple.com", CreateUserOptions{Activated: true}) bob, err := s.Create(ctx, "bob", "bob@example.com", CreateUserOptions{Activated: true})
require.NoError(t, err) require.NoError(t, err)
_, err = s.Create(ctx, "cindy", "cindy@exmaple.com", CreateUserOptions{Activated: true}) _, err = s.Create(ctx, "cindy", "cindy@example.com", CreateUserOptions{Activated: true})
require.NoError(t, err) require.NoError(t, err)
got, err := s.GetMailableEmailsByUsernames(ctx, []string{alice.Name, bob.Name, "ignore-non-exist"}) got, err := s.GetMailableEmailsByUsernames(ctx, []string{alice.Name, bob.Name, "ignore-non-exist"})

View File

@@ -14,8 +14,8 @@ import (
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
gouuid "github.com/satori/go.uuid" gouuid "github.com/satori/go.uuid"
"gorm.io/gorm"
log "unknwon.dev/clog/v2" log "unknwon.dev/clog/v2"
"xorm.io/xorm"
api "github.com/gogs/go-gogs-client" api "github.com/gogs/go-gogs-client"
@@ -95,42 +95,45 @@ type Webhook struct {
ID int64 ID int64
RepoID int64 RepoID int64
OrgID int64 OrgID int64
URL string `gorm:"type:text;column:url"` URL string `xorm:"url TEXT"`
ContentType HookContentType ContentType HookContentType
Secret string `gorm:"type:text"` Secret string `xorm:"TEXT"`
Events string `gorm:"type:text"` Events string `xorm:"TEXT"`
*HookEvent `gorm:"-"` // LEGACY [1.0]: Cannot ignore JSON (i.e. json:"-") here, it breaks old backup archive *HookEvent `xorm:"-"` // LEGACY [1.0]: Cannot ignore JSON (i.e. json:"-") here, it breaks old backup archive
IsSSL bool `gorm:"column:is_ssl"` IsSSL bool `xorm:"is_ssl"`
IsActive bool IsActive bool
HookTaskType HookTaskType HookTaskType HookTaskType
Meta string `gorm:"type:text"` // store hook-specific attributes Meta string `xorm:"TEXT"` // store hook-specific attributes
LastStatus HookStatus // Last delivery status LastStatus HookStatus // Last delivery status
Created time.Time `gorm:"-" json:"-"` Created time.Time `xorm:"-" json:"-" gorm:"-"`
CreatedUnix int64 CreatedUnix int64
Updated time.Time `gorm:"-" json:"-"` Updated time.Time `xorm:"-" json:"-" gorm:"-"`
UpdatedUnix int64 UpdatedUnix int64
} }
func (w *Webhook) BeforeCreate(tx *gorm.DB) error { func (w *Webhook) BeforeInsert() {
w.CreatedUnix = tx.NowFunc().Unix() w.CreatedUnix = time.Now().Unix()
w.UpdatedUnix = w.CreatedUnix w.UpdatedUnix = w.CreatedUnix
return nil
} }
func (w *Webhook) BeforeUpdate(tx *gorm.DB) error { func (w *Webhook) BeforeUpdate() {
w.UpdatedUnix = tx.NowFunc().Unix() w.UpdatedUnix = time.Now().Unix()
return nil
} }
func (w *Webhook) AfterFind(tx *gorm.DB) error { func (w *Webhook) AfterSet(colName string, _ xorm.Cell) {
var err error
switch colName {
case "events":
w.HookEvent = &HookEvent{} w.HookEvent = &HookEvent{}
if err := jsoniter.Unmarshal([]byte(w.Events), w.HookEvent); err != nil { if err = jsoniter.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
log.Error("Unmarshal [%d]: %v", w.ID, err) log.Error("Unmarshal [%d]: %v", w.ID, err)
} }
case "created_unix":
w.Created = time.Unix(w.CreatedUnix, 0).Local() w.Created = time.Unix(w.CreatedUnix, 0).Local()
case "updated_unix":
w.Updated = time.Unix(w.UpdatedUnix, 0).Local() w.Updated = time.Unix(w.UpdatedUnix, 0).Local()
return nil }
} }
func (w *Webhook) SlackMeta() *SlackMeta { func (w *Webhook) SlackMeta() *SlackMeta {
@@ -228,7 +231,8 @@ func (w *Webhook) EventsArray() []string {
// CreateWebhook creates a new web hook. // CreateWebhook creates a new web hook.
func CreateWebhook(w *Webhook) error { func CreateWebhook(w *Webhook) error {
return db.Create(w).Error _, err := x.Insert(w)
return err
} }
var _ errutil.NotFound = (*ErrWebhookNotExist)(nil) var _ errutil.NotFound = (*ErrWebhookNotExist)(nil)
@@ -253,12 +257,11 @@ func (ErrWebhookNotExist) NotFound() bool {
// getWebhook uses argument bean as query condition, // getWebhook uses argument bean as query condition,
// ID must be specified and do not assign unnecessary fields. // ID must be specified and do not assign unnecessary fields.
func getWebhook(bean *Webhook) (*Webhook, error) { func getWebhook(bean *Webhook) (*Webhook, error) {
err := db.Where(bean).First(bean).Error has, err := x.Get(bean)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrWebhookNotExist{args: map[string]any{"webhookID": bean.ID}}
}
return nil, err return nil, err
} else if !has {
return nil, ErrWebhookNotExist{args: map[string]any{"webhookID": bean.ID}}
} }
return bean, nil return bean, nil
} }
@@ -289,34 +292,39 @@ func GetWebhookByOrgID(orgID, id int64) (*Webhook, error) {
} }
// getActiveWebhooksByRepoID returns all active webhooks of repository. // getActiveWebhooksByRepoID returns all active webhooks of repository.
func getActiveWebhooksByRepoID(tx *gorm.DB, repoID int64) ([]*Webhook, error) { func getActiveWebhooksByRepoID(e Engine, repoID int64) ([]*Webhook, error) {
webhooks := make([]*Webhook, 0, 5) webhooks := make([]*Webhook, 0, 5)
return webhooks, tx.Where("repo_id = ? AND is_active = ?", repoID, true).Find(&webhooks).Error return webhooks, e.Where("repo_id = ?", repoID).And("is_active = ?", true).Find(&webhooks)
} }
// GetWebhooksByRepoID returns all webhooks of a repository. // GetWebhooksByRepoID returns all webhooks of a repository.
func GetWebhooksByRepoID(repoID int64) ([]*Webhook, error) { func GetWebhooksByRepoID(repoID int64) ([]*Webhook, error) {
webhooks := make([]*Webhook, 0, 5) webhooks := make([]*Webhook, 0, 5)
return webhooks, db.Where("repo_id = ?", repoID).Find(&webhooks).Error return webhooks, x.Find(&webhooks, &Webhook{RepoID: repoID})
} }
// UpdateWebhook updates information of webhook. // UpdateWebhook updates information of webhook.
func UpdateWebhook(w *Webhook) error { func UpdateWebhook(w *Webhook) error {
return db.Model(w).Where("id = ?", w.ID).Updates(w).Error _, err := x.Id(w.ID).AllCols().Update(w)
return err
} }
// deleteWebhook uses argument bean as query condition, // deleteWebhook uses argument bean as query condition,
// ID must be specified and do not assign unnecessary fields. // ID must be specified and do not assign unnecessary fields.
func deleteWebhook(bean *Webhook) error { func deleteWebhook(bean *Webhook) (err error) {
return db.Transaction(func(tx *gorm.DB) error { sess := x.NewSession()
if err := tx.Delete(bean).Error; err != nil { defer sess.Close()
if err = sess.Begin(); err != nil {
return err return err
} }
if err := tx.Where("hook_id = ?", bean.ID).Delete(&HookTask{}).Error; err != nil {
if _, err = sess.Delete(bean); err != nil {
return err
} else if _, err = sess.Delete(&HookTask{HookID: bean.ID}); err != nil {
return err return err
} }
return nil
}) return sess.Commit()
} }
// DeleteWebhookOfRepoByID deletes webhook of repository by given ID. // DeleteWebhookOfRepoByID deletes webhook of repository by given ID.
@@ -337,14 +345,14 @@ func DeleteWebhookOfOrgByID(orgID, id int64) error {
// GetWebhooksByOrgID returns all webhooks for an organization. // GetWebhooksByOrgID returns all webhooks for an organization.
func GetWebhooksByOrgID(orgID int64) (ws []*Webhook, err error) { func GetWebhooksByOrgID(orgID int64) (ws []*Webhook, err error) {
err = db.Where("org_id = ?", orgID).Find(&ws).Error err = x.Find(&ws, &Webhook{OrgID: orgID})
return ws, err return ws, err
} }
// getActiveWebhooksByOrgID returns all active webhooks for an organization. // getActiveWebhooksByOrgID returns all active webhooks for an organization.
func getActiveWebhooksByOrgID(tx *gorm.DB, orgID int64) ([]*Webhook, error) { func getActiveWebhooksByOrgID(e Engine, orgID int64) ([]*Webhook, error) {
ws := make([]*Webhook, 0, 3) ws := make([]*Webhook, 0, 3)
return ws, tx.Where("org_id = ? AND is_active = ?", orgID, true).Find(&ws).Error return ws, e.Where("org_id=?", orgID).And("is_active=?", true).Find(&ws)
} }
// ___ ___ __ ___________ __ // ___ ___ __ ___________ __
@@ -423,56 +431,64 @@ type HookResponse struct {
// HookTask represents a hook task. // HookTask represents a hook task.
type HookTask struct { type HookTask struct {
ID int64 ID int64
RepoID int64 `gorm:"index"` RepoID int64 `xorm:"INDEX"`
HookID int64 HookID int64
UUID string UUID string
Type HookTaskType Type HookTaskType
URL string `gorm:"type:text"` URL string `xorm:"TEXT"`
Signature string `gorm:"type:text"` Signature string `xorm:"TEXT"`
api.Payloader `gorm:"-" json:"-"` api.Payloader `xorm:"-" json:"-" gorm:"-"`
PayloadContent string `gorm:"type:text"` PayloadContent string `xorm:"TEXT"`
ContentType HookContentType ContentType HookContentType
EventType HookEventType EventType HookEventType
IsSSL bool IsSSL bool
IsDelivered bool IsDelivered bool
Delivered int64 Delivered int64
DeliveredString string `gorm:"-" json:"-"` DeliveredString string `xorm:"-" json:"-" gorm:"-"`
// History info. // History info.
IsSucceed bool IsSucceed bool
RequestContent string `gorm:"type:text"` RequestContent string `xorm:"TEXT"`
RequestInfo *HookRequest `gorm:"-" json:"-"` RequestInfo *HookRequest `xorm:"-" json:"-" gorm:"-"`
ResponseContent string `gorm:"type:text"` ResponseContent string `xorm:"TEXT"`
ResponseInfo *HookResponse `gorm:"-" json:"-"` ResponseInfo *HookResponse `xorm:"-" json:"-" gorm:"-"`
} }
func (t *HookTask) BeforeUpdate(tx *gorm.DB) error { func (t *HookTask) BeforeUpdate() {
if t.RequestInfo != nil { if t.RequestInfo != nil {
t.RequestContent = t.ToJSON(t.RequestInfo) t.RequestContent = t.ToJSON(t.RequestInfo)
} }
if t.ResponseInfo != nil { if t.ResponseInfo != nil {
t.ResponseContent = t.ToJSON(t.ResponseInfo) t.ResponseContent = t.ToJSON(t.ResponseInfo)
} }
return nil
} }
func (t *HookTask) AfterFind(tx *gorm.DB) error { func (t *HookTask) AfterSet(colName string, _ xorm.Cell) {
var err error
switch colName {
case "delivered":
t.DeliveredString = time.Unix(0, t.Delivered).Format("2006-01-02 15:04:05 MST") t.DeliveredString = time.Unix(0, t.Delivered).Format("2006-01-02 15:04:05 MST")
if t.RequestContent != "" { case "request_content":
t.RequestInfo = &HookRequest{} if t.RequestContent == "" {
if err := jsoniter.Unmarshal([]byte(t.RequestContent), t.RequestInfo); err != nil { return
log.Error("Unmarshal[%d]: %v", t.ID, err) }
}
t.RequestInfo = &HookRequest{}
if err = jsoniter.Unmarshal([]byte(t.RequestContent), t.RequestInfo); err != nil {
log.Error("Unmarshal[%d]: %v", t.ID, err)
}
case "response_content":
if t.ResponseContent == "" {
return
} }
if t.ResponseContent != "" {
t.ResponseInfo = &HookResponse{} t.ResponseInfo = &HookResponse{}
if err := jsoniter.Unmarshal([]byte(t.ResponseContent), t.ResponseInfo); err != nil { if err = jsoniter.Unmarshal([]byte(t.ResponseContent), t.ResponseInfo); err != nil {
log.Error("Unmarshal [%d]: %v", t.ID, err) log.Error("Unmarshal [%d]: %v", t.ID, err)
} }
} }
return nil
} }
func (t *HookTask) ToJSON(v any) string { func (t *HookTask) ToJSON(v any) string {
@@ -486,19 +502,20 @@ func (t *HookTask) ToJSON(v any) string {
// HookTasks returns a list of hook tasks by given conditions. // HookTasks returns a list of hook tasks by given conditions.
func HookTasks(hookID int64, page int) ([]*HookTask, error) { func HookTasks(hookID int64, page int) ([]*HookTask, error) {
tasks := make([]*HookTask, 0, conf.Webhook.PagingNum) tasks := make([]*HookTask, 0, conf.Webhook.PagingNum)
return tasks, db.Where("hook_id = ?", hookID).Order("id DESC").Limit(conf.Webhook.PagingNum).Offset((page - 1) * conf.Webhook.PagingNum).Find(&tasks).Error return tasks, x.Limit(conf.Webhook.PagingNum, (page-1)*conf.Webhook.PagingNum).Where("hook_id=?", hookID).Desc("id").Find(&tasks)
} }
// createHookTask creates a new hook task, // createHookTask creates a new hook task,
// it handles conversion from Payload to PayloadContent. // it handles conversion from Payload to PayloadContent.
func createHookTask(tx *gorm.DB, t *HookTask) error { func createHookTask(e Engine, t *HookTask) error {
data, err := t.JSONPayload() data, err := t.JSONPayload()
if err != nil { if err != nil {
return err return err
} }
t.UUID = gouuid.NewV4().String() t.UUID = gouuid.NewV4().String()
t.PayloadContent = string(data) t.PayloadContent = string(data)
return tx.Create(t).Error _, err = e.Insert(t)
return err
} }
var _ errutil.NotFound = (*ErrHookTaskNotExist)(nil) var _ errutil.NotFound = (*ErrHookTaskNotExist)(nil)
@@ -526,23 +543,23 @@ func GetHookTaskOfWebhookByUUID(webhookID int64, uuid string) (*HookTask, error)
HookID: webhookID, HookID: webhookID,
UUID: uuid, UUID: uuid,
} }
err := db.Where("hook_id = ? AND uuid = ?", webhookID, uuid).First(hookTask).Error has, err := x.Get(hookTask)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrHookTaskNotExist{args: map[string]any{"webhookID": webhookID, "uuid": uuid}}
}
return nil, err return nil, err
} else if !has {
return nil, ErrHookTaskNotExist{args: map[string]any{"webhookID": webhookID, "uuid": uuid}}
} }
return hookTask, nil return hookTask, nil
} }
// UpdateHookTask updates information of hook task. // UpdateHookTask updates information of hook task.
func UpdateHookTask(t *HookTask) error { func UpdateHookTask(t *HookTask) error {
return db.Model(t).Where("id = ?", t.ID).Updates(t).Error _, err := x.Id(t.ID).AllCols().Update(t)
return err
} }
// prepareHookTasks adds list of webhooks to task queue. // prepareHookTasks adds list of webhooks to task queue.
func prepareHookTasks(tx *gorm.DB, repo *Repository, event HookEventType, p api.Payloader, webhooks []*Webhook) (err error) { func prepareHookTasks(e Engine, repo *Repository, event HookEventType, p api.Payloader, webhooks []*Webhook) (err error) {
if len(webhooks) == 0 { if len(webhooks) == 0 {
return nil return nil
} }
@@ -616,7 +633,7 @@ func prepareHookTasks(tx *gorm.DB, repo *Repository, event HookEventType, p api.
signature = hex.EncodeToString(sig.Sum(nil)) signature = hex.EncodeToString(sig.Sum(nil))
} }
if err = createHookTask(tx, &HookTask{ if err = createHookTask(e, &HookTask{
RepoID: repo.ID, RepoID: repo.ID,
HookID: w.ID, HookID: w.ID,
Type: w.HookTaskType, Type: w.HookTaskType,
@@ -638,32 +655,32 @@ func prepareHookTasks(tx *gorm.DB, repo *Repository, event HookEventType, p api.
return nil return nil
} }
func prepareWebhooks(tx *gorm.DB, repo *Repository, event HookEventType, p api.Payloader) error { func prepareWebhooks(e Engine, repo *Repository, event HookEventType, p api.Payloader) error {
webhooks, err := getActiveWebhooksByRepoID(tx, repo.ID) webhooks, err := getActiveWebhooksByRepoID(e, repo.ID)
if err != nil { if err != nil {
return errors.Newf("getActiveWebhooksByRepoID [%d]: %v", repo.ID, err) return errors.Newf("getActiveWebhooksByRepoID [%d]: %v", repo.ID, err)
} }
// check if repo belongs to org and append additional webhooks // check if repo belongs to org and append additional webhooks
if repo.mustOwner(tx).IsOrganization() { if repo.mustOwner(e).IsOrganization() {
// get hooks for org // get hooks for org
orgws, err := getActiveWebhooksByOrgID(tx, repo.OwnerID) orgws, err := getActiveWebhooksByOrgID(e, repo.OwnerID)
if err != nil { if err != nil {
return errors.Newf("getActiveWebhooksByOrgID [%d]: %v", repo.OwnerID, err) return errors.Newf("getActiveWebhooksByOrgID [%d]: %v", repo.OwnerID, err)
} }
webhooks = append(webhooks, orgws...) webhooks = append(webhooks, orgws...)
} }
return prepareHookTasks(tx, repo, event, p, webhooks) return prepareHookTasks(e, repo, event, p, webhooks)
} }
// PrepareWebhooks adds all active webhooks to task queue. // PrepareWebhooks adds all active webhooks to task queue.
func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) error { func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) error {
// NOTE: To prevent too many cascading changes in a single refactoring PR, we // NOTE: To prevent too many cascading changes in a single refactoring PR, we
// choose to ignore this function in tests. // choose to ignore this function in tests.
if db == nil && testutil.InTest { if x == nil && testutil.InTest {
return nil return nil
} }
return prepareWebhooks(db, repo, event, p) return prepareWebhooks(x, repo, event, p)
} }
// TestWebhook adds the test webhook matches the ID to task queue. // TestWebhook adds the test webhook matches the ID to task queue.
@@ -672,7 +689,7 @@ func TestWebhook(repo *Repository, event HookEventType, p api.Payloader, webhook
if err != nil { if err != nil {
return errors.Newf("GetWebhookOfRepoByID [repo_id: %d, id: %d]: %v", repo.ID, webhookID, err) return errors.Newf("GetWebhookOfRepoByID [repo_id: %d, id: %d]: %v", repo.ID, webhookID, err)
} }
return prepareHookTasks(db, repo, event, p, []*Webhook{webhook}) return prepareHookTasks(x, repo, event, p, []*Webhook{webhook})
} }
func (t *HookTask) deliver() { func (t *HookTask) deliver() {
@@ -767,17 +784,20 @@ func (t *HookTask) deliver() {
// TODO: shoot more hooks at same time. // TODO: shoot more hooks at same time.
func DeliverHooks() { func DeliverHooks() {
tasks := make([]*HookTask, 0, 10) tasks := make([]*HookTask, 0, 10)
err := db.Where("is_delivered = ?", false).Find(&tasks).Error _ = x.Where("is_delivered = ?", false).Iterate(new(HookTask),
if err != nil { func(idx int, bean any) error {
log.Error("Find undelivered hook tasks: %v", err) t := bean.(*HookTask)
} else {
for _, t := range tasks {
t.deliver() t.deliver()
tasks = append(tasks, t)
return nil
})
// Update hook task status.
for _, t := range tasks {
if err := UpdateHookTask(t); err != nil { if err := UpdateHookTask(t); err != nil {
log.Error("UpdateHookTask [%d]: %v", t.ID, err) log.Error("UpdateHookTask [%d]: %v", t.ID, err)
} }
} }
}
// Start listening on new hook requests. // Start listening on new hook requests.
for repoID := range HookQueue.Queue() { for repoID := range HookQueue.Queue() {
@@ -785,7 +805,7 @@ func DeliverHooks() {
HookQueue.Remove(repoID) HookQueue.Remove(repoID)
tasks = make([]*HookTask, 0, 5) tasks = make([]*HookTask, 0, 5)
if err := db.Where("repo_id = ? AND is_delivered = ?", repoID, false).Find(&tasks).Error; err != nil { if err := x.Where("repo_id = ?", repoID).And("is_delivered = ?", false).Find(&tasks); err != nil {
log.Error("Get repository [%s] hook tasks: %v", repoID, err) log.Error("Get repository [%s] hook tasks: %v", repoID, err)
continue continue
} }

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