Compare commits

..

29 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
074714d61f Fix lint and MySQL test failures: remove unused function and fix UUID column type
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 16:53:49 +00:00
copilot-swe-agent[bot]
68f049ad15 Replace XORM with GORM while maintaining functionality
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 16:20:20 +00:00
copilot-swe-agent[bot]
ec5eede78f Fix code review issues: FindInBatches usage and memory optimization
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 16:17:07 +00:00
copilot-swe-agent[bot]
06b85ac5ba Convert remaining files from XORM to GORM and fix unused imports
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 16:11:35 +00:00
copilot-swe-agent[bot]
c74b48bd2d Convert repo_branch.go and repo_collaboration.go from XORM to GORM
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 16:10:14 +00:00
copilot-swe-agent[bot]
e89db8393d Fix transaction scope issues in repo.go UpdateRepository and DeleteRepository
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 16:03:10 +00:00
copilot-swe-agent[bot]
653c95c842 Complete XORM to GORM migration for issue.go, pull.go, and repo.go
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 16:01:10 +00:00
deepsource-autofix[bot]
dfc768b968 style: format code with Go fmt and Gofumpt
This commit fixes the style issues introduced in 63221df according to the output
from Go fmt and Gofumpt.

Details: https://github.com/gogs/gogs/pull/8105
2026-01-24 16:00:41 +00:00
copilot-swe-agent[bot]
63221dfec3 Complete XORM to GORM migration for internal/database/repo.go
- Replace import "xorm.io/xorm" with "gorm.io/gorm"
- Replace all Engine parameters with *gorm.DB
- Replace *xorm.Session parameters with *gorm.DB
- Convert all database queries from XORM to GORM syntax:
  - e.ID(id).Get(&model) → db.First(&model, id).Error
  - e.Insert(&model) → db.Create(&model).Error
  - e.Update(&model) → db.Updates(&model).Error
  - e.In("col", values) → db.Where("col IN ?", values)
  - e.Find(&results) → db.Find(&results).Error
  - e.Iterate() → db.FindInBatches()
- Convert transaction patterns from NewSession/Begin/Commit to db.Transaction()
- Replace global x variable with db
- Convert XORM AfterSet callback to GORM AfterFind hook
- Handle gorm.ErrRecordNotFound for missing records
- Fix related files: repositories.go, repo_collaboration.go, two_factor.go

Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 16:00:27 +00:00
deepsource-autofix[bot]
d2ff1fa978 style: format code with Go fmt and Gofumpt
This commit fixes the style issues introduced in 748ecbf according to the output
from Go fmt and Gofumpt.

Details: https://github.com/gogs/gogs/pull/8105
2026-01-24 15:48:04 +00:00
copilot-swe-agent[bot]
748ecbfdf3 Complete XORM to GORM migration for internal/database/pull.go
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 15:47:50 +00:00
copilot-swe-agent[bot]
04d063c65d Complete XORM to GORM migration for issue.go and pull.go
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 15:43:22 +00:00
copilot-swe-agent[bot]
eebe4065c6 Migrate internal/database/backup.go from XORM to GORM
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 01:21:38 +00:00
copilot-swe-agent[bot]
de9f03d533 Migrate webhook.go, org_team.go, ssh_key.go from XORM to GORM
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 01:17:45 +00:00
copilot-swe-agent[bot]
8d43893af2 Migrate internal/database/ssh_key.go from XORM to GORM
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 01:15:23 +00:00
copilot-swe-agent[bot]
233f0fb18d Migrate internal/database/org_team.go from XORM to GORM
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 01:07:23 +00:00
copilot-swe-agent[bot]
aba381e7f8 Migrate webhook.go from XORM to GORM
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 01:00:42 +00:00
deepsource-autofix[bot]
1a285ca5ce style: format code with Go fmt and Gofumpt
This commit fixes the style issues introduced in b74ce2f according to the output
from Go fmt and Gofumpt.

Details: https://github.com/gogs/gogs/pull/8105
2026-01-24 00:52:55 +00:00
copilot-swe-agent[bot]
b74ce2f6bc Migrate org.go from XORM to GORM
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 00:52:43 +00:00
deepsource-autofix[bot]
5469f017bc style: format code with Go fmt and Gofumpt
This commit fixes the style issues introduced in cddfd5f according to the output
from Go fmt and Gofumpt.

Details: https://github.com/gogs/gogs/pull/8105
2026-01-24 00:51:36 +00:00
copilot-swe-agent[bot]
cddfd5fbb9 Migrate issue_label.go and comment.go from XORM to GORM
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 00:51:00 +00:00
deepsource-autofix[bot]
9bd5123bcf style: format code with Go fmt and Gofumpt
This commit fixes the style issues introduced in 589bf5f according to the output
from Go fmt and Gofumpt.

Details: https://github.com/gogs/gogs/pull/8105
2026-01-24 00:47:06 +00:00
copilot-swe-agent[bot]
589bf5f532 Convert release.go from XORM to GORM
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 00:46:54 +00:00
copilot-swe-agent[bot]
9d9175df80 Convert mirror.go from XORM to GORM
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 00:45:29 +00:00
deepsource-autofix[bot]
e3e107f95d style: format code with Go fmt and Gofumpt
This commit fixes the style issues introduced in 36833b1 according to the output
from Go fmt and Gofumpt.

Details: https://github.com/gogs/gogs/pull/8105
2026-01-24 00:44:38 +00:00
copilot-swe-agent[bot]
36833b135e Convert milestone.go from XORM to GORM
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 00:44:21 +00:00
deepsource-autofix[bot]
d5954c42eb style: format code with Go fmt and Gofumpt
This commit fixes the style issues introduced in f9478a3 according to the output
from Go fmt and Gofumpt.

Details: https://github.com/gogs/gogs/pull/8105
2026-01-24 00:43:07 +00:00
copilot-swe-agent[bot]
f9478a3f76 Initial XORM to GORM migration: models.go and attachment.go converted
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
2026-01-24 00:42:51 +00:00
copilot-swe-agent[bot]
9bfb9a719b Initial plan 2026-01-24 00:34:38 +00:00
139 changed files with 3246 additions and 4104 deletions

View File

@@ -1,7 +1,6 @@
--- ---
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
--- ---
@@ -14,33 +13,40 @@ 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/f1102a7a7c545ec221d2906f02fa19170d96f96d) to include entries for the current minor release. - [ ] [Update CHANGELOG](https://github.com/gogs/gogs/commit/540134d4436d8da82247dd2cabe9312ca2f5b1f1) to include entries for the current minor release.
- Do not forget adding entries for GHSA patches. - [ ] Cut a new release branch `release/<MAJOR>.<MINOR>`, e.g. `release/0.12`.
- [ ] 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/f0e3cd90f8d7695960eeef2e4e54b2e717302f6c) to the current release, e.g. `0.14.0+dev` -> `0.14.0`. - [ ] [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`.
- [ ] 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.14.0-rc.1`, `v0.14.0-rc.2`) ⚠️ **on the release branch** ⚠️ and ensure Docker and release workflows both succeed. - [ ] 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**.
- [ ] 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.
- [ ] 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`. - [ ] 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**.
- [ ] Upload all archives and `checksum_sha256.txt` to https://dl.gogs.io. - [ ] [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).
- [ ] [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.
- [ ] Close the milestone for the minor release. - [ ] Publish a new release article on [OSChina](http://my.oschina.net/Obahua/admin/releases).
- [ ] [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`. - [ ] Close the minor milestone.
- [ ] [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,7 +1,6 @@
--- ---
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
--- ---
@@ -14,7 +13,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/f1102a7a7c545ec221d2906f02fa19170d96f96d) to include entries for the current patch release. - [ ] [Update CHANGELOG on the `main` branch](https://github.com/gogs/gogs/commit/e6c5633f580399c8f4dfc07166a63a01c6c70346) to include entries for the current patch release.
## During release ## During release
@@ -22,18 +21,28 @@ 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`) ⚠️ **on the release branch** ⚠️ and 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`) to ensure Docker workflow succeeds.
- [ ] 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. - ⚠️ **Make sure the tag is created on the release branch**.
- [ ] Download one of the release archives and run through application setup 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) ⚠️ **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. - [ ] 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.
- ⚠️ **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 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). - [ ] [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).
- 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 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`. - [ ] [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).
- [ ] Upload all archives and `checksum_sha256.txt` to https://dl.gogs.io. - [ ] [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_`):
- [ ] 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
@@ -45,5 +54,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 milestone for the patch release. - [ ] Close the patch milestone.
- [ ] **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,8 +19,18 @@ 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: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1 uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0
if: ${{ failure() }} if: ${{ failure() }}
with: with:
smtp_username: ${{ secrets.SMTP_USERNAME }} server_address: smtp.mailgun.org
smtp_password: ${{ secrets.SMTP_PASSWORD }} server_port: 465
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,11 +68,21 @@ 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: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1 uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0
if: ${{ failure() }} if: ${{ failure() }}
with: with:
smtp_username: ${{ secrets.SMTP_USERNAME }} server_address: smtp.mailgun.org
smtp_password: ${{ secrets.SMTP_PASSWORD }} server_port: 465
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' }}
@@ -135,11 +145,21 @@ 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: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1 uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0
if: ${{ failure() }} if: ${{ failure() }}
with: with:
smtp_username: ${{ secrets.SMTP_USERNAME }} server_address: smtp.mailgun.org
smtp_password: ${{ secrets.SMTP_PASSWORD }} server_port: 465
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' }}
@@ -161,11 +181,21 @@ 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: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1 uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0
if: ${{ failure() }} if: ${{ failure() }}
with: with:
smtp_username: ${{ secrets.SMTP_USERNAME }} server_address: smtp.mailgun.org
smtp_password: ${{ secrets.SMTP_PASSWORD }} server_port: 465
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'}}
@@ -255,25 +285,8 @@ jobs:
contents: read contents: read
packages: write packages: write
steps: steps:
- name: Compute image tags - name: Compute image tag name
run: | run: echo "IMAGE_TAG=$(echo $GITHUB_REF_NAME | cut -c 2-)" >> $GITHUB_ENV
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
@@ -307,13 +320,25 @@ jobs:
context: . context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7 platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true push: true
tags: ${{ env.TAGS }} tags: |
gogs/gogs:${{ env.IMAGE_TAG }}
ghcr.io/gogs/gogs:${{ env.IMAGE_TAG }}
- name: Send email on failure - name: Send email on failure
uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1 uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0
if: ${{ failure() }} if: ${{ failure() }}
with: with:
smtp_username: ${{ secrets.SMTP_USERNAME }} server_address: smtp.mailgun.org
smtp_password: ${{ secrets.SMTP_PASSWORD }} server_port: 465
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:
@@ -324,25 +349,8 @@ jobs:
contents: read contents: read
packages: write packages: write
steps: steps:
- name: Compute image tags - name: Compute image tag name
run: | run: echo "IMAGE_TAG=$(echo $GITHUB_REF_NAME | cut -c 2-)" >> $GITHUB_ENV
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
@@ -377,13 +385,25 @@ 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: ${{ env.TAGS }} 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: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1 uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0
if: ${{ failure() }} if: ${{ failure() }}
with: with:
smtp_username: ${{ secrets.SMTP_USERNAME }} server_address: smtp.mailgun.org
smtp_password: ${{ secrets.SMTP_PASSWORD }} server_port: 465
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,16 +72,28 @@ jobs:
with: with:
go-version: ${{ matrix.go-version }} go-version: ${{ matrix.go-version }}
- name: Run tests with coverage - name: Run tests with coverage
run: | run: go test -shuffle=on -v -race -coverprofile=coverage -covermode=atomic ./...
go test -shuffle=on -v -race -coverprofile=coverage -covermode=atomic -json ./... > test-report.json - name: Upload coverage report to Codecov
go install github.com/mfridman/tparse@latest uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
tparse -all -file=test-report.json with:
file: ./coverage
flags: unittests
- name: Send email on failure - name: Send email on failure
uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1 uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0
if: ${{ failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' }} if: ${{ failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
with: with:
smtp_username: ${{ secrets.SMTP_USERNAME }} server_address: smtp.mailgun.org
smtp_password: ${{ secrets.SMTP_PASSWORD }} server_port: 465
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.
@@ -101,12 +113,27 @@ 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: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1 uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0
if: ${{ failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' }} if: ${{ failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
with: with:
smtp_username: ${{ secrets.SMTP_USERNAME }} server_address: smtp.mailgun.org
smtp_password: ${{ secrets.SMTP_PASSWORD }} server_port: 465
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
@@ -135,10 +162,7 @@ jobs:
with: with:
go-version: ${{ matrix.go-version }} go-version: ${{ matrix.go-version }}
- name: Run tests with coverage - name: Run tests with coverage
run: | run: go test -shuffle=on -v -race -coverprofile=coverage -covermode=atomic ./internal/database/...
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
@@ -164,13 +188,29 @@ jobs:
with: with:
go-version: ${{ matrix.go-version }} go-version: ${{ matrix.go-version }}
- name: Run tests with coverage - name: Run tests with coverage
run: | run: go test -shuffle=on -v -race -coverprofile=coverage -covermode=atomic ./internal/database/...
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

View File

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

View File

@@ -1,7 +1,6 @@
## Core principles ## Core principles
- 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 god damn pattern in the surrounding context!
- 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
@@ -16,17 +15,6 @@ 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,38 +2,26 @@
All notable changes to Gogs are documented in this file. All notable changes to Gogs are documented in this file.
## 0.15.0+dev (`main`) ## 0.14.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.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) - 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)
### 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

View File

@@ -1 +0,0 @@
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/.bin/gogs . COPY --from=binarybuilder /gogs.io/gogs/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 --noproxy localhost -o /dev/null -sS http://localhost:3000/healthcheck) || exit 1 HEALTHCHECK CMD (curl -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/.bin/gogs . COPY --from=binarybuilder /gogs.io/gogs/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 --noproxy localhost -o /dev/null -sS http://localhost:3000/healthcheck) || exit 1 HEALTHCHECK CMD (curl -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 (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. Only the latest minor version releases are supported (>= 0.13) for accepting vulnerability reports and patching fixes.
Existing vulnerability reports are being tracked in [GitHub Security Advisories](https://github.com/gogs/gogs/security/advisories). Not all accepted GHSA are published. Existing vulnerability reports are being tracked in [GitHub Security Advisories](https://github.com/gogs/gogs/security/advisories).
## Vulnerability lifecycle ## Vulnerability lifecycle
@@ -14,7 +14,6 @@ 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,10 +10,8 @@ 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:
- .bin/gogs web - ./gogs web
build: build:
desc: Build the binary desc: Build the binary
@@ -24,7 +22,7 @@ tasks:
-X "{{.PKG_PATH}}.BuildCommit={{.BUILD_COMMIT}}" -X "{{.PKG_PATH}}.BuildCommit={{.BUILD_COMMIT}}"
' '
-tags '{{.TAGS}}' -tags '{{.TAGS}}'
-trimpath -o .bin/gogs{{.BINARY_EXT}} ./cmd/gogs -trimpath -o gogs{{.BINARY_EXT}}
vars: vars:
PKG_PATH: gogs.io/gogs/internal/conf PKG_PATH: gogs.io/gogs/internal/conf
BUILD_TIME: BUILD_TIME:
@@ -33,7 +31,7 @@ tasks:
sh: git rev-parse HEAD sh: git rev-parse HEAD
sources: sources:
- go.mod - go.mod
- cmd/gogs/*.go - gogs.go
- internal/**/*.go - internal/**/*.go
- conf/**/* - conf/**/*
- public/**/* - public/**/*
@@ -61,6 +59,18 @@ 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:
@@ -89,8 +99,3 @@ tasks:
dropdb "$dbname" dropdb "$dbname"
echo "dropped $dbname" echo "dropped $dbname"
done done
lint:
desc: Run all linters
cmds:
- golangci-lint run

16
codecov.yml Normal file
View File

@@ -0,0 +1,16 @@
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,8 +494,6 @@ 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,7 +1274,6 @@ 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,8 +494,6 @@ 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
@@ -1276,7 +1274,6 @@ 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,8 +494,6 @@ 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
@@ -1276,7 +1274,6 @@ 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,8 +501,6 @@ 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
@@ -1348,7 +1346,6 @@ 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,8 +494,6 @@ 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,8 +494,6 @@ 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
@@ -1276,7 +1274,6 @@ 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,8 +496,6 @@ 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=بارگذاری پرونده
@@ -1278,7 +1276,6 @@ 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,8 +494,6 @@ 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
@@ -1276,7 +1274,6 @@ 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,8 +494,6 @@ 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
@@ -1276,7 +1274,6 @@ 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,8 +495,6 @@ 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
@@ -1277,7 +1275,6 @@ 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,8 +494,6 @@ 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
@@ -1276,7 +1274,6 @@ 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,8 +494,6 @@ 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
@@ -1276,7 +1274,6 @@ 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,8 +494,6 @@ 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
@@ -1276,7 +1274,6 @@ 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,8 +494,6 @@ 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,7 +1274,6 @@ 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,8 +495,6 @@ 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=파일 업로드
@@ -1278,7 +1276,6 @@ 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,8 +494,6 @@ 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
@@ -1276,7 +1274,6 @@ 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,8 +494,6 @@ 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=Файл хуулах
@@ -1276,7 +1274,6 @@ 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,8 +494,6 @@ 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
@@ -1276,7 +1274,6 @@ 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,8 +494,6 @@ 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
@@ -1276,7 +1274,6 @@ 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,8 +494,6 @@ 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
@@ -1276,7 +1274,6 @@ 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=Clonar endereço resolvido para um endereço de rede local implicitamente bloqueado. migrate.clone_address_resolved_to_blocked_local_address=Clone address resolved to a local network address that is implicitly blocked.
migrate.failed=Migração falhada: %v migrate.failed=Migração falhada: %v
mirror_from=mirror de mirror_from=mirror de
@@ -494,8 +494,6 @@ 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
@@ -1204,120 +1202,119 @@ 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=Verificação de tamanho mínimo da chave config.ssh.minimum_key_size_check=Minimum key size check
config.ssh.minimum_key_sizes=Tamanhos mínimos de chaves config.ssh.minimum_key_sizes=Minimum key sizes
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=Iniciar servidor embutido config.ssh.start_builtin_server=Start builtin server
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=MACs do servidor config.ssh.server_macs=Server MACs
config.ssh.server_algorithms=Algoritmos do servidor config.ssh.server_algorithms=Server algorithms
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=Forçar privado config.repo.force_private=Force private
config.repo.max_creation_limit=Limite máximo de criação config.repo.max_creation_limit=Max creation limit
config.repo.preferred_licenses=Licenças preferidas config.repo.preferred_licenses=Preferred licenses
config.repo.disable_http_git=Desativar Git HTTP config.repo.disable_http_git=Disable HTTP Git
config.repo.enable_local_path_migration=Ativar a migração de caminho local config.repo.enable_local_path_migration=Enable local path migration
config.repo.enable_raw_file_render_mode=Ativar o modo de renderização do ficheiro bruto config.repo.enable_raw_file_render_mode=Enable raw file render mode
config.repo.commits_fetch_concurrency=Commits fetch concurrency config.repo.commits_fetch_concurrency=Commits fetch concurrency
config.repo.editor.line_wrap_extensions=Extensões de quebra automática de linha do editor config.repo.editor.line_wrap_extensions=Editor line wrap extensions
config.repo.editor.previewable_file_modes=Editor previewable file modes config.repo.editor.previewable_file_modes=Editor previewable file modes
config.repo.upload.enabled=Envio ativado config.repo.upload.enabled=Upload enabled
config.repo.upload.temp_path=Caminho temporário para envios config.repo.upload.temp_path=Upload temporary path
config.repo.upload.allowed_types=Tipos de envios permitidos config.repo.upload.allowed_types=Upload allowed types
config.repo.upload.file_max_size=Tamanho limite de ficheiros enviados config.repo.upload.file_max_size=Upload file size limit
config.repo.upload.max_files=Quantidade limite de ficheiros enviados config.repo.upload.max_files=Upload files limit
config.db_config=Configuração da base de dados config.db_config=Configuração da base de dados
config.db.type=Tipo config.db.type=Type
config.db.host=Anfitrião config.db.host=Host
config.db.name=Nome config.db.name=Name
config.db.schema=Esquema config.db.schema=Schema
config.db.schema_helper=(apenas para "postgres") config.db.schema_helper=(for "postgres" only)
config.db.user=Utilizador config.db.user=User
config.db.ssl_mode=Modo SSL config.db.ssl_mode=SSL mode
config.db.ssl_mode_helper=(apenas para "postgres") config.db.ssl_mode_helper=(for "postgres" only)
config.db.path=Caminho config.db.path=Path
config.db.path_helper=(apenas para "sqlite3") config.db.path_helper=(for "sqlite3"only)
config.db.max_open_conns=Máximo de conexões abertas config.db.max_open_conns=Maximum open connections
config.db.max_idle_conns=Máximo de conexões ociosas config.db.max_idle_conns=Maximum idle connections
config.security_config=Configuração da segurança config.security_config=Security configuration
config.security.login_remember_days=Dias lembrados de login config.security.login_remember_days=Login remember days
config.security.cookie_remember_name=Lembrar do cookie config.security.cookie_remember_name=Remember cookie
config.security.cookie_username=Cookie do nome do utilizador config.security.cookie_username=Username cookie
config.security.cookie_secure=Ativar cookie seguro config.security.cookie_secure=Enable secure cookie
config.security.reverse_proxy_auth_user=Cabeçalho de autenticação de proxy reverso config.security.reverse_proxy_auth_user=Reverse proxy authentication header
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=Configuração de E-mail config.email_config=Email configuration
config.email.enabled=Ativado config.email.enabled=Enabled
config.email.subject_prefix=Prefixo do assunto config.email.subject_prefix=Subject prefix
config.email.host=Anfitrião config.email.host=Host
config.email.from=De config.email.from=From
config.email.user=Utilizador config.email.user=User
config.email.disable_helo=Desativar HELO config.email.disable_helo=Disable HELO
config.email.helo_hostname=Nome de anfitrião HELO config.email.helo_hostname=HELO hostname
config.email.skip_verify=Skip certificate verify config.email.skip_verify=Skip certificate verify
config.email.use_certificate=Usar certificado personalizado config.email.use_certificate=Use custom certificate
config.email.cert_file=Ficheiro de certificado criptográfico config.email.cert_file=Certificate file
config.email.key_file=Ficheiro da chave criptográfica config.email.key_file=Key file
config.email.use_plain_text=Usar texto simples config.email.use_plain_text=Use plain text
config.email.add_plain_text_alt=Adicionar alternativa de texto simples config.email.add_plain_text_alt=Add plain text alternative
config.email.send_test_mail=Enviar e-mail de teste config.email.send_test_mail=Send test email
config.email.test_mail_failed=Falhou o envio do e-mail de teste para '%s': %v config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=O e-mail de teste foi enviado para '%s'. config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Configuração da autenticação 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=Exigir confirmação por e-mail config.auth.require_email_confirm=Require email confirmation
config.auth.require_sign_in_view=Exigir login para ver config.auth.require_sign_in_view=Require sign in view
config.auth.disable_registration=Desativar registo config.auth.disable_registration=Disable registration
config.auth.enable_registration_captcha=Ativar captcha para registar config.auth.enable_registration_captcha=Enable registration captcha
config.auth.enable_reverse_proxy_authentication=Ativar autenticação do proxy reverso config.auth.enable_reverse_proxy_authentication=Enable reverse proxy authentication
config.auth.enable_reverse_proxy_auto_registration=Ativar o registo automático do proxy reverso config.auth.enable_reverse_proxy_auto_registration=Enable reverse proxy auto registration
config.auth.reverse_proxy_authentication_header=Cabeçalho de autenticação de proxy reverso config.auth.reverse_proxy_authentication_header=Reverse proxy authentication header
config.user_config=Configuração do utilizador config.user_config=User configuration
config.user.enable_email_notify=Ativar a notificação por e-mail config.user.enable_email_notify=Enable email notification
config.session_config=Configuração de sessão config.session_config=Configuração de sessão
config.session.provider=Provedor config.session.provider=Provider
config.session.provider_config=Configuração do provedor config.session.provider_config=Provider config
config.session.cookie_name=Cookie config.session.cookie_name=Cookie
config.session.https_only=Somente HTTPS config.session.https_only=HTTPS only
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=Adaptador config.cache.adapter=Adapter
config.cache.interval=Intervalo de GC config.cache.interval=GC interval
config.cache.host=Anfitrião config.cache.host=Host
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=Configuração de anexos config.attachment_config=Attachment configuration
config.attachment.enabled=Ativado config.attachment.enabled=Enabled
config.attachment.path=Caminho config.attachment.path=Path
config.attachment.allowed_types=Tipos permitidos config.attachment.allowed_types=Allowed types
config.attachment.max_size=Limite de tamanho config.attachment.max_size=Size limit
config.attachment.max_files=Limite de ficheiros config.attachment.max_files=Files limit
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=Tamanho máximo dos anexos config.release.attachment.max_size=Attachment size limit
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
@@ -1328,10 +1325,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=Intervalo predefinido config.mirror.default_interval=Default interval
config.webhook_config=Configuração de WebHook config.webhook_config=Configuração de WebHook
config.webhook.types=Tipos config.webhook.types=Types
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,8 +494,6 @@ 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
@@ -1276,7 +1274,6 @@ 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,8 +494,6 @@ 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=Загрузить файл
@@ -1276,7 +1274,6 @@ 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,8 +494,6 @@ 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
@@ -1276,7 +1274,6 @@ 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,8 +494,6 @@ 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,7 +1274,6 @@ 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,8 +494,6 @@ 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
@@ -1276,7 +1274,6 @@ 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,8 +494,6 @@ 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
@@ -1276,7 +1274,6 @@ 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,8 +494,6 @@ 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,7 +1274,6 @@ 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,8 +494,6 @@ 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
@@ -1276,7 +1274,6 @@ 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,8 +494,6 @@ 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=上传文件
@@ -1277,7 +1275,6 @@ 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,8 +494,6 @@ 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
@@ -1276,7 +1274,6 @@ 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,8 +494,6 @@ 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,7 +1274,6 @@ 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.16.0. > This is the next-generation, security-focused Docker image. This will become the default image distribution (`gogs/gogs:latest`) starting 0.15.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,15 +1,10 @@
# 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.16.0, and be completely removed no earlier than 0.17.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.15.0, and be completely removed starting 0.16.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

@@ -1,131 +0,0 @@
# 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
```

131
docs/dev/database_schema.md Normal file
View File

@@ -0,0 +1,131 @@
# 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
```

View File

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

58
go.mod
View File

@@ -6,11 +6,8 @@ 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.4 github.com/editorconfig/editorconfig-core-go/v2 v2.6.3
github.com/fergusstrange/embedded-postgres v1.33.0 github.com/go-ldap/ldap/v3 v3.4.11
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
@@ -21,19 +18,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.6 github.com/gogs/git-module v1.8.4
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 v1.1.3 github.com/olekukonko/tablewriter v0.0.5
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
@@ -46,51 +43,47 @@ 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.47.0 golang.org/x/crypto v0.45.0
golang.org/x/net v0.48.0 golang.org/x/net v0.47.0
golang.org/x/text v0.33.0 golang.org/x/text v0.31.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.1 gopkg.in/macaron.v1 v1.5.0
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.18.0 // indirect github.com/fatih/color v1.13.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.7.0 // indirect github.com/go-sql-driver/mysql v1.8.1 // 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
@@ -108,38 +101,34 @@ 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/lib/pq v1.10.9 // indirect github.com/mattn/go-colorable v0.1.13 // 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.19 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mattn/go-sqlite3 v1.14.24 // indirect github.com/mattn/go-sqlite3 v1.14.32 // 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 v0.17.0 // indirect github.com/microsoft/go-mssqldb v1.8.2 // 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.31.0 // indirect golang.org/x/mod v0.29.0 // indirect
golang.org/x/sync v0.19.0 // indirect golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.40.0 // indirect golang.org/x/sys v0.38.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
@@ -148,8 +137,7 @@ 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 ./cmd/gogs // +heroku install ./

262
go.sum
View File

@@ -1,33 +1,35 @@
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=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
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/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= 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/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/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg=
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/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
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=
@@ -40,13 +42,6 @@ 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=
@@ -66,9 +61,6 @@ 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=
@@ -82,17 +74,12 @@ 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/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/editorconfig/editorconfig-core-go/v2 v2.6.3 h1:XVUp6qW3BIkmM3/1EkrHpa6bL56APOynfXcZEmIgOhs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/editorconfig/editorconfig-core-go/v2 v2.6.3/go.mod h1:ThHVc+hqbUsmE1wmK/MASpQEhCleWu1JDJDNhUOMy0c=
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.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
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=
@@ -100,18 +87,12 @@ 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-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4= github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
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=
@@ -140,22 +121,18 @@ 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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-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.6 h1:4Io9vWZYQyIjdIPxfKgeYZXnDKNgydc6OZTxII5xCH4= github.com/gogs/git-module v1.8.4 h1:oSt8sOL4NWOGrSo/CwbS+C4YXtk76QvxyPofem/ViTU=
github.com/gogs/git-module v1.8.6/go.mod h1:IiMSJqi8XH62Kjqjt5Rw8IawSo+DHfM2dDjkSzWLjhs= github.com/gogs/git-module v1.8.4/go.mod h1:bQY0aoMK5Q5+NKgy4jXe3K1GFW+GnsSk0SJK0jh6yD0=
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=
@@ -163,17 +140,16 @@ 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=
@@ -187,8 +163,6 @@ 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=
@@ -201,8 +175,6 @@ 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=
@@ -210,19 +182,14 @@ 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=
@@ -231,8 +198,6 @@ 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=
@@ -249,6 +214,8 @@ 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=
@@ -268,17 +235,13 @@ 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=
@@ -288,32 +251,36 @@ 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.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
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.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
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.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
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=
@@ -325,7 +292,6 @@ 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=
@@ -337,14 +303,8 @@ 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/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
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=
@@ -361,17 +321,15 @@ 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=
@@ -379,24 +337,18 @@ 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=
@@ -414,27 +366,22 @@ 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=
@@ -458,15 +405,10 @@ 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=
@@ -475,9 +417,7 @@ 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=
@@ -486,25 +426,15 @@ 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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
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.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
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=
@@ -515,27 +445,18 @@ 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.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
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=
@@ -547,6 +468,8 @@ 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=
@@ -555,30 +478,26 @@ 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.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.38.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.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
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.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
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=
@@ -586,23 +505,14 @@ 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.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
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=
@@ -615,7 +525,6 @@ 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=
@@ -635,8 +544,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.1 h1:0ytdXYcf6//a8bzedl1fVXzPeIXblEqoNPntWAo9YLU= gopkg.in/macaron.v1 v1.5.0 h1:/dXJaeQagWLjVjCrKH8dgSSU7yG4qTv6rBKpqhYaCyc=
gopkg.in/macaron.v1 v1.5.1/go.mod h1:AiquOw8YeZJC8sUe11vIO6NeA1/TKSlzQXuJ7Tc4cCQ= gopkg.in/macaron.v1 v1.5.0/go.mod h1:sAYUd2r8Q+jLnCN4/ZmdAYHzQn67agV5sAqKFQgrRrw=
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=
@@ -657,15 +566,14 @@ 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=
@@ -686,8 +594,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.39.0 h1:6bwu9Ooim0yVYA7IZn9demiQk/Ejp0BtTjBWFLymSeY= modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
modernc.org/sqlite v1.39.0/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= modernc.org/sqlite v1.38.2/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=
@@ -696,9 +604,3 @@ 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

@@ -1,3 +1,5 @@
//go:build go1.18
// Gogs is a painless self-hosted Git Service. // Gogs is a painless self-hosted Git Service.
package main package main
@@ -7,11 +9,12 @@ 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.15.0+dev" conf.App.Version = "0.14.0+dev"
} }
func main() { func main() {
@@ -20,14 +23,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{
webCommand, cmd.Web,
servCommand, cmd.Serv,
hookCommand, cmd.Hook,
certCommand, cmd.Cert,
adminCommand, cmd.Admin,
importCommand, cmd.Import,
backupCommand, cmd.Backup,
restoreCommand, cmd.Restore,
} }
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 main package cmd
import ( import (
"context" "context"
@@ -14,7 +14,7 @@ import (
) )
var ( var (
adminCommand = cli.Command{ Admin = 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 main package cmd
import ( import (
"context" "context"
@@ -20,7 +20,7 @@ import (
"gogs.io/gogs/internal/osutil" "gogs.io/gogs/internal/osutil"
) )
var backupCommand = cli.Command{ var Backup = 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 main package cmd
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
@@ -22,7 +22,7 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
) )
var certCommand = cli.Command{ var Cert = 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 main package cmd
import ( import (
"time" "time"
@@ -21,6 +21,7 @@ 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,
@@ -29,6 +30,7 @@ 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 main package cmd
import ( import (
"bufio" "bufio"
@@ -24,7 +24,7 @@ import (
) )
var ( var (
hookCommand = cli.Command{ Hook = 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 main package cmd
import ( import (
"bufio" "bufio"
@@ -16,7 +16,7 @@ import (
) )
var ( var (
importCommand = cli.Command{ Import = 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,4 +1,4 @@
package main package cmd
import ( import (
"context" "context"
@@ -18,7 +18,7 @@ import (
"gogs.io/gogs/internal/semverutil" "gogs.io/gogs/internal/semverutil"
) )
var restoreCommand = cli.Command{ var Restore = 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 main package cmd
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 servCommand = cli.Command{ var Serv = 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 main package cmd
import ( import (
"crypto/tls" "crypto/tls"
@@ -30,7 +30,6 @@ 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"
@@ -46,7 +45,7 @@ import (
"gogs.io/gogs/templates" "gogs.io/gogs/templates"
) )
var webCommand = cli.Command{ var Web = 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,
@@ -55,7 +54,6 @@ 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"),
}, },
} }
@@ -162,29 +160,7 @@ func newMacaron() *macaron.Macaron {
} }
func runWeb(c *cli.Context) error { func runWeb(c *cli.Context) error {
// Initialize configuration first to get WorkDir err := route.GlobalInit(c.String("config"))
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)
} }
@@ -353,12 +329,9 @@ 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)
}, reqSignIn) }, ignSignIn)
m.Group("/:username", func() { m.Group("/:username", func() {
m.Post("/action/:action", user.Action) m.Post("/action/:action", user.Action)
@@ -503,7 +476,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 request. // FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
// 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).
@@ -528,7 +501,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 request. // FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
// 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

@@ -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 if !HookMode { } else {
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,11 +142,9 @@ 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() {
@@ -348,11 +346,6 @@ 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,15 +1,11 @@
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.
@@ -231,7 +227,7 @@ var (
) )
type AppOpts struct { type AppOpts struct {
// ⚠️ WARNING: Should only be set by the main package (i.e. "cmd/gogs/main.go"). // ⚠️ WARNING: Should only be set by the main package (i.e. "gogs.go").
Version string `ini:"-"` Version string `ini:"-"`
BrandName string BrandName string
@@ -434,75 +430,10 @@ 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 "cmd/gogs/serv.go". // ⚠️ WARNING: Should only be set by "internal/cmd/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,11 +1,9 @@
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) {
@@ -31,48 +29,3 @@ 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,9 @@ 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_REGISTRATION_CAPTCHA = true ENABLE_CAPTCHA = true
REVERSE_PROXY_AUTHENTICATION_HEADER = X-FORWARDED-FOR ENABLE_NOTIFY_MAIL = true
REVERSE_PROXY_AUTHENTICATION_HEADER=X-FORWARDED-FOR
[user] [user]
ENABLE_EMAIL_NOTIFICATION = true ENABLE_EMAIL_NOTIFICATION = true

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 string, status int, tpl string, f any) { func (c *Context) RenderWithErr(msg, 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(status, tpl) c.HTML(http.StatusOK, tpl)
} }
// NotFound renders the 404 page. // NotFound renders the 404 page.

View File

@@ -142,11 +142,6 @@ 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() {
@@ -437,12 +432,6 @@ 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,
@@ -488,12 +477,6 @@ 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,
@@ -535,12 +518,6 @@ 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,
@@ -582,12 +559,6 @@ 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,
@@ -653,12 +624,6 @@ 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,
@@ -738,11 +703,6 @@ 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)
@@ -836,12 +796,6 @@ 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,
@@ -879,12 +833,6 @@ 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"
"xorm.io/xorm" "gorm.io/gorm"
"gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/errutil" "gogs.io/gogs/internal/errutil"
@@ -19,25 +19,26 @@ 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 `xorm:"uuid UNIQUE"` UUID string `gorm:"column:uuid;type:varchar(191);uniqueIndex"`
IssueID int64 `xorm:"INDEX"` IssueID int64 `gorm:"index"`
CommentID int64 CommentID int64
ReleaseID int64 `xorm:"INDEX"` ReleaseID int64 `gorm:"index"`
Name string Name string
Created time.Time `xorm:"-" json:"-" gorm:"-"` Created time.Time `gorm:"-" json:"-"`
CreatedUnix int64 CreatedUnix int64
} }
func (a *Attachment) BeforeInsert() { func (a *Attachment) BeforeCreate(tx *gorm.DB) error {
a.CreatedUnix = time.Now().Unix() if a.CreatedUnix == 0 {
a.CreatedUnix = tx.NowFunc().Unix()
}
return nil
} }
func (a *Attachment) AfterSet(colName string, _ xorm.Cell) { func (a *Attachment) AfterFind(tx *gorm.DB) error {
switch colName { a.Created = time.Unix(a.CreatedUnix, 0).Local()
case "created_unix": return nil
a.Created = time.Unix(a.CreatedUnix, 0).Local()
}
} }
// 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.
@@ -74,7 +75,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 := x.Insert(attach); err != nil { if err := db.Create(attach).Error; err != nil {
return nil, err return nil, err
} }
@@ -100,60 +101,60 @@ func (ErrAttachmentNotExist) NotFound() bool {
return true return true
} }
func getAttachmentByUUID(e Engine, uuid string) (*Attachment, error) { func getAttachmentByUUID(e *gorm.DB, uuid string) (*Attachment, error) {
attach := &Attachment{UUID: uuid} attach := &Attachment{}
has, err := e.Get(attach) err := e.Where("uuid = ?", uuid).First(attach).Error
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 Engine, uuids []string) ([]*Attachment, error) { func getAttachmentsByUUIDs(e *gorm.DB, 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.In("uuid", uuids).Find(&attachments) return attachments, e.Where("uuid IN ?", uuids).Find(&attachments).Error
} }
// 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(x, uuid) return getAttachmentByUUID(db, uuid)
} }
func getAttachmentsByIssueID(e Engine, issueID int64) ([]*Attachment, error) { func getAttachmentsByIssueID(e *gorm.DB, 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) return attachments, e.Where("issue_id = ? AND comment_id = 0", issueID).Find(&attachments).Error
} }
// 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(x, issueID) return getAttachmentsByIssueID(db, issueID)
} }
func getAttachmentsByCommentID(e Engine, commentID int64) ([]*Attachment, error) { func getAttachmentsByCommentID(e *gorm.DB, commentID int64) ([]*Attachment, error) {
attachments := make([]*Attachment, 0, 5) attachments := make([]*Attachment, 0, 5)
return attachments, e.Where("comment_id=?", commentID).Find(&attachments) return attachments, e.Where("comment_id = ?", commentID).Find(&attachments).Error
} }
// 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(x, commentID) return getAttachmentsByCommentID(db, commentID)
} }
func getAttachmentsByReleaseID(e Engine, releaseID int64) ([]*Attachment, error) { func getAttachmentsByReleaseID(e *gorm.DB, releaseID int64) ([]*Attachment, error) {
attachments := make([]*Attachment, 0, 10) attachments := make([]*Attachment, 0, 10)
return attachments, e.Where("release_id = ?", releaseID).Find(&attachments) return attachments, e.Where("release_id = ?", releaseID).Find(&attachments).Error
} }
// 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(x, releaseID) return getAttachmentsByReleaseID(db, releaseID)
} }
// DeleteAttachment deletes the given attachment and optionally the associated file. // DeleteAttachment deletes the given attachment and optionally the associated file.
@@ -171,7 +172,7 @@ func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) {
} }
} }
if _, err := x.Delete(a); err != nil { if err := db.Delete(a).Error; err != nil {
return i, err return i, err
} }
} }

View File

@@ -17,8 +17,6 @@ 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"
@@ -122,19 +120,19 @@ func dumpLegacyTables(ctx context.Context, dirPath string, verbose bool) error {
log.Trace("Dumping table %q...", tableName) log.Trace("Dumping table %q...", tableName)
} }
tableFile := filepath.Join(dirPath, tableName+".json") err := func() error {
f, err := os.Create(tableFile) tableFile := filepath.Join(dirPath, tableName+".json")
if err != nil { f, err := os.Create(tableFile)
return errors.Newf("create JSON file: %v", err) if err != nil {
} return errors.Wrap(err, "create JSON file")
}
defer func() { _ = f.Close() }()
if err = x.Context(ctx).Asc("id").Iterate(table, func(idx int, bean any) (err error) { return dumpTable(ctx, db, table, f)
return jsoniter.NewEncoder(f).Encode(bean) }()
}); err != nil { if err != nil {
_ = f.Close() return errors.Wrapf(err, "dump table %q", tableName)
return errors.Newf("dump table '%s': %v", tableName, err)
} }
_ = f.Close()
} }
return nil return nil
} }
@@ -229,8 +227,6 @@ 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,
@@ -255,9 +251,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 := x.DropTables(table); err != nil { if err := db.WithContext(ctx).Migrator().DropTable(table); err != nil {
return errors.Newf("drop table %q: %v", tableName, err) return errors.Newf("drop table %q: %v", tableName, err)
} else if err = x.Sync2(table); err != nil { } else if err = db.WithContext(ctx).Migrator().AutoMigrate(table); err != nil {
return errors.Newf("sync table %q: %v", tableName, err) return errors.Newf("sync table %q: %v", tableName, err)
} }
@@ -265,16 +261,28 @@ 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)
_, isInsertProcessor := table.(xorm.BeforeInsertProcessor) s, err := schema.Parse(table, &sync.Map{}, db.NamingStrategy)
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() {
if err = jsoniter.Unmarshal(scanner.Bytes(), table); err != nil { // PostgreSQL does not like the null characters (U+0000)
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 = x.Insert(table); err != nil { if err = db.WithContext(ctx).Create(table).Error; err != nil {
return errors.Newf("insert strcut: %v", err) _ = f.Close()
return errors.Newf("insert struct: %v", err)
} }
var meta struct { var meta struct {
@@ -283,30 +291,30 @@ func importLegacyTables(ctx context.Context, dirPath string, verbose bool) error
DeadlineUnix int64 DeadlineUnix int64
ClosedDateUnix int64 ClosedDateUnix int64
} }
if err = jsoniter.Unmarshal(scanner.Bytes(), &meta); err != nil { if err = jsoniter.Unmarshal(cleaned, &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 save in archive because Insert method updates its value // Reset created_unix back to the date saved in archive because Create method updates its value
if isInsertProcessor && !skipInsertProcessors[rawTableName] { if isInsertProcessor && !skipInsertProcessors[rawTableName] {
if _, err = x.Exec("UPDATE `"+rawTableName+"` SET created_unix=? WHERE id=?", meta.CreatedUnix, meta.ID); err != nil { if err = db.WithContext(ctx).Exec("UPDATE `"+rawTableName+"` SET created_unix=? WHERE id=?", meta.CreatedUnix, meta.ID).Error; 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 = x.Exec("UPDATE `"+rawTableName+"` SET deadline_unix=?, closed_date_unix=? WHERE id=?", meta.DeadlineUnix, meta.ClosedDateUnix, meta.ID); err != nil { 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 {
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 = x.Exec(fmt.Sprintf(`SELECT setval('%s', COALESCE((SELECT MAX(id)+1 FROM "%s"), 1), false);`, seqName, rawTableName)); err != nil { 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 {
return errors.Newf("reset table %q' sequence: %v", rawTableName, err) return errors.Newf("reset table %q' sequence: %v", rawTableName, err)
} }
} }

View File

@@ -7,11 +7,10 @@ import (
"time" "time"
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
"github.com/unknwon/com"
log "unknwon.dev/clog/v2"
"xorm.io/xorm"
api "github.com/gogs/go-gogs-client" api "github.com/gogs/go-gogs-client"
"github.com/unknwon/com"
"gorm.io/gorm"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/errutil" "gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/markup" "gogs.io/gogs/internal/markup"
@@ -50,47 +49,50 @@ type Comment struct {
ID int64 ID int64
Type CommentType Type CommentType
PosterID int64 PosterID int64
Poster *User `xorm:"-" json:"-" gorm:"-"` Poster *User `gorm:"-" json:"-"`
IssueID int64 `xorm:"INDEX"` IssueID int64 `gorm:"index"`
Issue *Issue `xorm:"-" json:"-" gorm:"-"` Issue *Issue `gorm:"-" json:"-"`
CommitID int64 CommitID int64
Line int64 Line int64
Content string `xorm:"TEXT"` Content string `gorm:"type:text"`
RenderedContent string `xorm:"-" json:"-" gorm:"-"` RenderedContent string `gorm:"-" json:"-"`
Created time.Time `xorm:"-" json:"-" gorm:"-"` Created time.Time `gorm:"-" json:"-"`
CreatedUnix int64 CreatedUnix int64
Updated time.Time `xorm:"-" json:"-" gorm:"-"` Updated time.Time `gorm:"-" json:"-"`
UpdatedUnix int64 UpdatedUnix int64
// Reference issue in commit message // Reference issue in commit message
CommitSHA string `xorm:"VARCHAR(40)"` CommitSHA string `gorm:"type:varchar(40)"`
Attachments []*Attachment `xorm:"-" json:"-" gorm:"-"` Attachments []*Attachment `gorm:"-" json:"-"`
// For view issue page. // For view issue page.
ShowTag CommentTag `xorm:"-" json:"-" gorm:"-"` ShowTag CommentTag `gorm:"-" json:"-"`
} }
func (c *Comment) BeforeInsert() { func (c *Comment) BeforeCreate(tx *gorm.DB) error {
c.CreatedUnix = time.Now().Unix() if c.CreatedUnix == 0 {
c.UpdatedUnix = c.CreatedUnix c.CreatedUnix = tx.NowFunc().Unix()
}
func (c *Comment) BeforeUpdate() {
c.UpdatedUnix = time.Now().Unix()
}
func (c *Comment) AfterSet(colName string, _ xorm.Cell) {
switch colName {
case "created_unix":
c.Created = time.Unix(c.CreatedUnix, 0).Local()
case "updated_unix":
c.Updated = time.Unix(c.UpdatedUnix, 0).Local()
} }
if c.UpdatedUnix == 0 {
c.UpdatedUnix = c.CreatedUnix
}
return nil
} }
func (c *Comment) loadAttributes(e Engine) (err error) { func (c *Comment) BeforeUpdate(tx *gorm.DB) error {
c.UpdatedUnix = tx.NowFunc().Unix()
return nil
}
func (c *Comment) AfterFind(tx *gorm.DB) error {
c.Created = time.Unix(c.CreatedUnix, 0).Local()
c.Updated = time.Unix(c.UpdatedUnix, 0).Local()
return nil
}
func (c *Comment) loadAttributes(tx *gorm.DB) (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 {
@@ -104,12 +106,12 @@ func (c *Comment) loadAttributes(e Engine) (err error) {
} }
if c.Issue == nil { if c.Issue == nil {
c.Issue, err = getRawIssueByID(e, c.IssueID) c.Issue, err = getRawIssueByID(tx, 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(e, c.Issue.RepoID) c.Issue.Repo, err = getRepositoryByID(tx, 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)
} }
@@ -117,7 +119,7 @@ func (c *Comment) loadAttributes(e Engine) (err error) {
} }
if c.Attachments == nil { if c.Attachments == nil {
c.Attachments, err = getAttachmentsByCommentID(e, c.ID) c.Attachments, err = getAttachmentsByCommentID(tx, 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)
} }
@@ -127,7 +129,7 @@ func (c *Comment) loadAttributes(e Engine) (err error) {
} }
func (c *Comment) LoadAttributes() error { func (c *Comment) LoadAttributes() error {
return c.loadAttributes(x) return c.loadAttributes(db)
} }
func (c *Comment) HTMLURL() string { func (c *Comment) HTMLURL() string {
@@ -163,9 +165,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(e Engine, opType ActionType, issue *Issue) (err error) { func (c *Comment) mailParticipants(tx *gorm.DB, opType ActionType, issue *Issue) (err error) {
mentions := markup.FindAllMentions(c.Content) mentions := markup.FindAllMentions(c.Content)
if err = updateIssueMentions(e, c.IssueID, mentions); err != nil { if err = updateIssueMentions(tx, c.IssueID, mentions); err != nil {
return errors.Newf("UpdateIssueMentions [%d]: %v", c.IssueID, err) return errors.Newf("UpdateIssueMentions [%d]: %v", c.IssueID, err)
} }
@@ -184,7 +186,7 @@ func (c *Comment) mailParticipants(e Engine, opType ActionType, issue *Issue) (e
return nil return nil
} }
func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) { func createComment(tx *gorm.DB, opts *CreateCommentOptions) (_ *Comment, err error) {
comment := &Comment{ comment := &Comment{
Type: opts.Type, Type: opts.Type,
PosterID: opts.Doer.ID, PosterID: opts.Doer.ID,
@@ -195,7 +197,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
Line: opts.LineNum, Line: opts.LineNum,
Content: opts.Content, Content: opts.Content,
} }
if _, err = e.Insert(comment); err != nil { if err = tx.Create(comment).Error; err != nil {
return nil, err return nil, err
} }
@@ -216,14 +218,14 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
case CommentTypeComment: case CommentTypeComment:
act.OpType = ActionCommentIssue act.OpType = ActionCommentIssue
if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil { if err = tx.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID).Error; 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(e, uuid) attach, err := getAttachmentByUUID(tx, uuid)
if err != nil { if err != nil {
if IsErrAttachmentNotExist(err) { if IsErrAttachmentNotExist(err) {
continue continue
@@ -236,8 +238,10 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, 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
// No assign value could be 0, so ignore AllCols(). if err = tx.Model(attachments[i]).Where("id = ?", attachments[i].ID).Updates(map[string]any{
if _, err = e.ID(attachments[i].ID).Update(attachments[i]); err != nil { "issue_id": attachments[i].IssueID,
"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)
} }
} }
@@ -249,9 +253,9 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
} }
if opts.Issue.IsPull { if opts.Issue.IsPull {
_, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls-1 WHERE id=?", opts.Repo.ID) err = tx.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls-1 WHERE id=?", opts.Repo.ID).Error
} else { } else {
_, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?", opts.Repo.ID) err = tx.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?", opts.Repo.ID).Error
} }
if err != nil { if err != nil {
return nil, err return nil, err
@@ -264,38 +268,38 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
} }
if opts.Issue.IsPull { if opts.Issue.IsPull {
_, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls+1 WHERE id=?", opts.Repo.ID) err = tx.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls+1 WHERE id=?", opts.Repo.ID).Error
} else { } else {
_, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?", opts.Repo.ID) err = tx.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?", opts.Repo.ID).Error
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
if _, err = e.Exec("UPDATE `issue` SET updated_unix = ? WHERE id = ?", time.Now().Unix(), opts.Issue.ID); err != nil { if err = tx.Exec("UPDATE `issue` SET updated_unix = ? WHERE id = ?", tx.NowFunc().Unix(), opts.Issue.ID).Error; 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(e, act); err != nil { if err = notifyWatchers(tx, act); err != nil {
log.Error("notifyWatchers: %v", err) log.Error("notifyWatchers: %v", err)
} }
if err = comment.mailParticipants(e, act.OpType, opts.Issue); err != nil { if err = comment.mailParticipants(tx, act.OpType, opts.Issue); err != nil {
log.Error("MailParticipants: %v", err) log.Error("MailParticipants: %v", err)
} }
} }
return comment, comment.loadAttributes(e) return comment, comment.loadAttributes(tx)
} }
func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue) (*Comment, error) { func createStatusComment(tx *gorm.DB, doer *User, repo *Repository, issue *Issue) (*Comment, error) {
cmtType := CommentTypeClose cmtType := CommentTypeClose
if !issue.IsClosed { if !issue.IsClosed {
cmtType = CommentTypeReopen cmtType = CommentTypeReopen
} }
return createComment(e, &CreateCommentOptions{ return createComment(tx, &CreateCommentOptions{
Type: cmtType, Type: cmtType,
Doer: doer, Doer: doer,
Repo: repo, Repo: repo,
@@ -318,18 +322,12 @@ 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) {
sess := x.NewSession() err = db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() var err error
if err = sess.Begin(); err != nil { comment, err = createComment(tx, opts)
return nil, err return 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.
@@ -367,14 +365,12 @@ 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.
has, err := x.Get(&Comment{ var count int64
Type: CommentTypeCommitRef, err := db.Model(new(Comment)).Where("type = ? AND issue_id = ? AND commit_sha = ?",
IssueID: issue.ID, CommentTypeCommitRef, issue.ID, commitSHA).Count(&count).Error
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 has { } else if count > 0 {
return nil return nil
} }
@@ -411,19 +407,20 @@ 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)
has, err := x.Id(id).Get(c) err := db.Where("id = ?", id).First(c).Error
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(e Engine, comments []*Comment) (err error) { func loadCommentsAttributes(tx *gorm.DB, comments []*Comment) (err error) {
for i := range comments { for i := range comments {
if err = comments[i].loadAttributes(e); err != nil { if err = comments[i].loadAttributes(tx); err != nil {
return errors.Newf("loadAttributes [%d]: %v", comments[i].ID, err) return errors.Newf("loadAttributes [%d]: %v", comments[i].ID, err)
} }
} }
@@ -431,53 +428,55 @@ func loadCommentsAttributes(e Engine, comments []*Comment) (err error) {
return nil return nil
} }
func getCommentsByIssueIDSince(e Engine, issueID, since int64) ([]*Comment, error) { func getCommentsByIssueIDSince(tx *gorm.DB, issueID, since int64) ([]*Comment, error) {
comments := make([]*Comment, 0, 10) comments := make([]*Comment, 0, 10)
sess := e.Where("issue_id = ?", issueID).Asc("created_unix") query := tx.Where("issue_id = ?", issueID).Order("created_unix ASC")
if since > 0 { if since > 0 {
sess.And("updated_unix >= ?", since) query = query.Where("updated_unix >= ?", since)
} }
if err := sess.Find(&comments); err != nil { if err := query.Find(&comments).Error; err != nil {
return nil, err return nil, err
} }
return comments, loadCommentsAttributes(e, comments) return comments, loadCommentsAttributes(tx, comments)
} }
func getCommentsByRepoIDSince(e Engine, repoID, since int64) ([]*Comment, error) { func getCommentsByRepoIDSince(tx *gorm.DB, repoID, since int64) ([]*Comment, error) {
comments := make([]*Comment, 0, 10) comments := make([]*Comment, 0, 10)
sess := e.Where("issue.repo_id = ?", repoID).Join("INNER", "issue", "issue.id = comment.issue_id").Asc("comment.created_unix") query := tx.Joins("INNER JOIN issue ON issue.id = comment.issue_id").
Where("issue.repo_id = ?", repoID).
Order("comment.created_unix ASC")
if since > 0 { if since > 0 {
sess.And("comment.updated_unix >= ?", since) query = query.Where("comment.updated_unix >= ?", since)
} }
if err := sess.Find(&comments); err != nil { if err := query.Find(&comments).Error; err != nil {
return nil, err return nil, err
} }
return comments, loadCommentsAttributes(e, comments) return comments, loadCommentsAttributes(tx, comments)
} }
func getCommentsByIssueID(e Engine, issueID int64) ([]*Comment, error) { func getCommentsByIssueID(tx *gorm.DB, issueID int64) ([]*Comment, error) {
return getCommentsByIssueIDSince(e, issueID, -1) return getCommentsByIssueIDSince(tx, 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(x, issueID) return getCommentsByIssueID(db, 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(x, issueID, since) return getCommentsByIssueIDSince(db, 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(x, repoID, since) return getCommentsByRepoIDSince(db, 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 = x.Id(c.ID).AllCols().Update(c); err != nil { if err = db.Model(c).Where("id = ?", c.ID).Updates(c).Error; err != nil {
return err return err
} }
@@ -511,24 +510,21 @@ func DeleteCommentByID(doer *User, id int64) error {
return err return err
} }
sess := x.NewSession() err = db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() if err := tx.Where("id = ?", comment.ID).Delete(new(Comment)).Error; err != nil {
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.ID(comment.ID).Delete(new(Comment)); err != nil {
return err
}
if comment.Type == CommentTypeComment {
if _, err = sess.Exec("UPDATE `issue` SET num_comments = num_comments - 1 WHERE id = ?", comment.IssueID); err != nil {
return err return err
} }
}
if err = sess.Commit(); err != nil { if comment.Type == CommentTypeComment {
return errors.Newf("commit: %v", err) if err := tx.Exec("UPDATE `issue` SET num_comments = num_comments - 1 WHERE id = ?", comment.IssueID).Error; err != nil {
return err
}
}
return nil
})
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,14 +6,12 @@ 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})")
@@ -53,13 +51,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 `xorm:"INDEX"` RepoID int64 `gorm:"index"`
Name string Name string
Color string `xorm:"VARCHAR(7)"` Color string `gorm:"type:varchar(7)"`
NumIssues int NumIssues int
NumClosedIssues int NumClosedIssues int
NumOpenIssues int `xorm:"-" json:"-" gorm:"-"` NumOpenIssues int `gorm:"-" json:"-"`
IsChecked bool `xorm:"-" json:"-" gorm:"-"` IsChecked bool `gorm:"-" json:"-"`
} }
func (l *Label) APIFormat() *api.Label { func (l *Label) APIFormat() *api.Label {
@@ -97,8 +95,7 @@ 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 {
_, err := x.Insert(labels) return db.Create(labels).Error
return err
} }
var _ errutil.NotFound = (*ErrLabelNotExist)(nil) var _ errutil.NotFound = (*ErrLabelNotExist)(nil)
@@ -123,20 +120,22 @@ 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(e Engine, repoID int64, labelName string) (*Label, error) { func getLabelOfRepoByName(tx *gorm.DB, 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{}
Name: labelName, query := tx.Where("name = ?", labelName)
RepoID: repoID, if repoID > 0 {
query = query.Where("repo_id = ?", repoID)
} }
has, err := e.Get(l) err := query.First(l).Error
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
} }
@@ -144,54 +143,56 @@ func getLabelOfRepoByName(e Engine, repoID int64, labelName string) (*Label, err
// 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(e Engine, repoID, labelID int64) (*Label, error) { func getLabelOfRepoByID(tx *gorm.DB, 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{}
ID: labelID, query := tx.Where("id = ?", labelID)
RepoID: repoID, if repoID > 0 {
query = query.Where("repo_id = ?", repoID)
} }
has, err := e.Get(l) err := query.First(l).Error
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(x, 0, id) return getLabelOfRepoByID(db, 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(x, repoID, labelID) return getLabelOfRepoByID(db, 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(x, repoID, labelName) return getLabelOfRepoByName(db, 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, x.Where("repo_id = ?", repoID).In("id", tool.Int64sToStrings(labelIDs)).Asc("name").Find(&labels) return labels, db.Where("repo_id = ? AND id IN ?", repoID, labelIDs).Order("name ASC").Find(&labels).Error
} }
// 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, x.Where("repo_id = ?", repoID).Asc("name").Find(&labels) return labels, db.Where("repo_id = ?", repoID).Order("name ASC").Find(&labels).Error
} }
func getLabelsByIssueID(e Engine, issueID int64) ([]*Label, error) { func getLabelsByIssueID(tx *gorm.DB, issueID int64) ([]*Label, error) {
issueLabels, err := getIssueLabels(e, issueID) issueLabels, err := getIssueLabels(tx, 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 {
@@ -204,22 +205,21 @@ func getLabelsByIssueID(e Engine, issueID int64) ([]*Label, error) {
} }
labels := make([]*Label, 0, len(labelIDs)) labels := make([]*Label, 0, len(labelIDs))
return labels, e.Where("id > 0").In("id", tool.Int64sToStrings(labelIDs)).Asc("name").Find(&labels) return labels, tx.Where("id > 0 AND id IN ?", labelIDs).Order("name ASC").Find(&labels).Error
} }
// 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(x, issueID) return getLabelsByIssueID(db, issueID)
} }
func updateLabel(e Engine, l *Label) error { func updateLabel(tx *gorm.DB, l *Label) error {
_, err := e.ID(l.ID).AllCols().Update(l) return tx.Model(l).Where("id = ?", l.ID).Updates(l).Error
return err
} }
// UpdateLabel updates label information. // UpdateLabel updates label information.
func UpdateLabel(l *Label) error { func UpdateLabel(l *Label) error {
return updateLabel(x, l) return updateLabel(db, l)
} }
// DeleteLabel delete a label of given repository. // DeleteLabel delete a label of given repository.
@@ -232,19 +232,15 @@ func DeleteLabel(repoID, labelID int64) error {
return err return err
} }
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() if err := tx.Where("id = ?", labelID).Delete(new(Label)).Error; err != nil {
if err = sess.Begin(); err != nil { return err
return err }
} if err := tx.Where("label_id = ?", labelID).Delete(new(IssueLabel)).Error; err != nil {
return err
if _, err = sess.ID(labelID).Delete(new(Label)); err != nil { }
return err return nil
} else if _, err = sess.Where("label_id = ?", labelID).Delete(new(IssueLabel)); err != nil { })
return err
}
return sess.Commit()
} }
// .___ .____ ___. .__ // .___ .____ ___. .__
@@ -257,25 +253,26 @@ 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 `xorm:"UNIQUE(s)"` IssueID int64 `gorm:"uniqueIndex:issue_label_unique"`
LabelID int64 `xorm:"UNIQUE(s)"` LabelID int64 `gorm:"uniqueIndex:issue_label_unique"`
} }
func hasIssueLabel(e Engine, issueID, labelID int64) bool { func hasIssueLabel(tx *gorm.DB, issueID, labelID int64) bool {
has, _ := e.Where("issue_id = ? AND label_id = ?", issueID, labelID).Get(new(IssueLabel)) var count int64
return has tx.Model(new(IssueLabel)).Where("issue_id = ? AND label_id = ?", issueID, labelID).Count(&count)
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(x, issueID, labelID) return hasIssueLabel(db, issueID, labelID)
} }
func newIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) { func newIssueLabel(tx *gorm.DB, issue *Issue, label *Label) (err error) {
if _, err = e.Insert(&IssueLabel{ if err = tx.Create(&IssueLabel{
IssueID: issue.ID, IssueID: issue.ID,
LabelID: label.ID, LabelID: label.ID,
}); err != nil { }).Error; err != nil {
return err return err
} }
@@ -284,7 +281,7 @@ func newIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) {
label.NumClosedIssues++ label.NumClosedIssues++
} }
if err = updateLabel(e, label); err != nil { if err = updateLabel(tx, label); err != nil {
return errors.Newf("updateLabel: %v", err) return errors.Newf("updateLabel: %v", err)
} }
@@ -298,26 +295,18 @@ func NewIssueLabel(issue *Issue, label *Label) (err error) {
return nil return nil
} }
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() return newIssueLabel(tx, issue, label)
if err = sess.Begin(); err != nil { })
return err
}
if err = newIssueLabel(sess, issue, label); err != nil {
return err
}
return sess.Commit()
} }
func newIssueLabels(e *xorm.Session, issue *Issue, labels []*Label) (err error) { func newIssueLabels(tx *gorm.DB, issue *Issue, labels []*Label) (err error) {
for i := range labels { for i := range labels {
if hasIssueLabel(e, issue.ID, labels[i].ID) { if hasIssueLabel(tx, issue.ID, labels[i].ID) {
continue continue
} }
if err = newIssueLabel(e, issue, labels[i]); err != nil { if err = newIssueLabel(tx, issue, labels[i]); err != nil {
return errors.Newf("newIssueLabel: %v", err) return errors.Newf("newIssueLabel: %v", err)
} }
} }
@@ -327,34 +316,23 @@ func newIssueLabels(e *xorm.Session, 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) {
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() return newIssueLabels(tx, issue, labels)
if err = sess.Begin(); err != nil { })
return err
}
if err = newIssueLabels(sess, issue, labels); err != nil {
return err
}
return sess.Commit()
} }
func getIssueLabels(e Engine, issueID int64) ([]*IssueLabel, error) { func getIssueLabels(tx *gorm.DB, issueID int64) ([]*IssueLabel, error) {
issueLabels := make([]*IssueLabel, 0, 10) issueLabels := make([]*IssueLabel, 0, 10)
return issueLabels, e.Where("issue_id=?", issueID).Asc("label_id").Find(&issueLabels) return issueLabels, tx.Where("issue_id = ?", issueID).Order("label_id ASC").Find(&issueLabels).Error
} }
// 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(x, issueID) return getIssueLabels(db, issueID)
} }
func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) { func deleteIssueLabel(tx *gorm.DB, issue *Issue, label *Label) (err error) {
if _, err = e.Delete(&IssueLabel{ if err = tx.Where("issue_id = ? AND label_id = ?", issue.ID, label.ID).Delete(&IssueLabel{}).Error; err != nil {
IssueID: issue.ID,
LabelID: label.ID,
}); err != nil {
return err return err
} }
@@ -362,7 +340,7 @@ func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) {
if issue.IsClosed { if issue.IsClosed {
label.NumClosedIssues-- label.NumClosedIssues--
} }
if err = updateLabel(e, label); err != nil { if err = updateLabel(tx, label); err != nil {
return errors.Newf("updateLabel: %v", err) return errors.Newf("updateLabel: %v", err)
} }
@@ -377,15 +355,7 @@ func deleteIssueLabel(e *xorm.Session, 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) {
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() return deleteIssueLabel(tx, issue, label)
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(x, issue.ID, mentions); err != nil { if err = updateIssueMentions(db, 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,6 +8,7 @@ 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,6 +7,7 @@ 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,11 +4,10 @@ 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"
@@ -17,29 +16,30 @@ 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 `xorm:"INDEX"` RepoID int64 `gorm:"index"`
Name string Name string
Content string `xorm:"TEXT"` Content string `gorm:"type:text"`
RenderedContent string `xorm:"-" json:"-" gorm:"-"` RenderedContent string `gorm:"-" json:"-"`
IsClosed bool IsClosed bool
NumIssues int NumIssues int
NumClosedIssues int NumClosedIssues int
NumOpenIssues int `xorm:"-" json:"-" gorm:"-"` NumOpenIssues int `gorm:"-" json:"-"`
Completeness int // Percentage(1-100). Completeness int // Percentage(1-100).
IsOverDue bool `xorm:"-" json:"-" gorm:"-"` IsOverDue bool `gorm:"-" json:"-"`
DeadlineString string `xorm:"-" json:"-" gorm:"-"` DeadlineString string `gorm:"-" json:"-"`
Deadline time.Time `xorm:"-" json:"-" gorm:"-"` Deadline time.Time `gorm:"-" json:"-"`
DeadlineUnix int64 DeadlineUnix int64
ClosedDate time.Time `xorm:"-" json:"-" gorm:"-"` ClosedDate time.Time `gorm:"-" json:"-"`
ClosedDateUnix int64 ClosedDateUnix int64
} }
func (m *Milestone) BeforeInsert() { func (m *Milestone) BeforeCreate(tx *gorm.DB) error {
m.DeadlineUnix = m.Deadline.Unix() m.DeadlineUnix = m.Deadline.Unix()
return nil
} }
func (m *Milestone) BeforeUpdate() { func (m *Milestone) BeforeUpdate(tx *gorm.DB) error {
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,27 +48,22 @@ func (m *Milestone) BeforeUpdate() {
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) AfterSet(colName string, _ xorm.Cell) { func (m *Milestone) AfterFind(tx *gorm.DB) error {
switch colName { m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
case "num_closed_issues":
m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
case "deadline_unix":
m.Deadline = time.Unix(m.DeadlineUnix, 0).Local()
if m.Deadline.Year() == 9999 {
return
}
m.Deadline = time.Unix(m.DeadlineUnix, 0).Local()
if m.Deadline.Year() != 9999 {
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.
@@ -102,30 +97,24 @@ func (m *Milestone) APIFormat() *api.Milestone {
} }
func (m *Milestone) CountIssues(isClosed, includePulls bool) int64 { func (m *Milestone) CountIssues(isClosed, includePulls bool) int64 {
sess := x.Where("milestone_id = ?", m.ID).And("is_closed = ?", isClosed) query := db.Model(new(Issue)).Where("milestone_id = ? AND is_closed = ?", m.ID, isClosed)
if !includePulls { if !includePulls {
sess.And("is_pull = ?", false) query = query.Where("is_pull = ?", false)
} }
count, _ := sess.Count(new(Issue)) var count int64
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) {
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() if err := tx.Create(m).Error; err != nil {
if err = sess.Begin(); err != nil { return err
return err }
}
if _, err = sess.Insert(m); err != nil { return tx.Exec("UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?", m.RepoID).Error
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)
@@ -147,74 +136,73 @@ func (ErrMilestoneNotExist) NotFound() bool {
return true return true
} }
func getMilestoneByRepoID(e Engine, repoID, id int64) (*Milestone, error) { func getMilestoneByRepoID(e *gorm.DB, repoID, id int64) (*Milestone, error) {
m := &Milestone{ m := &Milestone{}
ID: id, err := e.Where("id = ? AND repo_id = ?", id, repoID).First(m).Error
RepoID: repoID,
}
has, err := e.Get(m)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrMilestoneNotExist{args: map[string]any{"repoID": repoID, "milestoneID": id}}
}
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(x, repoID, id) return getMilestoneByRepoID(db, 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, x.Where("repo_id = ?", repoID).Find(&miles) return miles, db.Where("repo_id = ?", repoID).Find(&miles).Error
} }
// 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)
sess := x.Where("repo_id = ? AND is_closed = ?", repoID, isClosed) query := db.Where("repo_id = ? AND is_closed = ?", repoID, isClosed)
if page > 0 { if page > 0 {
sess = sess.Limit(conf.UI.IssuePagingNum, (page-1)*conf.UI.IssuePagingNum) query = query.Limit(conf.UI.IssuePagingNum).Offset((page - 1) * conf.UI.IssuePagingNum)
} }
return miles, sess.Find(&miles) return miles, query.Find(&miles).Error
} }
func updateMilestone(e Engine, m *Milestone) error { func updateMilestone(e *gorm.DB, m *Milestone) error {
_, err := e.ID(m.ID).AllCols().Update(m) return e.Model(m).Where("id = ?", m.ID).Updates(m).Error
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(x, m) return updateMilestone(db, m)
} }
func countRepoMilestones(e Engine, repoID int64) int64 { func countRepoMilestones(e *gorm.DB, repoID int64) int64 {
count, _ := e.Where("repo_id=?", repoID).Count(new(Milestone)) var count int64
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(x, repoID) return countRepoMilestones(db, repoID)
} }
func countRepoClosedMilestones(e Engine, repoID int64) int64 { func countRepoClosedMilestones(e *gorm.DB, repoID int64) int64 {
closed, _ := e.Where("repo_id=? AND is_closed=?", repoID, true).Count(new(Milestone)) var count int64
return closed e.Model(new(Milestone)).Where("repo_id = ? AND is_closed = ?", repoID, true).Count(&count)
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(x, repoID) return countRepoClosedMilestones(db, 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) {
open, _ = x.Where("repo_id=? AND is_closed=?", repoID, false).Count(new(Milestone)) db.Model(new(Milestone)).Where("repo_id = ? AND is_closed = ?", repoID, false).Count(&open)
return open, CountRepoClosedMilestones(repoID) return open, CountRepoClosedMilestones(repoID)
} }
@@ -227,26 +215,19 @@ func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) {
return err return err
} }
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() m.IsClosed = isClosed
if err = sess.Begin(); err != nil { if err := updateMilestone(tx, m); err != nil {
return err return err
} }
m.IsClosed = isClosed repo.NumMilestones = int(countRepoMilestones(tx, repo.ID))
if err = updateMilestone(sess, m); err != nil { repo.NumClosedMilestones = int(countRepoClosedMilestones(tx, repo.ID))
return err return tx.Model(repo).Where("id = ?", repo.ID).Updates(repo).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 { func changeMilestoneIssueStats(e *gorm.DB, issue *Issue) error {
if issue.MilestoneID == 0 { if issue.MilestoneID == 0 {
return nil return nil
} }
@@ -270,20 +251,12 @@ func changeMilestoneIssueStats(e *xorm.Session, 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) {
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() return changeMilestoneIssueStats(tx, issue)
if err = sess.Begin(); err != nil { })
return err
}
if err = changeMilestoneIssueStats(sess, issue); err != nil {
return err
}
return sess.Commit()
} }
func changeMilestoneAssign(e *xorm.Session, issue *Issue, oldMilestoneID int64) error { func changeMilestoneAssign(e *gorm.DB, 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 {
@@ -297,7 +270,9 @@ func changeMilestoneAssign(e *xorm.Session, issue *Issue, oldMilestoneID int64)
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
} }
@@ -317,7 +292,9 @@ func changeMilestoneAssign(e *xorm.Session, issue *Issue, oldMilestoneID int64)
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
} }
@@ -329,18 +306,11 @@ func changeMilestoneAssign(e *xorm.Session, issue *Issue, oldMilestoneID int64)
// 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) {
sess := x.NewSession() err = db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() return changeMilestoneAssign(tx, issue, oldMilestoneID)
if err = sess.Begin(); err != nil { })
return err if err != nil {
} 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
@@ -394,26 +364,21 @@ func DeleteMilestoneOfRepoByID(repoID, id int64) error {
return err return err
} }
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() if err := tx.Where("id = ?", m.ID).Delete(new(Milestone)).Error; err != nil {
if err = sess.Begin(); err != nil { return err
return err }
}
if _, err = sess.ID(m.ID).Delete(new(Milestone)); err != nil { repo.NumMilestones = int(countRepoMilestones(tx, repo.ID))
return err repo.NumClosedMilestones = int(countRepoClosedMilestones(tx, repo.ID))
} if err := tx.Model(repo).Where("id = ?", repo.ID).Updates(repo).Error; err != nil {
return err
}
repo.NumMilestones = int(countRepoMilestones(sess, repo.ID)) if err := tx.Exec("UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?", m.ID).Error; err != nil {
repo.NumClosedMilestones = int(countRepoClosedMilestones(sess, repo.ID)) return err
if _, err = sess.ID(repo.ID).AllCols().Update(repo); err != nil { }
return err
}
if _, err = sess.Exec("UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?", m.ID); err != nil { return tx.Exec("UPDATE `issue_user` SET milestone_id = 0 WHERE milestone_id = ?", m.ID).Error
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,12 +8,11 @@ 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"
@@ -41,41 +40,41 @@ func (err MirrorNotExist) Error() string {
type Mirror struct { type Mirror struct {
ID int64 ID int64
RepoID int64 RepoID int64
Repo *Repository `xorm:"-" json:"-" gorm:"-"` Repo *Repository `gorm:"-" json:"-"`
Interval int // Hour. Interval int // Hour.
EnablePrune bool `xorm:"NOT NULL DEFAULT true"` EnablePrune bool `gorm:"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 `xorm:"-" json:"-" gorm:"-"` LastSync time.Time `gorm:"-" json:"-"`
LastSyncUnix int64 `xorm:"updated_unix"` LastSyncUnix int64 `gorm:"column:updated_unix"`
NextSync time.Time `xorm:"-" json:"-" gorm:"-"` NextSync time.Time `gorm:"-" json:"-"`
NextSyncUnix int64 `xorm:"next_update_unix"` NextSyncUnix int64 `gorm:"column:next_update_unix"`
address string `xorm:"-"` address string `gorm:"-"`
} }
func (m *Mirror) BeforeInsert() { func (m *Mirror) BeforeCreate(tx *gorm.DB) error {
m.NextSyncUnix = m.NextSync.Unix() m.NextSyncUnix = m.NextSync.Unix()
return nil
} }
func (m *Mirror) BeforeUpdate() { func (m *Mirror) BeforeUpdate(tx *gorm.DB) error {
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) AfterSet(colName string, _ xorm.Cell) { func (m *Mirror) AfterFind(tx *gorm.DB) error {
var err error if m.RepoID > 0 {
switch colName { var err error
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()
case "next_update_unix":
m.NextSync = time.Unix(m.NextSyncUnix, 0).Local()
} }
m.LastSync = time.Unix(m.LastSyncUnix, 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.
@@ -277,34 +276,33 @@ func (m *Mirror) runSync() ([]*mirrorSyncResult, bool) {
return parseRemoteUpdateOutput(output), true return parseRemoteUpdateOutput(output), true
} }
func getMirrorByRepoID(e Engine, repoID int64) (*Mirror, error) { func getMirrorByRepoID(e *gorm.DB, repoID int64) (*Mirror, error) {
m := &Mirror{RepoID: repoID} m := &Mirror{}
has, err := e.Get(m) err := e.Where("repo_id = ?", repoID).First(m).Error
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(x, repoID) return getMirrorByRepoID(db, repoID)
} }
func updateMirror(e Engine, m *Mirror) error { func updateMirror(e *gorm.DB, m *Mirror) error {
_, err := e.ID(m.ID).AllCols().Update(m) return e.Model(m).Where("id = ?", m.ID).Updates(m).Error
return err
} }
func UpdateMirror(m *Mirror) error { func UpdateMirror(m *Mirror) error {
return updateMirror(x, m) return updateMirror(db, m)
} }
func DeleteMirrorByRepoID(repoID int64) error { func DeleteMirrorByRepoID(repoID int64) error {
_, err := x.Delete(&Mirror{RepoID: repoID}) return db.Where("repo_id = ?", repoID).Delete(&Mirror{}).Error
return err
} }
// MirrorUpdate checks and updates mirror repositories. // MirrorUpdate checks and updates mirror repositories.
@@ -317,17 +315,18 @@ func MirrorUpdate() {
log.Trace("Doing: MirrorUpdate") log.Trace("Doing: MirrorUpdate")
if err := x.Where("next_update_unix<=?", time.Now().Unix()).Iterate(new(Mirror), func(idx int, bean any) error { var mirrors []*Mirror
m := bean.(*Mirror) if err := db.Where("next_update_unix <= ?", time.Now().Unix()).Find(&mirrors).Error; err != nil {
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)
return nil continue
} }
MirrorQueue.Add(m.RepoID) MirrorQueue.Add(m.RepoID)
return nil
}); err != nil {
log.Error("MirrorUpdate: %v", err)
} }
} }
@@ -454,7 +453,7 @@ func SyncMirrors() {
} }
} }
if _, err = x.Exec("UPDATE mirror SET updated_unix = ? WHERE repo_id = ?", time.Now().Unix(), m.RepoID); err != nil { if err = db.Exec("UPDATE mirror SET updated_unix = ? WHERE repo_id = ?", time.Now().Unix(), m.RepoID).Error; 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
} }
@@ -469,7 +468,7 @@ func SyncMirrors() {
continue continue
} }
if _, err = x.Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", latestCommitTime.Unix(), m.RepoID); err != nil { if err = db.Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", latestCommitTime.Unix(), m.RepoID).Error; 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,7 +2,6 @@ package database
import ( import (
"context" "context"
"database/sql"
"fmt" "fmt"
"os" "os"
"path" "path"
@@ -11,44 +10,23 @@ 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 (
x *xorm.Engine db *gorm.DB
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),
@@ -59,93 +37,90 @@ 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 getEngine() (*xorm.Engine, error) { func getGormDB(gormLogger logger.Writer) (*gorm.DB, error) {
Param := "?" if conf.Database.Type == "sqlite3" {
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)
} }
conf.UseSQLite3 = true
connStr = "file:" + conf.Database.Path + "?cache=shared&mode=rwc"
default:
return nil, errors.Newf("unknown database type: %s", conf.Database.Type)
} }
return xorm.NewEngine(driver, connStr)
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
case "mssql":
conf.UseMSSQL = true
}
return gormDB, nil
} }
func NewTestEngine() error { func NewTestEngine() error {
x, err := getEngine() var err error
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)
} }
if conf.UsePostgreSQL { for _, table := range legacyTables {
x.SetSchema(conf.Database.Schema) if db.Migrator().HasTable(table) {
continue
}
if err = db.Migrator().AutoMigrate(table); err != nil {
return errors.Wrap(err, "auto migrate")
}
} }
return nil
x.SetMapper(core.GonicMapper{})
return x.StoreEngine("InnoDB").Sync2(legacyTables...)
} }
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", "xorm.log") logPath = filepath.Join(conf.Log.RootPath, "hooks", "gorm.log")
} else { } else {
logPath = filepath.Join(conf.Log.RootPath, "xorm.log") logPath = filepath.Join(conf.Log.RootPath, "gorm.log")
} }
sec := conf.File.Section("log.xorm") sec := conf.File.Section("log.gorm")
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),
@@ -155,20 +130,9 @@ func SetEngine() (*gorm.DB, error) {
}, },
) )
if err != nil { if err != nil {
return nil, errors.Newf("create 'xorm.log': %v", err) return nil, errors.Newf("create 'gorm.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}
@@ -178,23 +142,37 @@ 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 {
db, err := SetEngine() gormDB, err := SetEngine()
if err != nil { if err != nil {
return err return err
} }
if err = migrations.Migrate(db); err != nil { if err = migrations.Migrate(gormDB); err != nil {
return errors.Newf("migrate: %v", err) return errors.Newf("migrate: %v", err)
} }
if err = x.StoreEngine("InnoDB").Sync2(legacyTables...); err != nil { for _, table := range legacyTables {
return errors.Wrap(err, "sync tables") if gormDB.Migrator().HasTable(table) {
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
} }
@@ -212,33 +190,54 @@ 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()
stats.Counter.PublicKey, _ = x.Count(new(PublicKey)) var count int64
db.Model(new(PublicKey)).Count(&count)
stats.Counter.PublicKey = count
stats.Counter.Repo = CountRepositories(true) stats.Counter.Repo = CountRepositories(true)
stats.Counter.Watch, _ = x.Count(new(Watch)) db.Model(new(Watch)).Count(&count)
stats.Counter.Star, _ = x.Count(new(Star)) stats.Counter.Watch = count
stats.Counter.Action, _ = x.Count(new(Action)) db.Model(new(Star)).Count(&count)
stats.Counter.Access, _ = x.Count(new(Access)) stats.Counter.Star = count
stats.Counter.Issue, _ = x.Count(new(Issue)) db.Model(new(Action)).Count(&count)
stats.Counter.Comment, _ = x.Count(new(Comment)) stats.Counter.Action = count
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
stats.Counter.Follow, _ = x.Count(new(Follow)) db.Model(new(Follow)).Count(&count)
stats.Counter.Mirror, _ = x.Count(new(Mirror)) stats.Counter.Follow = count
stats.Counter.Release, _ = x.Count(new(Release)) db.Model(new(Mirror)).Count(&count)
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)
stats.Counter.Webhook, _ = x.Count(new(Webhook)) db.Model(new(Webhook)).Count(&count)
stats.Counter.Milestone, _ = x.Count(new(Milestone)) stats.Counter.Webhook = count
stats.Counter.Label, _ = x.Count(new(Label)) db.Model(new(Milestone)).Count(&count)
stats.Counter.HookTask, _ = x.Count(new(HookTask)) stats.Counter.Milestone = count
stats.Counter.Team, _ = x.Count(new(Team)) db.Model(new(Label)).Count(&count)
stats.Counter.Attachment, _ = x.Count(new(Attachment)) stats.Counter.Label = count
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 x == nil { if db == nil {
return errors.New("database not available") return errors.New("database not available")
} }
return x.Ping() sqlDB, err := db.DB()
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,8 +6,7 @@ import (
"strings" "strings"
"github.com/cockroachdb/errors" "github.com/cockroachdb/errors"
"xorm.io/builder" "gorm.io/gorm"
"xorm.io/xorm"
"gogs.io/gogs/internal/errutil" "gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/repoutil" "gogs.io/gogs/internal/repoutil"
@@ -26,32 +25,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(e Engine, name string) (*Team, error) { func (org *User) getTeam(tx *gorm.DB, name string) (*Team, error) {
return getTeamOfOrgByName(e, org.ID, name) return getTeamOfOrgByName(tx, 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(x, name) return org.getTeam(db, name)
} }
func (org *User) getOwnerTeam(e Engine) (*Team, error) { func (org *User) getOwnerTeam(tx *gorm.DB) (*Team, error) {
return org.getTeam(e, ownerTeamName) return org.getTeam(tx, 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(x) return org.getOwnerTeam(db)
} }
func (org *User) getTeams(e Engine) (err error) { func (org *User) getTeams(tx *gorm.DB) (err error) {
org.Teams, err = getTeamsByOrgID(e, org.ID) org.Teams, err = getTeamsByOrgID(tx, 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(x) return org.getTeams(db)
} }
// TeamsHaveAccessToRepo returns all teams that have given access level to the repository. // TeamsHaveAccessToRepo returns all teams that have given access level to the repository.
@@ -86,13 +85,13 @@ func (org *User) RemoveMember(uid int64) error {
return RemoveOrgUser(org.ID, uid) return RemoveOrgUser(org.ID, uid)
} }
func (org *User) removeOrgRepo(e Engine, repoID int64) error { func (org *User) removeOrgRepo(tx *gorm.DB, repoID int64) error {
return removeOrgRepo(e, org.ID, repoID) return removeOrgRepo(tx, 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(x, repoID) return org.removeOrgRepo(db, repoID)
} }
// CreateOrganization creates record of a new organization. // CreateOrganization creates record of a new organization.
@@ -121,52 +120,48 @@ func CreateOrganization(org, owner *User) (err error) {
org.NumTeams = 1 org.NumTeams = 1
org.NumMembers = 1 org.NumMembers = 1
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() if err := tx.Create(org).Error; err != nil {
if err = sess.Begin(); err != nil { return errors.Newf("insert organization: %v", err)
return err }
} _ = userutil.GenerateRandomAvatar(org.ID, org.Name, org.Email)
if _, err = sess.Insert(org); err != nil { // Add initial creator to organization and owner team.
return errors.Newf("insert organization: %v", err) if err := tx.Create(&OrgUser{
} UID: owner.ID,
_ = userutil.GenerateRandomAvatar(org.ID, org.Name, org.Email) OrgID: org.ID,
IsOwner: true,
NumTeams: 1,
}).Error; err != nil {
return errors.Newf("insert org-user relation: %v", err)
}
// Add initial creator to organization and owner team. // Create default owner team.
if _, err = sess.Insert(&OrgUser{ t := &Team{
UID: owner.ID, OrgID: org.ID,
OrgID: org.ID, LowerName: strings.ToLower(ownerTeamName),
IsOwner: true, Name: ownerTeamName,
NumTeams: 1, Authorize: AccessModeOwner,
}); err != nil { NumMembers: 1,
return errors.Newf("insert org-user relation: %v", err) }
} if err := tx.Create(t).Error; err != nil {
return errors.Newf("insert owner team: %v", err)
}
// Create default owner team. if err := tx.Create(&TeamUser{
t := &Team{ UID: owner.ID,
OrgID: org.ID, OrgID: org.ID,
LowerName: strings.ToLower(ownerTeamName), TeamID: t.ID,
Name: ownerTeamName, }).Error; err != nil {
Authorize: AccessModeOwner, return errors.Newf("insert team-user relation: %v", err)
NumMembers: 1, }
}
if _, err = sess.Insert(t); err != nil {
return errors.Newf("insert owner team: %v", err)
}
if _, err = sess.Insert(&TeamUser{ if err := os.MkdirAll(repoutil.UserPath(org.Name), os.ModePerm); err != nil {
UID: owner.ID, return errors.Newf("create directory: %v", err)
OrgID: org.ID, }
TeamID: t.ID,
}); err != nil {
return errors.Newf("insert team-user relation: %v", err)
}
if err = os.MkdirAll(repoutil.UserPath(org.Name), os.ModePerm); err != nil { return nil
return errors.Newf("create directory: %v", err) })
}
return sess.Commit()
} }
// GetOrgByName returns organization by given name. // GetOrgByName returns organization by given name.
@@ -174,35 +169,36 @@ func GetOrgByName(name string) (*User, error) {
if name == "" { if name == "" {
return nil, ErrOrgNotExist return nil, ErrOrgNotExist
} }
u := &User{ u := &User{}
LowerName: strings.ToLower(name), err := db.Where("lower_name = ? AND type = ?", strings.ToLower(name), UserTypeOrganization).First(u).Error
Type: UserTypeOrganization,
}
has, err := x.Get(u)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrOrgNotExist
}
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 {
count, _ := x.Where("type=1").Count(new(User)) var count int64
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, x.Limit(pageSize, (page-1)*pageSize).Where("type=1").Asc("id").Find(&orgs) return orgs, db.Where("type = ?", UserTypeOrganization).
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(e Engine, beans ...any) (err error) { func deleteBeans(tx *gorm.DB, beans ...any) (err error) {
for i := range beans { for i := range beans {
if _, err = e.Delete(beans[i]); err != nil { if err = tx.Delete(beans[i]).Error; err != nil {
return err return err
} }
} }
@@ -216,20 +212,13 @@ func DeleteOrganization(org *User) error {
return err return err
} }
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() return deleteBeans(tx,
if err = sess.Begin(); err != nil { &Team{OrgID: org.ID},
return err &OrgUser{OrgID: org.ID},
} &TeamUser{OrgID: org.ID},
)
if err = deleteBeans(sess, })
&Team{OrgID: org.ID},
&OrgUser{OrgID: org.ID},
&TeamUser{OrgID: org.ID},
); err != nil {
return errors.Newf("deleteBeans: %v", err)
}
return sess.Commit()
} }
// ________ ____ ___ // ________ ____ ___
@@ -251,84 +240,94 @@ 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 {
has, _ := x.Where("is_owner = ?", true).And("uid = ?", userID).And("org_id = ?", orgID).Get(new(OrgUser)) var count int64
return has db.Model(new(OrgUser)).Where("is_owner = ? AND uid = ? AND org_id = ?", true, userID, orgID).Count(&count)
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 {
has, _ := x.Where("uid=?", uid).And("org_id=?", orgID).Get(new(OrgUser)) var count int64
return has db.Model(new(OrgUser)).Where("uid = ? AND org_id = ?", uid, orgID).Count(&count)
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 {
has, _ := x.Where("uid=?", uid).And("org_id=?", orgID).And("is_public=?", true).Get(new(OrgUser)) var count int64
return has db.Model(new(OrgUser)).Where("uid = ? AND org_id = ? AND is_public = ?", uid, orgID, true).Count(&count)
return count > 0
} }
func getOrgsByUserID(sess *xorm.Session, userID int64, showAll bool) ([]*User, error) { func getOrgsByUserID(tx *gorm.DB, 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 {
sess.And("`org_user`.is_public=?", true) query = query.Where("`org_user`.is_public = ?", true)
} }
return orgs, sess.And("`org_user`.uid=?", userID). return orgs, query.Find(&orgs).Error
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(x.NewSession(), userID, showAll) return getOrgsByUserID(db, userID, showAll)
} }
func getOwnedOrgsByUserID(sess *xorm.Session, userID int64) ([]*User, error) { func getOwnedOrgsByUserID(tx *gorm.DB, userID int64) ([]*User, error) {
orgs := make([]*User, 0, 10) orgs := make([]*User, 0, 10)
return orgs, sess.Where("`org_user`.uid=?", userID).And("`org_user`.is_owner=?", true). return orgs, tx.Table("`user`").
Join("INNER", "`org_user`", "`org_user`.org_id=`user`.id").Find(&orgs) Joins("INNER JOIN `org_user` ON `org_user`.org_id = `user`.id").
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) {
sess := x.NewSession() return getOwnedOrgsByUserID(db, userID)
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) {
sess := x.NewSession() orgs := make([]*User, 0, 10)
return getOwnedOrgsByUserID(sess.Desc(desc), userID) return orgs, db.Table("`user`").
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(e Engine, orgID int64, limit int) ([]*OrgUser, error) { func getOrgUsersByOrgID(tx *gorm.DB, orgID int64, limit int) ([]*OrgUser, error) {
orgUsers := make([]*OrgUser, 0, 10) orgUsers := make([]*OrgUser, 0, 10)
sess := e.Where("org_id=?", orgID) query := tx.Where("org_id = ?", orgID)
if limit > 0 { if limit > 0 {
sess = sess.Limit(limit) query = query.Limit(limit)
} }
return orgUsers, sess.Find(&orgUsers) return orgUsers, query.Find(&orgUsers).Error
} }
// 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(x, orgID, limit) return getOrgUsersByOrgID(db, 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)
has, err := x.Where("uid=?", uid).And("org_id=?", orgID).Get(ou) err := db.Where("uid = ? AND org_id = ?", uid, orgID).First(ou).Error
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
_, err = x.Id(ou.ID).AllCols().Update(ou) return db.Model(ou).Where("id = ?", ou.ID).Updates(ou).Error
return err
} }
// AddOrgUser adds new user to given organization. // AddOrgUser adds new user to given organization.
@@ -337,35 +336,33 @@ func AddOrgUser(orgID, uid int64) error {
return nil return nil
} }
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() ou := &OrgUser{
if err := sess.Begin(); err != nil { UID: uid,
return err OrgID: orgID,
} }
ou := &OrgUser{ if err := tx.Create(ou).Error; err != nil {
UID: uid, return err
OrgID: orgID, }
} if err := tx.Exec("UPDATE `user` SET num_members = num_members + 1 WHERE id = ?", orgID).Error; err != nil {
return err
}
if _, err := sess.Insert(ou); err != nil { return nil
return err })
} else if _, err = sess.Exec("UPDATE `user` SET num_members = num_members + 1 WHERE id = ?", orgID); err != nil {
return err
}
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)
has, err := x.Where("uid=?", userID).And("org_id=?", orgID).Get(ou) err := db.Where("uid = ? AND org_id = ?", userID, orgID).First(ou).Error
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)
@@ -394,71 +391,69 @@ func RemoveOrgUser(orgID, userID int64) error {
} }
} }
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() if err := tx.Where("id = ?", ou.ID).Delete(ou).Error; err != nil {
if err := sess.Begin(); err != nil {
return err
}
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
}
// Delete all repository accesses and unwatch them.
repoIDs := make([]int64, 0, len(repos))
for i := range repos {
repoIDs = append(repoIDs, repos[i].ID)
if err = watchRepo(sess, user.ID, repos[i].ID, false); 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 len(repoIDs) > 0 {
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 all repository accesses and unwatch them.
teams, err := getUserTeams(sess, org.ID, user.ID) repoIDs := make([]int64, 0, len(repos))
if err != nil { for i := range repos {
return err repoIDs = append(repoIDs, repos[i].ID)
} if err = watchRepo(tx, user.ID, repos[i].ID, false); err != nil {
for _, t := range teams { return err
if err = removeTeamMember(sess, org.ID, t.ID, user.ID); err != nil { }
}
if len(repoIDs) > 0 {
if err := tx.Where("user_id = ? AND repo_id IN ?", user.ID, repoIDs).Delete(new(Access)).Error; err != nil {
return err
}
}
// Delete member in his/her teams.
teams, err := getUserTeams(tx, org.ID, user.ID)
if err != nil {
return err return err
} }
} for _, t := range teams {
if err = removeTeamMember(tx, org.ID, t.ID, user.ID); err != nil {
return err
}
}
return sess.Commit() return nil
})
} }
func removeOrgRepo(e Engine, orgID, repoID int64) error { func removeOrgRepo(tx *gorm.DB, orgID, repoID int64) error {
_, err := e.Delete(&TeamRepo{ return tx.Where("org_id = ? AND repo_id = ?", orgID, repoID).Delete(&TeamRepo{}).Error
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(x, orgID, repoID) return removeOrgRepo(db, orgID, repoID)
} }
func (org *User) getUserTeams(e Engine, userID int64, cols ...string) ([]*Team, error) { func (org *User) getUserTeams(tx *gorm.DB, userID int64, cols ...string) ([]*Team, error) {
teams := make([]*Team, 0, org.NumTeams) teams := make([]*Team, 0, org.NumTeams)
return teams, e.Where("team_user.org_id = ?", org.ID). query := tx.Table("team").
And("team_user.uid = ?", userID). Joins("INNER JOIN team_user ON team_user.team_id = team.id").
Join("INNER", "team_user", "team_user.team_id = team.id"). Where("team_user.org_id = ? AND team_user.uid = ?", org.ID, userID)
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(x, userID, "team.id") teams, err := org.getUserTeams(db, 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)
} }
@@ -473,7 +468,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(x, userID) return org.getUserTeams(db, 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,
@@ -489,7 +484,7 @@ func (org *User) GetUserRepositories(userID int64, page, pageSize int) ([]*Repos
} }
var teamRepoIDs []int64 var teamRepoIDs []int64
if err = x.Table("team_repo").In("team_id", teamIDs).Distinct("repo_id").Find(&teamRepoIDs); err != nil { if err = db.Table("team_repo").Where("team_id IN ?", teamIDs).Distinct("repo_id").Find(&teamRepoIDs).Error; 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 {
@@ -501,22 +496,18 @@ 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 = x.Where("owner_id = ?", org.ID). if err = db.Where("owner_id = ?", org.ID).
And(builder.Or( Where(db.Where("is_private = ? AND is_unlisted = ?", false, false).Or("id IN ?", teamRepoIDs)).
builder.And(builder.Expr("is_private = ?", false), builder.Expr("is_unlisted = ?", false)), Order("updated_unix DESC").
builder.In("id", teamRepoIDs))). Limit(pageSize).Offset((page - 1) * pageSize).
Desc("updated_unix"). Find(&repos).Error; err != nil {
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)
} }
repoCount, err := x.Where("owner_id = ?", org.ID). var repoCount int64
And(builder.Or( if err = db.Model(&Repository{}).Where("owner_id = ?", org.ID).
builder.Expr("is_private = ?", false), Where(db.Where("is_private = ?", false).Or("id IN ?", teamRepoIDs)).
builder.In("id", teamRepoIDs))). Count(&repoCount).Error; err != nil {
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)
} }
@@ -534,7 +525,7 @@ func (org *User) GetUserMirrorRepositories(userID int64) ([]*Repository, error)
} }
var teamRepoIDs []int64 var teamRepoIDs []int64
err = x.Table("team_repo").In("team_id", teamIDs).Distinct("repo_id").Find(&teamRepoIDs) err = db.Table("team_repo").Where("team_id IN ?", teamIDs).Distinct("repo_id").Find(&teamRepoIDs).Error
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)
} }
@@ -544,12 +535,12 @@ func (org *User) GetUserMirrorRepositories(userID int64) ([]*Repository, error)
} }
repos := make([]*Repository, 0, 10) repos := make([]*Repository, 0, 10)
if err = x.Where("owner_id = ?", org.ID). if err = db.Where("owner_id = ?", org.ID).
And("is_private = ?", false). Where("is_private = ?", false).
Or(builder.In("id", teamRepoIDs)). Or("id IN ?", teamRepoIDs).
And("is_mirror = ?", true). // Don't move up because it's an independent condition Where("is_mirror = ?", true). // Don't move up because it's an independent condition
Desc("updated_unix"). Order("updated_unix DESC").
Find(&repos); err != nil { Find(&repos).Error; 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"
"xorm.io/xorm" "gorm.io/gorm"
"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 `xorm:"INDEX"` OrgID int64 `gorm:"index"`
LowerName string LowerName string
Name string Name string
Description string Description string
@@ -27,14 +27,12 @@ type Team struct {
NumMembers int NumMembers int
} }
func (t *Team) AfterSet(colName string, _ xorm.Cell) { func (t *Team) AfterFind(tx *gorm.DB) error {
switch colName { // LEGACY [1.0]: this is backward compatibility bug fix for https://gogs.io/gogs/issues/3671
case "num_repos": if t.NumRepos < 0 {
// LEGACY [1.0]: this is backward compatibility bug fix for https://gogs.io/gogs/issues/3671 t.NumRepos = 0
if t.NumRepos < 0 {
t.NumRepos = 0
}
} }
return nil
} }
// IsOwnerTeam returns true if team is owner team. // IsOwnerTeam returns true if team is owner team.
@@ -52,15 +50,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(e Engine) (err error) { func (t *Team) getRepositories(tx *gorm.DB) (err error) {
teamRepos := make([]*TeamRepo, 0, t.NumRepos) teamRepos := make([]*TeamRepo, 0, t.NumRepos)
if err = x.Where("team_id=?", t.ID).Find(&teamRepos); err != nil { if err = tx.Where("team_id = ?", t.ID).Find(&teamRepos).Error; 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(e, teamRepos[i].RepoID) repo, err := getRepositoryByID(tx, 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)
} }
@@ -71,17 +69,17 @@ func (t *Team) getRepositories(e Engine) (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(x) return t.getRepositories(db)
} }
func (t *Team) getMembers(e Engine) (err error) { func (t *Team) getMembers(tx *gorm.DB) (err error) {
t.Members, err = getTeamMembers(e, t.ID) t.Members, err = getTeamMembers(tx, 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(x) return t.getMembers(db)
} }
// AddMember adds new membership of the team to the organization, // AddMember adds new membership of the team to the organization,
@@ -95,34 +93,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(e Engine, repoID int64) bool { func (t *Team) hasRepository(tx *gorm.DB, repoID int64) bool {
return hasTeamRepo(e, t.OrgID, t.ID, repoID) return hasTeamRepo(tx, 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(x, repoID) return t.hasRepository(db, repoID)
} }
func (t *Team) addRepository(e Engine, repo *Repository) (err error) { func (t *Team) addRepository(tx *gorm.DB, repo *Repository) (err error) {
if err = addTeamRepo(e, t.OrgID, t.ID, repo.ID); err != nil { if err = addTeamRepo(tx, t.OrgID, t.ID, repo.ID); err != nil {
return err return err
} }
t.NumRepos++ t.NumRepos++
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 errors.Newf("update team: %v", err) return errors.Newf("update team: %v", err)
} }
if err = repo.recalculateTeamAccesses(e, 0); err != nil { if err = repo.recalculateTeamAccesses(tx, 0); err != nil {
return errors.Newf("recalculateAccesses: %v", err) return errors.Newf("recalculateAccesses: %v", err)
} }
if err = t.getMembers(e); err != nil { if err = t.getMembers(tx); 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(e, u.ID, repo.ID, true); err != nil { if err = watchRepo(tx, u.ID, repo.ID, true); err != nil {
return errors.Newf("watchRepo: %v", err) return errors.Newf("watchRepo: %v", err)
} }
} }
@@ -137,42 +135,34 @@ func (t *Team) AddRepository(repo *Repository) (err error) {
return nil return nil
} }
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() return t.addRepository(tx, repo)
if err = sess.Begin(); err != nil { })
return err
}
if err = t.addRepository(sess, repo); err != nil {
return err
}
return sess.Commit()
} }
func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (err error) { func (t *Team) removeRepository(tx *gorm.DB, repo *Repository, recalculate bool) (err error) {
if err = removeTeamRepo(e, t.ID, repo.ID); err != nil { if err = removeTeamRepo(tx, t.ID, repo.ID); err != nil {
return err return err
} }
t.NumRepos-- t.NumRepos--
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
} }
// 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(e, t.ID); err != nil { if err = repo.recalculateTeamAccesses(tx, t.ID); err != nil {
return err return err
} }
} }
if err = t.getMembers(e); err != nil { if err = t.getMembers(tx); 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(e Engine, userID int64, repo *Repository) (AccessMode, error) { userAccessMode := func(tx *gorm.DB, 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 {
@@ -191,26 +181,29 @@ func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (e
UserID: userID, UserID: userID,
RepoID: repo.ID, RepoID: repo.ID,
} }
if has, err := e.Get(access); !has || err != nil { err := tx.Where("user_id = ? AND repo_id = ?", userID, repo.ID).First(access).Error
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(e Engine, userID int64, repo *Repository, testMode AccessMode) (bool, error) { hasAccess := func(tx *gorm.DB, userID int64, repo *Repository, testMode AccessMode) (bool, error) {
mode, err := userAccessMode(e, userID, repo) mode, err := userAccessMode(tx, userID, repo)
return mode >= testMode, err return mode >= testMode, err
} }
for _, member := range t.Members { for _, member := range t.Members {
has, err := hasAccess(e, member.ID, repo, AccessModeRead) has, err := hasAccess(tx, 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(e, member.ID, repo.ID, false); err != nil { if err = watchRepo(tx, member.ID, repo.ID, false); err != nil {
return err return err
} }
} }
@@ -229,17 +222,9 @@ func (t *Team) RemoveRepository(repoID int64) error {
return err return err
} }
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() return t.removeRepository(tx, repo, true)
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{}{
@@ -264,37 +249,30 @@ func NewTeam(t *Team) error {
return err return err
} }
has, err := x.Id(t.OrgID).Get(new(User)) err := db.Where("id = ?", t.OrgID).First(new(User)).Error
if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) {
return err
} else if !has {
return ErrOrgNotExist return ErrOrgNotExist
} else if err != nil {
return err
} }
t.LowerName = strings.ToLower(t.Name) t.LowerName = strings.ToLower(t.Name)
existingTeam := Team{} existingTeam := Team{}
has, err = x.Where("org_id=?", t.OrgID).And("lower_name=?", t.LowerName).Get(&existingTeam) err = db.Where("org_id = ? AND lower_name = ?", t.OrgID, t.LowerName).First(&existingTeam).Error
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
} }
if _, err = sess.Insert(t); err != nil { return db.Transaction(func(tx *gorm.DB) error {
return err if err := tx.Create(t).Error; err != nil {
} return err
}
// Update organization number of teams. // Update organization number of teams.
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil { return tx.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID).Error
return err })
}
return sess.Commit()
} }
var _ errutil.NotFound = (*ErrTeamNotExist)(nil) var _ errutil.NotFound = (*ErrTeamNotExist)(nil)
@@ -316,49 +294,46 @@ func (ErrTeamNotExist) NotFound() bool {
return true return true
} }
func getTeamOfOrgByName(e Engine, orgID int64, name string) (*Team, error) { func getTeamOfOrgByName(tx *gorm.DB, orgID int64, name string) (*Team, error) {
t := &Team{ t := new(Team)
OrgID: orgID, err := tx.Where("org_id = ? AND lower_name = ?", orgID, strings.ToLower(name)).First(t).Error
LowerName: strings.ToLower(name), if errors.Is(err, gorm.ErrRecordNotFound) {
}
has, err := e.Get(t)
if err != nil {
return nil, err
} else if !has {
return nil, ErrTeamNotExist{args: map[string]any{"orgID": orgID, "name": name}} return nil, ErrTeamNotExist{args: map[string]any{"orgID": orgID, "name": name}}
} else if err != nil {
return nil, err
} }
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(x, orgID, name) return getTeamOfOrgByName(db, orgID, name)
} }
func getTeamByID(e Engine, teamID int64) (*Team, error) { func getTeamByID(tx *gorm.DB, teamID int64) (*Team, error) {
t := new(Team) t := new(Team)
has, err := e.ID(teamID).Get(t) err := tx.Where("id = ?", teamID).First(t).Error
if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
} else if !has {
return nil, ErrTeamNotExist{args: map[string]any{"teamID": teamID}} return nil, ErrTeamNotExist{args: map[string]any{"teamID": teamID}}
} else if err != nil {
return nil, err
} }
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(x, teamID) return getTeamByID(db, teamID)
} }
func getTeamsByOrgID(e Engine, orgID int64) ([]*Team, error) { func getTeamsByOrgID(tx *gorm.DB, orgID int64) ([]*Team, error) {
teams := make([]*Team, 0, 3) teams := make([]*Team, 0, 3)
return teams, e.Where("org_id = ?", orgID).Find(&teams) return teams, tx.Where("org_id = ?", orgID).Find(&teams).Error
} }
// 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(x, orgID) return getTeamsByOrgID(db, orgID)
} }
// UpdateTeam updates information of team. // UpdateTeam updates information of team.
@@ -371,39 +346,35 @@ func UpdateTeam(t *Team, authChanged bool) (err error) {
t.Description = t.Description[:255] t.Description = t.Description[:255]
} }
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
t.LowerName = strings.ToLower(t.Name) t.LowerName = strings.ToLower(t.Name)
existingTeam := new(Team) existingTeam := new(Team)
has, err := x.Where("org_id=?", t.OrgID).And("lower_name=?", t.LowerName).And("id!=?", t.ID).Get(existingTeam) err = db.Where("org_id = ? AND lower_name = ? AND id != ?", t.OrgID, t.LowerName, t.ID).First(existingTeam).Error
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) {
return err
} }
if _, err = sess.ID(t.ID).AllCols().Update(t); err != nil { return db.Transaction(func(tx *gorm.DB) error {
return errors.Newf("update: %v", err) if err := tx.Model(&Team{}).Where("id = ?", t.ID).Updates(t).Error; err != nil {
} return errors.Newf("update: %v", err)
// Update access for team members if needed.
if authChanged {
if err = t.getRepositories(sess); err != nil {
return errors.Newf("getRepositories:%v", err)
} }
for _, repo := range t.Repos { // Update access for team members if needed.
if err = repo.recalculateTeamAccesses(sess, 0); err != nil { if authChanged {
return errors.Newf("recalculateTeamAccesses: %v", err) if err := t.getRepositories(tx); err != nil {
return errors.Newf("getRepositories:%v", err)
}
for _, repo := range t.Repos {
if err := repo.recalculateTeamAccesses(tx, 0); err != nil {
return errors.Newf("recalculateTeamAccesses: %v", err)
}
} }
} }
}
return sess.Commit() return nil
})
} }
// DeleteTeam deletes given team. // DeleteTeam deletes given team.
@@ -419,34 +390,26 @@ func DeleteTeam(t *Team) error {
return err return err
} }
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() // Delete all accesses.
if err = sess.Begin(); err != nil { for _, repo := range t.Repos {
return err if err := repo.recalculateTeamAccesses(tx, t.ID); err != nil {
} return err
}
}
// Delete all accesses. // Delete team-user.
for _, repo := range t.Repos { if err := tx.Where("org_id = ? AND team_id = ?", org.ID, t.ID).Delete(new(TeamUser)).Error; err != nil {
if err = repo.recalculateTeamAccesses(sess, t.ID); err != nil {
return err return err
} }
}
// Delete team-user. // Delete team.
if _, err = sess.Where("org_id=?", org.ID).Where("team_id=?", t.ID).Delete(new(TeamUser)); err != nil { if err := tx.Where("id = ?", t.ID).Delete(new(Team)).Error; err != nil {
return err return err
} }
// Update organization number of teams.
// Delete team. return tx.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id = ?", t.OrgID).Error
if _, err = sess.ID(t.ID).Delete(new(Team)); err != nil { })
return err
}
// Update organization number of teams.
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil {
return err
}
return sess.Commit()
} }
// ___________ ____ ___ // ___________ ____ ___
@@ -459,31 +422,30 @@ 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 `xorm:"INDEX"` OrgID int64 `gorm:"index"`
TeamID int64 `xorm:"UNIQUE(s)"` TeamID int64 `gorm:"uniqueIndex:team_user_team_id_uid"`
UID int64 `xorm:"UNIQUE(s)"` UID int64 `gorm:"uniqueIndex:team_user_team_id_uid"`
} }
func isTeamMember(e Engine, orgID, teamID, uid int64) bool { func isTeamMember(tx *gorm.DB, orgID, teamID, uid int64) bool {
has, _ := e.Where("org_id=?", orgID).And("team_id=?", teamID).And("uid=?", uid).Get(new(TeamUser)) err := tx.Where("org_id = ? AND team_id = ? AND uid = ?", orgID, teamID, uid).First(new(TeamUser)).Error
return has return err == nil
} }
// 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(x, orgID, teamID, uid) return isTeamMember(db, orgID, teamID, uid)
} }
func getTeamMembers(e Engine, teamID int64) (_ []*User, err error) { func getTeamMembers(tx *gorm.DB, teamID int64) (_ []*User, err error) {
teamUsers := make([]*TeamUser, 0, 10) teamUsers := make([]*TeamUser, 0, 10)
if err = e.Sql("SELECT `id`, `org_id`, `team_id`, `uid` FROM `team_user` WHERE team_id = ?", teamID). if err = tx.Select("id, org_id, team_id, uid").Where("team_id = ?", teamID).Find(&teamUsers).Error; err != nil {
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 = e.ID(teamUsers[i].UID).Get(member); err != nil { if err = tx.Where("id = ?", teamUsers[i].UID).First(member).Error; 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)
@@ -493,12 +455,12 @@ func getTeamMembers(e Engine, 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(x, teamID) return getTeamMembers(db, teamID)
} }
func getUserTeams(e Engine, orgID, userID int64) ([]*Team, error) { func getUserTeams(tx *gorm.DB, orgID, userID int64) ([]*Team, error) {
teamUsers := make([]*TeamUser, 0, 5) teamUsers := make([]*TeamUser, 0, 5)
if err := e.Where("uid = ?", userID).And("org_id = ?", orgID).Find(&teamUsers); err != nil { if err := tx.Where("uid = ? AND org_id = ?", userID, orgID).Find(&teamUsers).Error; err != nil {
return nil, err return nil, err
} }
@@ -509,12 +471,12 @@ func getUserTeams(e Engine, 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, e.Where("org_id = ?", orgID).In("id", teamIDs).Find(&teams) return teams, tx.Where("org_id = ? AND id IN ?", orgID, teamIDs).Find(&teams).Error
} }
// 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(x, orgID, userID) return getUserTeams(db, orgID, userID)
} }
// AddTeamMember adds new membership of given team to given organization, // AddTeamMember adds new membership of given team to given organization,
@@ -539,53 +501,46 @@ func AddTeamMember(orgID, teamID, userID int64) error {
return err return err
} }
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() tu := &TeamUser{
if err = sess.Begin(); err != nil { UID: userID,
return err OrgID: orgID,
} TeamID: teamID,
}
tu := &TeamUser{ if err := tx.Create(tu).Error; err != nil {
UID: userID, return err
OrgID: orgID, }
TeamID: teamID, if err := tx.Model(&Team{}).Where("id = ?", t.ID).Updates(t).Error; err != nil {
}
if _, err = sess.Insert(tu); err != nil {
return err
} else if _, err = sess.ID(t.ID).Update(t); err != nil {
return err
}
// Give access to team repositories.
for _, repo := range t.Repos {
if err = repo.recalculateTeamAccesses(sess, 0); err != nil {
return err return err
} }
}
// We make sure it exists before. // Give access to team repositories.
ou := new(OrgUser) for _, repo := range t.Repos {
if _, err = sess.Where("uid = ?", userID).And("org_id = ?", orgID).Get(ou); err != nil { if err := repo.recalculateTeamAccesses(tx, 0); err != nil {
return err return err
} }
ou.NumTeams++ }
if t.IsOwnerTeam() {
ou.IsOwner = true
}
if _, err = sess.ID(ou.ID).AllCols().Update(ou); err != nil {
return err
}
return sess.Commit() // We make sure it exists before.
ou := new(OrgUser)
if err := tx.Where("uid = ? AND org_id = ?", userID, orgID).First(ou).Error; err != nil {
return err
}
ou.NumTeams++
if t.IsOwnerTeam() {
ou.IsOwner = true
}
return tx.Model(&OrgUser{}).Where("id = ?", ou.ID).Updates(ou).Error
})
} }
func removeTeamMember(e Engine, orgID, teamID, uid int64) error { func removeTeamMember(tx *gorm.DB, orgID, teamID, uid int64) error {
if !isTeamMember(e, orgID, teamID, uid) { if !isTeamMember(tx, orgID, teamID, uid) {
return nil return nil
} }
// Get team and its repositories. // Get team and its repositories.
t, err := getTeamByID(e, teamID) t, err := getTeamByID(tx, teamID)
if err != nil { if err != nil {
return err return err
} }
@@ -597,12 +552,12 @@ func removeTeamMember(e Engine, orgID, teamID, uid int64) error {
t.NumMembers-- t.NumMembers--
if err = t.getRepositories(e); err != nil { if err = t.getRepositories(tx); err != nil {
return err return err
} }
// Get organization. // Get organization.
org, err := getUserByID(e, orgID) org, err := getUserByID(tx, orgID)
if err != nil { if err != nil {
return err return err
} }
@@ -612,46 +567,37 @@ func removeTeamMember(e Engine, orgID, teamID, uid int64) error {
OrgID: orgID, OrgID: orgID,
TeamID: teamID, TeamID: teamID,
} }
if _, err := e.Delete(tu); err != nil { if err := tx.Where("uid = ? AND org_id = ? AND team_id = ?", uid, orgID, teamID).Delete(tu).Error; 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(e, 0); err != nil { if err = repo.recalculateTeamAccesses(tx, 0); err != nil {
return err return err
} }
} }
// This must exist. // This must exist.
ou := new(OrgUser) ou := new(OrgUser)
_, err = e.Where("uid = ?", uid).And("org_id = ?", org.ID).Get(ou) if err = tx.Where("uid = ? AND org_id = ?", uid, org.ID).First(ou).Error; err != nil {
if err != nil {
return err return err
} }
ou.NumTeams-- ou.NumTeams--
if t.IsOwnerTeam() { if t.IsOwnerTeam() {
ou.IsOwner = false ou.IsOwner = false
} }
if _, err = e.ID(ou.ID).AllCols().Update(ou); err != nil { return tx.Model(&OrgUser{}).Where("id = ?", ou.ID).Updates(ou).Error
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 {
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() return removeTeamMember(tx, orgID, teamID, uid)
if err := sess.Begin(); err != nil { })
return err
}
if err := removeTeamMember(sess, orgID, teamID, uid); err != nil {
return err
}
return sess.Commit()
} }
// ___________ __________ // ___________ __________
@@ -664,54 +610,49 @@ 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 `xorm:"INDEX"` OrgID int64 `gorm:"index"`
TeamID int64 `xorm:"UNIQUE(s)"` TeamID int64 `gorm:"uniqueIndex:team_repo_team_id_repo_id"`
RepoID int64 `xorm:"UNIQUE(s)"` RepoID int64 `gorm:"uniqueIndex:team_repo_team_id_repo_id"`
} }
func hasTeamRepo(e Engine, orgID, teamID, repoID int64) bool { func hasTeamRepo(tx *gorm.DB, orgID, teamID, repoID int64) bool {
has, _ := e.Where("org_id = ?", orgID).And("team_id = ?", teamID).And("repo_id = ?", repoID).Get(new(TeamRepo)) err := tx.Where("org_id = ? AND team_id = ? AND repo_id = ?", orgID, teamID, repoID).First(new(TeamRepo)).Error
return has return err == nil
} }
// 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(x, orgID, teamID, repoID) return hasTeamRepo(db, orgID, teamID, repoID)
} }
func addTeamRepo(e Engine, orgID, teamID, repoID int64) error { func addTeamRepo(tx *gorm.DB, orgID, teamID, repoID int64) error {
_, err := e.InsertOne(&TeamRepo{ return tx.Create(&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(x, orgID, teamID, repoID) return addTeamRepo(db, orgID, teamID, repoID)
} }
func removeTeamRepo(e Engine, teamID, repoID int64) error { func removeTeamRepo(tx *gorm.DB, teamID, repoID int64) error {
_, err := e.Delete(&TeamRepo{ return tx.Where("team_id = ? AND repo_id = ?", teamID, repoID).Delete(new(TeamRepo)).Error
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(x, teamID, repoID) return removeTeamRepo(db, 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, x.Where("team.authorize >= ?", mode). return teams, db.Table("team").
Join("INNER", "team_repo", "team_repo.team_id = team.id"). Where("team.authorize >= ?", mode).
And("team_repo.org_id = ?", orgID). Joins("INNER JOIN team_repo ON team_repo.team_id = team.id").
And("team_repo.repo_id = ?", repoID). Where("team_repo.org_id = ? AND team_repo.repo_id = ?", orgID, repoID).
Find(&teams) Find(&teams).Error
} }

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,35 +71,31 @@ 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) AfterSet(colName string, _ xorm.Cell) { func (pr *PullRequest) AfterFind(tx *gorm.DB) error {
switch colName { if pr.HasMerged {
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(e Engine) (err error) { func (pr *PullRequest) loadAttributes(db *gorm.DB) (err error) {
if pr.HeadRepo == nil { if pr.HeadRepo == nil {
pr.HeadRepo, err = getRepositoryByID(e, pr.HeadRepoID) pr.HeadRepo, err = getRepositoryByID(db, 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(e, pr.BaseRepoID) pr.BaseRepo, err = getRepositoryByID(db, 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(e, pr.MergerID) pr.Merger, err = getUserByID(db, pr.MergerID)
if IsErrUserNotExist(err) { if IsErrUserNotExist(err) {
pr.MergerID = -1 pr.MergerID = -1
pr.Merger = NewGhostUser() pr.Merger = NewGhostUser()
@@ -112,7 +108,7 @@ func (pr *PullRequest) loadAttributes(e Engine) (err error) {
} }
func (pr *PullRequest) LoadAttributes() error { func (pr *PullRequest) LoadAttributes() error {
return pr.loadAttributes(x) return pr.loadAttributes(db)
} }
func (pr *PullRequest) LoadIssue() (err error) { func (pr *PullRequest) LoadIssue() (err error) {
@@ -199,198 +195,190 @@ 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)
}() }()
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() if err := pr.Issue.changeStatus(tx, doer, pr.Issue.Repo, true); err != nil {
if err = sess.Begin(); err != nil { return errors.Newf("Issue.changeStatus: %v", err)
return err
}
if err = pr.Issue.changeStatus(sess, doer, pr.Issue.Repo, true); err != nil {
return errors.Newf("Issue.changeStatus: %v", err)
}
headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
headGitRepo, err := git.Open(headRepoPath)
if err != nil {
return errors.Newf("open repository: %v", err)
}
// Create temporary directory to store temporary copy of the base repository,
// and clean it up when operation finished regardless of succeed or not.
tmpBasePath := filepath.Join(conf.Server.AppDataPath, "tmp", "repos", com.ToStr(time.Now().Nanosecond())+".git")
if err = os.MkdirAll(filepath.Dir(tmpBasePath), os.ModePerm); err != nil {
return err
}
defer func() {
_ = os.RemoveAll(filepath.Dir(tmpBasePath))
}()
// Clone the base repository to the defined temporary directory,
// and checks out to base branch directly.
var stderr string
if _, stderr, err = process.ExecTimeout(5*time.Minute,
fmt.Sprintf("PullRequest.Merge (git clone): %s", tmpBasePath),
"git", "clone", "-b", pr.BaseBranch, baseGitRepo.Path(), tmpBasePath); err != nil {
return errors.Newf("git clone: %s", stderr)
}
// Add remote which points to the head repository.
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git remote add): %s", tmpBasePath),
"git", "remote", "add", "head_repo", headRepoPath); err != nil {
return errors.Newf("git remote add [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
}
// Fetch information from head repository to the temporary copy.
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git fetch): %s", tmpBasePath),
"git", "fetch", "head_repo"); err != nil {
return errors.Newf("git fetch [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
}
remoteHeadBranch := "head_repo/" + pr.HeadBranch
// Check if merge style is allowed, reset to default style if not
if mergeStyle == MergeStyleRebase && !pr.BaseRepo.PullsAllowRebase {
mergeStyle = MergeStyleRegular
}
switch mergeStyle {
case MergeStyleRegular: // Create merge commit
// Merge changes from head branch.
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git merge --no-ff --no-commit): %s", tmpBasePath),
"git", "merge", "--no-ff", "--no-commit", remoteHeadBranch); err != nil {
return errors.Newf("git merge --no-ff --no-commit [%s]: %v - %s", tmpBasePath, err, stderr)
} }
// Create a merge commit for the base branch. headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
if _, stderr, err = process.ExecDir(-1, tmpBasePath, headGitRepo, err := git.Open(headRepoPath)
fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath), if err != nil {
"git", "commit", fmt.Sprintf("--author='%s <%s>'", doer.DisplayName(), doer.Email), return errors.Newf("open repository: %v", err)
"-m", fmt.Sprintf("Merge branch '%s' of %s/%s into %s", pr.HeadBranch, pr.HeadUserName, pr.HeadRepo.Name, pr.BaseBranch),
"-m", commitDescription); err != nil {
return errors.Newf("git commit [%s]: %v - %s", tmpBasePath, err, stderr)
} }
case MergeStyleRebase: // Rebase before merging // Create temporary directory to store temporary copy of the base repository,
// and clean it up when operation finished regardless of succeed or not.
tmpBasePath := filepath.Join(conf.Server.AppDataPath, "tmp", "repos", com.ToStr(time.Now().Nanosecond())+".git")
if err = os.MkdirAll(filepath.Dir(tmpBasePath), os.ModePerm); err != nil {
return err
}
defer func() {
_ = os.RemoveAll(filepath.Dir(tmpBasePath))
}()
// Rebase head branch based on base branch, this creates a non-branch commit state. // Clone the base repository to the defined temporary directory,
if _, stderr, err = process.ExecDir(-1, tmpBasePath, // and checks out to base branch directly.
fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath), var stderr string
"git", "rebase", "--quiet", pr.BaseBranch, remoteHeadBranch); err != nil { if _, stderr, err = process.ExecTimeout(5*time.Minute,
return errors.Newf("git rebase [%s on %s]: %s", remoteHeadBranch, pr.BaseBranch, stderr) fmt.Sprintf("PullRequest.Merge (git clone): %s", tmpBasePath),
"git", "clone", "-b", pr.BaseBranch, baseGitRepo.Path(), tmpBasePath); err != nil {
return errors.Newf("git clone: %s", stderr)
} }
// Name non-branch commit state to a new temporary branch in order to save changes. // Add remote which points to the head repository.
tmpBranch := com.ToStr(time.Now().UnixNano(), 10)
if _, stderr, err = process.ExecDir(-1, tmpBasePath, if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath), fmt.Sprintf("PullRequest.Merge (git remote add): %s", tmpBasePath),
"git", "checkout", "-b", tmpBranch); err != nil { "git", "remote", "add", "head_repo", headRepoPath); err != nil {
return errors.Newf("git checkout '%s': %s", tmpBranch, stderr) return errors.Newf("git remote add [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
} }
// Check out the base branch to be operated on. // Fetch information from head repository to the temporary copy.
if _, stderr, err = process.ExecDir(-1, tmpBasePath, if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath), fmt.Sprintf("PullRequest.Merge (git fetch): %s", tmpBasePath),
"git", "checkout", pr.BaseBranch); err != nil { "git", "fetch", "head_repo"); err != nil {
return errors.Newf("git checkout '%s': %s", pr.BaseBranch, stderr) return errors.Newf("git fetch [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
} }
// Merge changes from temporary branch to the base branch. remoteHeadBranch := "head_repo/" + pr.HeadBranch
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath), // Check if merge style is allowed, reset to default style if not
"git", "merge", tmpBranch); err != nil { if mergeStyle == MergeStyleRebase && !pr.BaseRepo.PullsAllowRebase {
return errors.Newf("git merge [%s]: %v - %s", tmpBasePath, err, stderr) mergeStyle = MergeStyleRegular
} }
default: switch mergeStyle {
return errors.Newf("unknown merge style: %s", mergeStyle) case MergeStyleRegular: // Create merge commit
}
// Push changes on base branch to upstream. // Merge changes from head branch.
if _, stderr, err = process.ExecDir(-1, tmpBasePath, if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git push): %s", tmpBasePath), fmt.Sprintf("PullRequest.Merge (git merge --no-ff --no-commit): %s", tmpBasePath),
"git", "push", baseGitRepo.Path(), pr.BaseBranch); err != nil { "git", "merge", "--no-ff", "--no-commit", remoteHeadBranch); err != nil {
return errors.Newf("git push: %s", stderr) return errors.Newf("git merge --no-ff --no-commit [%s]: %v - %s", tmpBasePath, err, stderr)
} }
pr.MergedCommitID, err = headGitRepo.BranchCommitID(pr.HeadBranch) // Create a merge commit for the base branch.
if err != nil { if _, stderr, err = process.ExecDir(-1, tmpBasePath,
return errors.Newf("get head branch %q commit ID: %v", pr.HeadBranch, err) fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath),
} "git", "commit", fmt.Sprintf("--author='%s <%s>'", doer.DisplayName(), doer.Email),
"-m", fmt.Sprintf("Merge branch '%s' of %s/%s into %s", pr.HeadBranch, pr.HeadUserName, pr.HeadRepo.Name, pr.BaseBranch),
"-m", commitDescription); err != nil {
return errors.Newf("git commit [%s]: %v - %s", tmpBasePath, err, stderr)
}
pr.HasMerged = true case MergeStyleRebase: // Rebase before merging
pr.Merged = time.Now()
pr.MergerID = doer.ID
if _, err = sess.ID(pr.ID).AllCols().Update(pr); err != nil {
return errors.Newf("update pull request: %v", err)
}
if err = sess.Commit(); err != nil { // Rebase head branch based on base branch, this creates a non-branch commit state.
return errors.Newf("commit: %v", err) if _, stderr, err = process.ExecDir(-1, tmpBasePath,
} fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath),
"git", "rebase", "--quiet", pr.BaseBranch, remoteHeadBranch); err != nil {
return errors.Newf("git rebase [%s on %s]: %s", remoteHeadBranch, pr.BaseBranch, stderr)
}
if err = Handle.Actions().MergePullRequest(ctx, doer, pr.Issue.Repo.Owner, pr.Issue.Repo, pr.Issue); err != nil { // Name non-branch commit state to a new temporary branch in order to save changes.
log.Error("Failed to create action for merge pull request, pull_request_id: %d, error: %v", pr.ID, err) tmpBranch := com.ToStr(time.Now().UnixNano(), 10)
} if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath),
"git", "checkout", "-b", tmpBranch); err != nil {
return errors.Newf("git checkout '%s': %s", tmpBranch, stderr)
}
// Reload pull request information. // Check out the base branch to be operated on.
if err = pr.LoadAttributes(); err != nil { if _, stderr, err = process.ExecDir(-1, tmpBasePath,
log.Error("LoadAttributes: %v", err) fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath),
"git", "checkout", pr.BaseBranch); err != nil {
return errors.Newf("git checkout '%s': %s", pr.BaseBranch, stderr)
}
// Merge changes from temporary branch to the base branch.
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath),
"git", "merge", tmpBranch); err != nil {
return errors.Newf("git merge [%s]: %v - %s", tmpBasePath, err, stderr)
}
default:
return errors.Newf("unknown merge style: %s", mergeStyle)
}
// Push changes on base branch to upstream.
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git push): %s", tmpBasePath),
"git", "push", baseGitRepo.Path(), pr.BaseBranch); err != nil {
return errors.Newf("git push: %s", stderr)
}
pr.MergedCommitID, err = headGitRepo.BranchCommitID(pr.HeadBranch)
if err != nil {
return errors.Newf("get head branch %q commit ID: %v", pr.HeadBranch, err)
}
pr.HasMerged = true
pr.Merged = time.Now()
pr.MergerID = doer.ID
if err := tx.Model(&PullRequest{}).Where("id = ?", pr.ID).Updates(pr).Error; err != nil {
return errors.Newf("update pull request: %v", err)
}
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)
}
// Reload pull request information.
if err = pr.LoadAttributes(); err != nil {
log.Error("LoadAttributes: %v", err)
return nil
}
if err = PrepareWebhooks(pr.Issue.Repo, HookEventTypePullRequest, &api.PullRequestPayload{
Action: api.HOOK_ISSUE_CLOSED,
Index: pr.Index,
PullRequest: pr.APIFormat(),
Repository: pr.Issue.Repo.APIFormatLegacy(nil),
Sender: doer.APIFormat(),
}); err != nil {
log.Error("PrepareWebhooks: %v", err)
return nil
}
commits, err := headGitRepo.RevList([]string{pr.MergeBase + "..." + pr.MergedCommitID})
if err != nil {
log.Error("Failed to list commits [merge_base: %s, merged_commit_id: %s]: %v", pr.MergeBase, pr.MergedCommitID, err)
return nil
}
// NOTE: It is possible that head branch is not fully sync with base branch
// for merge commits, so we need to get latest head commit and append merge
// commit manually to avoid strange diff commits produced.
mergeCommit, err := baseGitRepo.BranchCommit(pr.BaseBranch)
if err != nil {
log.Error("Failed to get base branch %q commit: %v", pr.BaseBranch, err)
return nil
}
if mergeStyle == MergeStyleRegular {
commits = append([]*git.Commit{mergeCommit}, commits...)
}
pcs, err := CommitsToPushCommits(commits).APIFormat(ctx, Handle.Users(), pr.BaseRepo.RepoPath(), pr.BaseRepo.HTMLURL())
if err != nil {
log.Error("Failed to convert to API payload commits: %v", err)
return nil
}
p := &api.PushPayload{
Ref: git.RefsHeads + pr.BaseBranch,
Before: pr.MergeBase,
After: mergeCommit.ID.String(),
CompareURL: conf.Server.ExternalURL + pr.BaseRepo.ComposeCompareURL(pr.MergeBase, pr.MergedCommitID),
Commits: pcs,
Repo: pr.BaseRepo.APIFormatLegacy(nil),
Pusher: pr.HeadRepo.MustOwner().APIFormat(),
Sender: doer.APIFormat(),
}
if err = PrepareWebhooks(pr.BaseRepo, HookEventTypePush, p); err != nil {
log.Error("Failed to prepare webhooks: %v", err)
return nil
}
return nil return nil
} })
if err = PrepareWebhooks(pr.Issue.Repo, HookEventTypePullRequest, &api.PullRequestPayload{
Action: api.HOOK_ISSUE_CLOSED,
Index: pr.Index,
PullRequest: pr.APIFormat(),
Repository: pr.Issue.Repo.APIFormatLegacy(nil),
Sender: doer.APIFormat(),
}); err != nil {
log.Error("PrepareWebhooks: %v", err)
return nil
}
commits, err := headGitRepo.RevList([]string{pr.MergeBase + "..." + pr.MergedCommitID})
if err != nil {
log.Error("Failed to list commits [merge_base: %s, merged_commit_id: %s]: %v", pr.MergeBase, pr.MergedCommitID, err)
return nil
}
// NOTE: It is possible that head branch is not fully sync with base branch
// for merge commits, so we need to get latest head commit and append merge
// commit manually to avoid strange diff commits produced.
mergeCommit, err := baseGitRepo.BranchCommit(pr.BaseBranch)
if err != nil {
log.Error("Failed to get base branch %q commit: %v", pr.BaseBranch, err)
return nil
}
if mergeStyle == MergeStyleRegular {
commits = append([]*git.Commit{mergeCommit}, commits...)
}
pcs, err := CommitsToPushCommits(commits).APIFormat(ctx, Handle.Users(), pr.BaseRepo.RepoPath(), pr.BaseRepo.HTMLURL())
if err != nil {
log.Error("Failed to convert to API payload commits: %v", err)
return nil
}
p := &api.PushPayload{
Ref: git.RefsHeads + pr.BaseBranch,
Before: pr.MergeBase,
After: mergeCommit.ID.String(),
CompareURL: conf.Server.ExternalURL + pr.BaseRepo.ComposeCompareURL(pr.MergeBase, pr.MergedCommitID),
Commits: pcs,
Repo: pr.BaseRepo.APIFormatLegacy(nil),
Pusher: pr.HeadRepo.MustOwner().APIFormat(),
Sender: doer.APIFormat(),
}
if err = PrepareWebhooks(pr.BaseRepo, HookEventTypePush, p); err != nil {
log.Error("Failed to prepare webhooks: %v", err)
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.
@@ -443,45 +431,42 @@ 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) {
sess := x.NewSession() err = db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() if err := newIssue(tx, NewIssueOptions{
if err = sess.Begin(); err != nil { Repo: repo,
Issue: pull,
LableIDs: labelIDs,
Attachments: uuids,
IsPull: true,
}); err != nil {
return errors.Newf("newIssue: %v", err)
}
pr.Index = pull.Index
if err := repo.SavePatch(pr.Index, patch); err != nil {
return errors.Newf("SavePatch: %v", err)
}
pr.BaseRepo = repo
if err := pr.testPatch(); err != nil {
return errors.Newf("testPatch: %v", err)
}
// No conflict appears after test means mergeable.
if pr.Status == PullRequestStatusChecking {
pr.Status = PullRequestStatusMergeable
}
pr.IssueID = pull.ID
if err := tx.Create(pr).Error; err != nil {
return errors.Newf("insert pull repo: %v", err)
}
return nil
})
if err != nil {
return err return err
} }
if err = newIssue(sess, NewIssueOptions{
Repo: repo,
Issue: pull,
LableIDs: labelIDs,
Attachments: uuids,
IsPull: true,
}); err != nil {
return errors.Newf("newIssue: %v", err)
}
pr.Index = pull.Index
if err = repo.SavePatch(pr.Index, patch); err != nil {
return errors.Newf("SavePatch: %v", err)
}
pr.BaseRepo = repo
if err = pr.testPatch(); err != nil {
return errors.Newf("testPatch: %v", err)
}
// No conflict appears after test means mergeable.
if pr.Status == PullRequestStatusChecking {
pr.Status = PullRequestStatusMergeable
}
pr.IssueID = pull.ID
if _, err = sess.Insert(pr); err != nil {
return errors.Newf("insert pull repo: %v", err)
}
if err = sess.Commit(); err != nil {
return errors.Newf("commit: %v", err)
}
if err = NotifyWatchers(&Action{ if err = NotifyWatchers(&Action{
ActUserID: pull.Poster.ID, ActUserID: pull.Poster.ID,
ActUserName: pull.Poster.Name, ActUserName: pull.Poster.Name,
@@ -517,18 +502,20 @@ 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)
has, err := x.Where("head_repo_id=? AND head_branch=? AND base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?", err := db.Joins("INNER JOIN issue ON issue.id = pull_request.issue_id").
headRepoID, headBranch, baseRepoID, baseBranch, false, false). 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 = ?",
Join("INNER", "issue", "issue.id=pull_request.issue_id").Get(pr) headRepoID, headBranch, baseRepoID, baseBranch, false, false).
First(pr).Error
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrPullRequestNotExist{args: map[string]any{
"headRepoID": headRepoID,
"baseRepoID": baseRepoID,
"headBranch": headBranch,
"baseBranch": baseBranch,
}}
}
return nil, err return nil, err
} else if !has {
return nil, ErrPullRequestNotExist{args: map[string]any{
"headRepoID": headRepoID,
"baseRepoID": baseRepoID,
"headBranch": headBranch,
"baseBranch": baseBranch,
}}
} }
return pr, nil return pr, nil
@@ -538,18 +525,22 @@ 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)
return prs, x.Where("head_repo_id = ? AND head_branch = ? AND has_merged = ? AND issue.is_closed = ?", err := db.Joins("INNER JOIN issue ON issue.id = pull_request.issue_id").
repoID, branch, false, false). Where("pull_request.head_repo_id = ? AND pull_request.head_branch = ? AND pull_request.has_merged = ? AND issue.is_closed = ?",
Join("INNER", "issue", "issue.id = pull_request.issue_id").Find(&prs) repoID, branch, false, false).
Find(&prs).Error
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)
return prs, x.Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?", err := db.Joins("INNER JOIN issue ON issue.id = pull_request.issue_id").
repoID, branch, false, false). Where("pull_request.base_repo_id = ? AND pull_request.base_branch = ? AND pull_request.has_merged = ? AND issue.is_closed = ?",
Join("INNER", "issue", "issue.id=pull_request.issue_id").Find(&prs) repoID, branch, false, false).
Find(&prs).Error
return prs, err
} }
var _ errutil.NotFound = (*ErrPullRequestNotExist)(nil) var _ errutil.NotFound = (*ErrPullRequestNotExist)(nil)
@@ -571,50 +562,65 @@ func (ErrPullRequestNotExist) NotFound() bool {
return true return true
} }
func getPullRequestByID(e Engine, id int64) (*PullRequest, error) { func getPullRequestByID(db *gorm.DB, id int64) (*PullRequest, error) {
pr := new(PullRequest) pr := new(PullRequest)
has, err := e.ID(id).Get(pr) err := db.First(pr, id).Error
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrPullRequestNotExist{args: map[string]any{"pullRequestID": id}}
}
return nil, err return nil, err
} else if !has {
return nil, ErrPullRequestNotExist{args: map[string]any{"pullRequestID": id}}
} }
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(x, id) return getPullRequestByID(db, id)
} }
func getPullRequestByIssueID(e Engine, issueID int64) (*PullRequest, error) { func getPullRequestByIssueID(db *gorm.DB, issueID int64) (*PullRequest, error) {
pr := &PullRequest{ pr := &PullRequest{}
IssueID: issueID, err := db.Where("issue_id = ?", issueID).First(pr).Error
}
has, err := e.Get(pr)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrPullRequestNotExist{args: map[string]any{"issueID": issueID}}
}
return nil, err return nil, err
} else if !has {
return nil, ErrPullRequestNotExist{args: map[string]any{"issueID": issueID}}
} }
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(x, issueID) return getPullRequestByIssueID(db, 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 {
_, err := x.Id(pr.ID).AllCols().Update(pr) return db.Model(&PullRequest{}).Where("id = ?", pr.ID).Updates(pr).Error
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 {
_, err := x.Id(pr.ID).Cols(cols...).Update(pr) updates := make(map[string]any)
return err for _, col := range cols {
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.
@@ -711,7 +717,7 @@ func (pr *PullRequest) AddToTaskQueue() {
type PullRequestList []*PullRequest type PullRequestList []*PullRequest
func (prs PullRequestList) loadAttributes(e Engine) (err error) { func (prs PullRequestList) loadAttributes(db *gorm.DB) (err error) {
if len(prs) == 0 { if len(prs) == 0 {
return nil return nil
} }
@@ -726,7 +732,7 @@ func (prs PullRequestList) loadAttributes(e Engine) (err error) {
issueIDs = append(issueIDs, issueID) issueIDs = append(issueIDs, issueID)
} }
issues := make([]*Issue, 0, len(issueIDs)) issues := make([]*Issue, 0, len(issueIDs))
if err = e.Where("id > 0").In("id", issueIDs).Find(&issues); err != nil { if err = db.Where("id IN ?", issueIDs).Find(&issues).Error; err != nil {
return errors.Newf("find issues: %v", err) return errors.Newf("find issues: %v", err)
} }
for i := range issues { for i := range issues {
@@ -738,7 +744,7 @@ func (prs PullRequestList) loadAttributes(e Engine) (err error) {
// Load attributes // Load attributes
for i := range prs { for i := range prs {
if err = prs[i].loadAttributes(e); err != nil { if err = prs[i].loadAttributes(db); err != nil {
return errors.Newf("loadAttributes [%d]: %v", prs[i].ID, err) return errors.Newf("loadAttributes [%d]: %v", prs[i].ID, err)
} }
} }
@@ -747,7 +753,7 @@ func (prs PullRequestList) loadAttributes(e Engine) (err error) {
} }
func (prs PullRequestList) LoadAttributes() error { func (prs PullRequestList) LoadAttributes() error {
return prs.loadAttributes(x) return prs.loadAttributes(db)
} }
func addHeadRepoTasks(prs []*PullRequest) { func addHeadRepoTasks(prs []*PullRequest) {
@@ -838,25 +844,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() {
prs := make([]*PullRequest, 0, 10) var prs []*PullRequest
_ = x.Iterate(PullRequest{ _ = db.Where("status = ?", PullRequestStatusChecking).FindInBatches(&prs, 100, func(tx *gorm.DB, batch int) error {
Status: PullRequestStatusChecking, for i := range prs {
}, 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)
return nil continue
} }
if err := pr.testPatch(); err != nil { if err := pr.testPatch(); err != nil {
log.Error("testPatch: %v", err) log.Error("testPatch: %v", err)
return nil continue
} }
prs = append(prs, pr) }
return nil return nil
}) })
// Update pull request status. // Update pull request status.
for _, pr := range prs { for _, pr := range prs {

View File

@@ -6,12 +6,11 @@ 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"
@@ -21,40 +20,39 @@ import (
type Release struct { type Release struct {
ID int64 ID int64
RepoID int64 RepoID int64
Repo *Repository `xorm:"-" json:"-" gorm:"-"` Repo *Repository `gorm:"-" json:"-"`
PublisherID int64 PublisherID int64
Publisher *User `xorm:"-" json:"-" gorm:"-"` Publisher *User `gorm:"-" json:"-"`
TagName string TagName string
LowerTagName string LowerTagName string
Target string Target string
Title string Title string
Sha1 string `xorm:"VARCHAR(40)"` Sha1 string `gorm:"type:varchar(40)"`
NumCommits int64 NumCommits int64
NumCommitsBehind int64 `xorm:"-" json:"-" gorm:"-"` NumCommitsBehind int64 `gorm:"-" json:"-"`
Note string `xorm:"TEXT"` Note string `gorm:"type:text"`
IsDraft bool `xorm:"NOT NULL DEFAULT false"` IsDraft bool `gorm:"not null;default:false"`
IsPrerelease bool IsPrerelease bool
Created time.Time `xorm:"-" json:"-" gorm:"-"` Created time.Time `gorm:"-" json:"-"`
CreatedUnix int64 CreatedUnix int64
Attachments []*Attachment `xorm:"-" json:"-" gorm:"-"` Attachments []*Attachment `gorm:"-" json:"-"`
} }
func (r *Release) BeforeInsert() { func (r *Release) BeforeCreate(tx *gorm.DB) error {
if r.CreatedUnix == 0 { if r.CreatedUnix == 0 {
r.CreatedUnix = time.Now().Unix() r.CreatedUnix = tx.NowFunc().Unix()
} }
return nil
} }
func (r *Release) AfterSet(colName string, _ xorm.Cell) { func (r *Release) AfterFind(tx *gorm.DB) error {
switch colName { r.Created = time.Unix(r.CreatedUnix, 0).Local()
case "created_unix": return nil
r.Created = time.Unix(r.CreatedUnix, 0).Local()
}
} }
func (r *Release) loadAttributes(e Engine) (err error) { func (r *Release) loadAttributes(e *gorm.DB) (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 {
@@ -85,7 +83,7 @@ func (r *Release) loadAttributes(e Engine) (err error) {
} }
func (r *Release) LoadAttributes() error { func (r *Release) LoadAttributes() error {
return r.loadAttributes(x) return r.loadAttributes(db)
} }
// This method assumes some fields assigned with values: // This method assumes some fields assigned with values:
@@ -110,7 +108,9 @@ func IsReleaseExist(repoID int64, tagName string) (bool, error) {
return false, nil return false, nil
} }
return x.Get(&Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)}) var count int64
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,24 +171,21 @@ func NewRelease(gitRepo *git.Repository, r *Release, uuids []string) error {
} }
r.LowerTagName = strings.ToLower(r.TagName) r.LowerTagName = strings.ToLower(r.TagName)
sess := x.NewSession() err = db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() if err := tx.Create(r).Error; err != nil {
if err = sess.Begin(); err != nil { return errors.Newf("insert: %v", err)
return err
}
if _, err = sess.Insert(r); err != nil {
return errors.Newf("insert: %v", err)
}
if len(uuids) > 0 {
if _, err = sess.In("uuid", uuids).Cols("release_id").Update(&Attachment{ReleaseID: r.ID}); err != nil {
return errors.Newf("link attachments: %v", err)
} }
}
if err = sess.Commit(); err != nil { if len(uuids) > 0 {
return errors.Newf("commit: %v", err) if err := tx.Model(&Attachment{}).Where("uuid IN ?", uuids).Update("release_id", r.ID).Error; err != nil {
return errors.Newf("link attachments: %v", err)
}
}
return nil
})
if err != nil {
return err
} }
// Only send webhook when actually published, skip drafts // Only send webhook when actually published, skip drafts
@@ -231,8 +228,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{RepoID: repoID, LowerTagName: strings.ToLower(tagName)} r := &Release{}
if _, err = x.Get(r); err != nil { if err = db.Where("repo_id = ? AND lower_tag_name = ?", repoID, strings.ToLower(tagName)).First(r).Error; err != nil {
return nil, errors.Newf("get: %v", err) return nil, errors.Newf("get: %v", err)
} }
@@ -242,11 +239,12 @@ 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)
has, err := x.Id(id).Get(r) err := db.Where("id = ?", id).First(r).Error
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()
@@ -256,24 +254,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) {
sess := x.Where("repo_id = ?", repoID).And("is_draft = ?", false).Desc("created_unix") query := db.Where("repo_id = ? AND is_draft = ?", repoID, false).Order("created_unix DESC")
if len(matches) > 0 { if len(matches) > 0 {
sess.In("tag_name", matches) query = query.Where("tag_name IN ?", matches)
} }
releases := make([]*Release, 0, 5) releases := make([]*Release, 0, 5)
return releases, sess.Find(&releases, new(Release)) return releases, query.Find(&releases).Error
} }
// 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, x.Where("repo_id = ?", repoID).Find(&releases) return releases, db.Where("repo_id = ?", repoID).Find(&releases).Error
} }
// 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, x.Where("repo_id = ?", repoID).And("is_draft = ?", true).Find(&releases) return releases, db.Where("repo_id = ? AND is_draft = ?", repoID, true).Find(&releases).Error
} }
type ReleaseSorter struct { type ReleaseSorter struct {
@@ -310,28 +308,26 @@ func UpdateRelease(doer *User, gitRepo *git.Repository, r *Release, isPublish bo
r.PublisherID = doer.ID r.PublisherID = doer.ID
sess := x.NewSession() err = db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() if err := tx.Model(r).Where("id = ?", r.ID).Updates(r).Error; err != nil {
if err = sess.Begin(); err != nil { return errors.Newf("Update: %v", err)
return err
}
if _, err = sess.ID(r.ID).AllCols().Update(r); err != nil {
return errors.Newf("Update: %v", err)
}
// Unlink all current attachments and link back later if still valid
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)
}
if len(uuids) > 0 {
if _, err = sess.In("uuid", uuids).Cols("release_id").Update(&Attachment{ReleaseID: r.ID}); err != nil {
return errors.Newf("link attachments: %v", err)
} }
}
if err = sess.Commit(); err != nil { // Unlink all current attachments and link back later if still valid
return errors.Newf("commit: %v", err) if err := tx.Exec("UPDATE attachment SET release_id = 0 WHERE release_id = ?", r.ID).Error; err != nil {
return errors.Newf("unlink current attachments: %v", err)
}
if len(uuids) > 0 {
if err := tx.Model(&Attachment{}).Where("uuid IN ?", uuids).Update("release_id", r.ID).Error; err != nil {
return errors.Newf("link attachments: %v", err)
}
}
return nil
})
if err != nil {
return err
} }
if !isPublish { if !isPublish {
@@ -366,7 +362,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 = x.Id(rel.ID).Delete(new(Release)); err != nil { if err = db.Where("id = ?", rel.ID).Delete(new(Release)).Error; 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,6 +8,7 @@ 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"
@@ -93,8 +94,9 @@ 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 {
has, err := x.Where("repo_id = ?", repoID).And("user_id = ?", userID).And("name = ?", branch).Get(new(ProtectBranchWhitelist)) var whitelist ProtectBranchWhitelist
return has && err == nil err := db.Where("repo_id = ?", repoID).Where("user_id = ?", userID).Where("name = ?", branch).First(&whitelist).Error
return err == nil
} }
// ProtectBranch contains options of a protected branch. // ProtectBranch contains options of a protected branch.
@@ -115,11 +117,11 @@ func GetProtectBranchOfRepoByName(repoID int64, name string) (*ProtectBranch, er
RepoID: repoID, RepoID: repoID,
Name: name, Name: name,
} }
has, err := x.Get(protectBranch) err := db.Where("repo_id = ? AND name = ?", repoID, name).First(protectBranch).Error
if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
} else if !has {
return nil, ErrBranchNotExist{args: map[string]any{"name": name}} return nil, ErrBranchNotExist{args: map[string]any{"name": name}}
} else if err != nil {
return nil, err
} }
return protectBranch, nil return protectBranch, nil
} }
@@ -136,23 +138,19 @@ 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) {
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() if protectBranch.ID == 0 {
if err = sess.Begin(); err != nil { if err := tx.Create(protectBranch).Error; err != nil {
return err return errors.Newf("insert: %v", err)
} }
if protectBranch.ID == 0 {
if _, err = sess.Insert(protectBranch); err != nil {
return errors.Newf("insert: %v", err)
} }
}
if _, err = sess.ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil { if err := tx.Model(&ProtectBranch{}).Where("id = ?", protectBranch.ID).Updates(protectBranch).Error; err != nil {
return errors.Newf("update: %v", err) return errors.Newf("update: %v", err)
} }
return sess.Commit() return nil
})
} }
// UpdateOrgProtectBranch saves branch protection options of organizational repository. // UpdateOrgProtectBranch saves branch protection options of organizational repository.
@@ -209,7 +207,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 = x.Insert(protectBranch); err != nil { if err = db.Create(protectBranch).Error; err != nil {
return errors.Newf("insert: %v", err) return errors.Newf("insert: %v", err)
} }
} }
@@ -247,30 +245,29 @@ func UpdateOrgProtectBranch(repo *Repository, protectBranch *ProtectBranch, whit
} }
} }
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() if err := tx.Model(&ProtectBranch{}).Where("id = ?", protectBranch.ID).Updates(protectBranch).Error; err != nil {
if err = sess.Begin(); err != nil { return errors.Newf("Update: %v", err)
return err
}
if _, err = sess.ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil {
return errors.Newf("Update: %v", err)
}
// Refresh whitelists
if hasUsersChanged || hasTeamsChanged {
if _, err = sess.Delete(&ProtectBranchWhitelist{ProtectBranchID: protectBranch.ID}); err != nil {
return errors.Newf("delete old protect branch whitelists: %v", err)
} else if _, err = sess.Insert(whitelists); err != nil {
return errors.Newf("insert new protect branch whitelists: %v", err)
} }
}
return sess.Commit() // Refresh whitelists
if hasUsersChanged || hasTeamsChanged {
if err := tx.Delete(&ProtectBranchWhitelist{}, "protect_branch_id = ?", protectBranch.ID).Error; err != nil {
return errors.Newf("delete old protect branch whitelists: %v", err)
}
if len(whitelists) > 0 {
if err := tx.Create(&whitelists).Error; err != nil {
return errors.Newf("insert new protect branch whitelists: %v", err)
}
}
}
return nil
})
} }
// 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, x.Where("repo_id = ? and protected = ?", repoID, true).Asc("name").Find(&protectBranches) return protectBranches, db.Where("repo_id = ? AND protected = ?", repoID, true).Order("name ASC").Find(&protectBranches).Error
} }

View File

@@ -5,6 +5,7 @@ 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.
@@ -34,12 +35,12 @@ func IsCollaborator(repoID, userID int64) bool {
RepoID: repoID, RepoID: repoID,
UserID: userID, UserID: userID,
} }
has, err := x.Get(collaboration) err := db.Where("repo_id = ? AND user_id = ?", repoID, userID).First(collaboration).Error
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 has return true
} }
func (r *Repository) IsCollaborator(userID int64) bool { func (r *Repository) IsCollaborator(userID int64) bool {
@@ -53,32 +54,29 @@ func (r *Repository) AddCollaborator(u *User) error {
UserID: u.ID, UserID: u.ID,
} }
has, err := x.Get(collaboration) var existing Collaboration
if err != nil { err := db.Where("repo_id = ? AND user_id = ?", r.ID, u.ID).First(&existing).Error
return err if err == nil {
} else if has {
return nil return nil
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
} }
collaboration.Mode = AccessModeWrite collaboration.Mode = AccessModeWrite
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() if err := tx.Create(collaboration).Error; err != nil {
if err = sess.Begin(); err != nil { return err
return err }
} if err := r.recalculateAccesses(tx); err != nil {
return errors.Newf("recalculateAccesses [repo_id: %v]: %v", r.ID, err)
if _, err = sess.Insert(collaboration); err != nil { }
return err return nil
} else if err = r.recalculateAccesses(sess); err != nil { })
return errors.Newf("recalculateAccesses [repo_id: %v]: %v", r.ID, err)
}
return sess.Commit()
} }
func (r *Repository) getCollaborations(e Engine) ([]*Collaboration, error) { func (r *Repository) getCollaborations(e *gorm.DB) ([]*Collaboration, error) {
collaborations := make([]*Collaboration, 0) collaborations := make([]*Collaboration, 0)
return collaborations, e.Find(&collaborations, &Collaboration{RepoID: r.ID}) return collaborations, e.Where("repo_id = ?", r.ID).Find(&collaborations).Error
} }
// Collaborator represents a user with collaboration details. // Collaborator represents a user with collaboration details.
@@ -98,7 +96,7 @@ func (c *Collaborator) APIFormat() *api.Collaborator {
} }
} }
func (r *Repository) getCollaborators(e Engine) ([]*Collaborator, error) { func (r *Repository) getCollaborators(e *gorm.DB) ([]*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)
@@ -120,7 +118,7 @@ func (r *Repository) getCollaborators(e Engine) ([]*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(x) return r.getCollaborators(db)
} }
// ChangeCollaborationAccessMode sets new access mode for the collaboration. // ChangeCollaborationAccessMode sets new access mode for the collaboration.
@@ -134,11 +132,11 @@ func (r *Repository) ChangeCollaborationAccessMode(userID int64, mode AccessMode
RepoID: r.ID, RepoID: r.ID,
UserID: userID, UserID: userID,
} }
has, err := x.Get(collaboration) err := db.Where("repo_id = ? AND user_id = ?", r.ID, userID).First(collaboration).Error
if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Newf("get collaboration: %v", err)
} else if !has {
return nil return nil
} else if err != nil {
return errors.Newf("get collaboration: %v", err)
} }
if collaboration.Mode == mode { if collaboration.Mode == mode {
@@ -159,35 +157,31 @@ func (r *Repository) ChangeCollaborationAccessMode(userID int64, mode AccessMode
} }
} }
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() if err := tx.Model(&Collaboration{}).Where("id = ?", collaboration.ID).Updates(collaboration).Error; err != nil {
if err = sess.Begin(); err != nil { return errors.Newf("update collaboration: %v", err)
return err }
}
if _, err = sess.ID(collaboration.ID).AllCols().Update(collaboration); err != nil { access := &Access{
return errors.Newf("update collaboration: %v", err) UserID: userID,
} RepoID: r.ID,
}
err := tx.Where("user_id = ? AND repo_id = ?", userID, r.ID).First(access).Error
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)
}
access := &Access{ return nil
UserID: userID, })
RepoID: r.ID,
}
has, err = sess.Get(access)
if err != nil {
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 sess.Commit()
} }
// DeleteCollaboration removes collaboration relation between the user and repository. // DeleteCollaboration removes collaboration relation between the user and repository.
@@ -201,19 +195,20 @@ func DeleteCollaboration(repo *Repository, userID int64) (err error) {
UserID: userID, UserID: userID,
} }
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() result := tx.Delete(collaboration, "repo_id = ? AND user_id = ?", repo.ID, userID)
if err = sess.Begin(); err != nil { if result.Error != nil {
return err return result.Error
} } else if result.RowsAffected == 0 {
return nil
}
if has, err := sess.Delete(collaboration); err != nil || has == 0 { if err := repo.recalculateAccesses(tx); err != nil {
return err return err
} else if err = repo.recalculateAccesses(sess); err != nil { }
return err
}
return sess.Commit() return nil
})
} }
func (r *Repository) DeleteCollaboration(userID int64) error { func (r *Repository) DeleteCollaboration(userID int64) error {

View File

@@ -14,6 +14,7 @@ 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"
@@ -23,6 +24,7 @@ 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.
@@ -414,10 +416,8 @@ 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) {
// 🚨 SECURITY: Prevent path traversal. if tool.IsMaliciousPath(name) {
name = pathutil.Clean(name) return nil, errors.Newf("malicious path detected: %s", 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 := x.Insert(upload); err != nil { if err := db.Create(upload).Error; err != nil {
return nil, err return nil, err
} }
@@ -451,11 +451,12 @@ 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}
has, err := x.Get(upload) err := db.Where("uuid = ?", uuid).First(upload).Error
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
} }
@@ -467,7 +468,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, x.In("uuid", uuids).Find(&uploads) return uploads, db.Where("uuid IN ?", uuids).Find(&uploads).Error
} }
func DeleteUploads(uploads ...*Upload) (err error) { func DeleteUploads(uploads ...*Upload) (err error) {
@@ -475,32 +476,28 @@ func DeleteUploads(uploads ...*Upload) (err error) {
return nil return nil
} }
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() ids := make([]int64, len(uploads))
if err = sess.Begin(); err != nil { for i := 0; i < len(uploads); i++ {
return err ids[i] = uploads[i].ID
} }
if err := tx.Where("id IN ?", ids).Delete(new(Upload)).Error; err != nil {
ids := make([]int64, len(uploads)) return errors.Newf("delete uploads: %v", err)
for i := 0; i < len(uploads); i++ {
ids[i] = uploads[i].ID
}
if _, err = sess.In("id", ids).Delete(new(Upload)); err != nil {
return errors.Newf("delete uploads: %v", err)
}
for _, upload := range uploads {
localPath := upload.LocalPath()
if !osutil.IsFile(localPath) {
continue
} }
if err := os.Remove(localPath); err != nil { for _, upload := range uploads {
return errors.Newf("remove upload: %v", err) localPath := upload.LocalPath()
} if !osutil.IsFile(localPath) {
} continue
}
return sess.Commit() if err := os.Remove(localPath); err != nil {
return errors.Newf("remove upload: %v", err)
}
}
return nil
})
} }
func DeleteUpload(u *Upload) error { func DeleteUpload(u *Upload) error {

View File

@@ -4,7 +4,6 @@ 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"
@@ -28,13 +27,6 @@ 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,13 +8,11 @@ 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"
@@ -63,25 +61,18 @@ func main() {
_, _ = w.WriteString("```\n") _, _ = w.WriteString("```\n")
table := tablewriter.NewTable(w, table := tablewriter.NewWriter(w)
tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{ table.SetHeader([]string{"Field", "Column", "PostgreSQL", "MySQL", "SQLite3"})
Borders: tw.BorderNone, table.SetBorder(false)
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 {
sqlite3Type := strings.ToUpper(collected[2][i].Fields[j].Type) table.Append([]string{
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
sqlite3Type, strings.ToUpper(collected[2][i].Fields[j].Type), // SQLite3
}) })
} }
_ = 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,38 +40,41 @@ 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 `xorm:"INDEX NOT NULL" gorm:"index;not null"` OwnerID int64 `gorm:"index;not null"`
Name string `xorm:"NOT NULL" gorm:"not null"` Name string `gorm:"not null"`
Fingerprint string `xorm:"NOT NULL" gorm:"not null"` Fingerprint string `gorm:"not null"`
Content string `xorm:"TEXT NOT NULL" gorm:"type:TEXT;not null"` Content string `gorm:"type:text;not null"`
Mode AccessMode `xorm:"NOT NULL DEFAULT 2" gorm:"not null;default:2"` Mode AccessMode `gorm:"not null;default:2"`
Type KeyType `xorm:"NOT NULL DEFAULT 1" gorm:"not null;default:1"` Type KeyType `gorm:"not null;default:1"`
Created time.Time `xorm:"-" json:"-" gorm:"-"` Created time.Time `gorm:"-" json:"-"`
CreatedUnix int64 CreatedUnix int64
Updated time.Time `xorm:"-" json:"-" gorm:"-"` // Note: Updated must below Created for AfterSet. Updated time.Time `gorm:"-" json:"-"` // Note: Updated must below Created for AfterFind.
UpdatedUnix int64 UpdatedUnix int64
HasRecentActivity bool `xorm:"-" json:"-" gorm:"-"` HasRecentActivity bool `gorm:"-" json:"-"`
HasUsed bool `xorm:"-" json:"-" gorm:"-"` HasUsed bool `gorm:"-" json:"-"`
} }
func (k *PublicKey) BeforeInsert() { func (k *PublicKey) BeforeCreate(tx *gorm.DB) error {
k.CreatedUnix = time.Now().Unix() if k.CreatedUnix == 0 {
k.CreatedUnix = tx.NowFunc().Unix()
}
return nil
} }
func (k *PublicKey) BeforeUpdate() { func (k *PublicKey) BeforeUpdate(tx *gorm.DB) error {
k.UpdatedUnix = time.Now().Unix() k.UpdatedUnix = tx.NowFunc().Unix()
return nil
} }
func (k *PublicKey) AfterSet(colName string, _ xorm.Cell) { func (k *PublicKey) AfterFind(tx *gorm.DB) error {
switch colName { k.Created = time.Unix(k.CreatedUnix, 0).Local()
case "created_unix": if k.UpdatedUnix > 0 {
k.Created = time.Unix(k.CreatedUnix, 0).Local()
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(time.Now()) k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(tx.NowFunc())
} }
return nil
} }
// OmitEmail returns content of public key without email address. // OmitEmail returns content of public key without email address.
@@ -175,8 +178,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, keyTestPath string) (string, error) { func writeTmpKeyFile(content string) (string, error) {
tmpFile, err := os.CreateTemp(keyTestPath, "gogs_keytest") tmpFile, err := os.CreateTemp(conf.SSH.KeyTestPath, "gogs_keytest")
if err != nil { if err != nil {
return "", errors.Newf("TempFile: %v", err) return "", errors.Newf("TempFile: %v", err)
} }
@@ -188,15 +191,15 @@ func writeTmpKeyFile(content, keyTestPath 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, keyTestPath, keygenPath string) (string, int, error) { func SSHKeyGenParsePublicKey(key string) (string, int, error) {
tmpName, err := writeTmpKeyFile(key, keyTestPath) tmpName, err := writeTmpKeyFile(key)
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", keygenPath, "-lf", tmpName) stdout, stderr, err := process.Exec("SSHKeyGenParsePublicKey", conf.SSH.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)
} }
@@ -301,8 +304,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, conf.SSH.KeyTestPath, conf.SSH.KeygenPath) keyType, length, err = SSHKeyGenParsePublicKey(content)
} }
if err != nil { if err != nil {
return "", errors.Newf("%s: %v", fnName, err) return "", errors.Newf("%s: %v", fnName, err)
@@ -356,19 +359,16 @@ 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 {
has, err := x.Get(&PublicKey{ err := db.Where("content = ? AND type = ?", content, KeyTypeUser).First(&PublicKey{}).Error
Content: content, if err == nil {
Type: KeyTypeUser,
})
if err != nil {
return err
} else if has {
return ErrKeyAlreadyExist{0, content} return ErrKeyAlreadyExist{0, content}
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
} }
return nil return nil
} }
func addKey(e Engine, key *PublicKey) (err error) { func addKey(tx *gorm.DB, 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(e Engine, key *PublicKey) (err error) {
key.Fingerprint = strings.Split(stdout, " ")[1] key.Fingerprint = strings.Split(stdout, " ")[1]
// Save SSH key. // Save SSH key.
if _, err = e.Insert(key); err != nil { if err = tx.Create(key).Error; err != nil {
return err return err
} }
@@ -404,16 +404,10 @@ 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.
has, err := x.Where("owner_id = ? AND name = ?", ownerID, name).Get(new(PublicKey)) err := db.Where("owner_id = ? AND name = ?", ownerID, name).First(new(PublicKey)).Error
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
} }
@@ -424,21 +418,25 @@ func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) {
Mode: AccessModeWrite, Mode: AccessModeWrite,
Type: KeyTypeUser, Type: KeyTypeUser,
} }
if err = addKey(sess, key); err != nil { err = db.Transaction(func(tx *gorm.DB) error {
return addKey(tx, key)
})
if err != nil {
return nil, errors.Newf("addKey: %v", err) return nil, errors.Newf("addKey: %v", err)
} }
return key, sess.Commit() return key, nil
} }
// 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)
has, err := x.Id(keyID).Get(key) err := db.Where("id = ?", keyID).First(key).Error
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
} }
@@ -448,11 +446,12 @@ 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)
has, err := x.Where("content like ?", content+"%").Get(key) err := db.Where("content LIKE ?", content+"%").First(key).Error
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
} }
@@ -460,23 +459,21 @@ 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, x.Where("owner_id = ?", uid).Find(&keys) return keys, db.Where("owner_id = ?", uid).Find(&keys).Error
} }
// UpdatePublicKey updates given public key. // UpdatePublicKey updates given public key.
func UpdatePublicKey(key *PublicKey) error { func UpdatePublicKey(key *PublicKey) error {
_, err := x.Id(key.ID).AllCols().Update(key) return db.Model(key).Where("id = ?", key.ID).Updates(key).Error
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(e *xorm.Session, keyIDs ...int64) error { func deletePublicKeys(tx *gorm.DB, keyIDs ...int64) error {
if len(keyIDs) == 0 { if len(keyIDs) == 0 {
return nil return nil
} }
_, err := e.In("id", keyIDs).Delete(new(PublicKey)) return tx.Where("id IN ?", keyIDs).Delete(new(PublicKey)).Error
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.
@@ -494,17 +491,10 @@ func DeletePublicKey(doer *User, id int64) (err error) {
return ErrKeyAccessDenied{doer.ID, key.ID, "public"} return ErrKeyAccessDenied{doer.ID, key.ID, "public"}
} }
sess := x.NewSession() err = db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() return deletePublicKeys(tx, id)
if err = sess.Begin(); err != nil { })
return err if err != nil {
}
if err = deletePublicKeys(sess, id); err != nil {
return err
}
if err = sess.Commit(); err != nil {
return err return err
} }
@@ -531,14 +521,24 @@ func RewriteAuthorizedKeys() error {
} }
defer os.Remove(tmpPath) defer os.Remove(tmpPath)
err = x.Iterate(new(PublicKey), func(idx int, bean any) (err error) { // Use FindInBatches to process keys in chunks to avoid memory issues with large datasets
_, err = f.WriteString((bean.(*PublicKey)).AuthorizedString()) err = db.FindInBatches(&[]PublicKey{}, 100, func(tx *gorm.DB, batch int) error {
return err var keys []PublicKey
}) if err := tx.Find(&keys).Error; err != nil {
_ = f.Close() return err
}
for _, key := range keys {
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,37 +562,40 @@ 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 `xorm:"UNIQUE(s) INDEX"` KeyID int64 `gorm:"uniqueIndex:s;index"`
RepoID int64 `xorm:"UNIQUE(s) INDEX"` RepoID int64 `gorm:"uniqueIndex:s;index"`
Name string Name string
Fingerprint string Fingerprint string
Content string `xorm:"-" json:"-" gorm:"-"` Content string `gorm:"-" json:"-"`
Created time.Time `xorm:"-" json:"-" gorm:"-"` Created time.Time `gorm:"-" json:"-"`
CreatedUnix int64 CreatedUnix int64
Updated time.Time `xorm:"-" json:"-" gorm:"-"` // Note: Updated must below Created for AfterSet. Updated time.Time `gorm:"-" json:"-"` // Note: Updated must below Created for AfterFind.
UpdatedUnix int64 UpdatedUnix int64
HasRecentActivity bool `xorm:"-" json:"-" gorm:"-"` HasRecentActivity bool `gorm:"-" json:"-"`
HasUsed bool `xorm:"-" json:"-" gorm:"-"` HasUsed bool `gorm:"-" json:"-"`
} }
func (k *DeployKey) BeforeInsert() { func (k *DeployKey) BeforeCreate(tx *gorm.DB) error {
k.CreatedUnix = time.Now().Unix() if k.CreatedUnix == 0 {
k.CreatedUnix = tx.NowFunc().Unix()
}
return nil
} }
func (k *DeployKey) BeforeUpdate() { func (k *DeployKey) BeforeUpdate(tx *gorm.DB) error {
k.UpdatedUnix = time.Now().Unix() k.UpdatedUnix = tx.NowFunc().Unix()
return nil
} }
func (k *DeployKey) AfterSet(colName string, _ xorm.Cell) { func (k *DeployKey) AfterFind(tx *gorm.DB) error {
switch colName { k.Created = time.Unix(k.CreatedUnix, 0).Local()
case "created_unix": if k.UpdatedUnix > 0 {
k.Created = time.Unix(k.CreatedUnix, 0).Local()
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(time.Now()) k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(tx.NowFunc())
} }
return nil
} }
// GetContent gets associated public key content. // GetContent gets associated public key content.
@@ -605,28 +608,28 @@ func (k *DeployKey) GetContent() error {
return nil return nil
} }
func checkDeployKey(e Engine, keyID, repoID int64, name string) error { func checkDeployKey(tx *gorm.DB, 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.
has, err := e.Where("key_id = ? AND repo_id = ?", keyID, repoID).Get(new(DeployKey)) err := tx.Where("key_id = ? AND repo_id = ?", keyID, repoID).First(new(DeployKey)).Error
if err != nil { if err == nil {
return err
} else if has {
return ErrDeployKeyAlreadyExist{keyID, repoID} return ErrDeployKeyAlreadyExist{keyID, repoID}
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
} }
has, err = e.Where("repo_id = ? AND name = ?", repoID, name).Get(new(DeployKey)) err = tx.Where("repo_id = ? AND name = ?", repoID, name).First(new(DeployKey)).Error
if err != nil { if err == nil {
return err
} else if has {
return ErrDeployKeyNameAlreadyUsed{repoID, name} return ErrDeployKeyNameAlreadyUsed{repoID, name}
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
} }
return nil return nil
} }
// addDeployKey adds new key-repo relation. // addDeployKey adds new key-repo relation.
func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string) (*DeployKey, error) { func addDeployKey(tx *gorm.DB, keyID, repoID int64, name, fingerprint string) (*DeployKey, error) {
if err := checkDeployKey(e, keyID, repoID, name); err != nil { if err := checkDeployKey(tx, keyID, repoID, name); err != nil {
return nil, err return nil, err
} }
@@ -636,14 +639,14 @@ func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string
Name: name, Name: name,
Fingerprint: fingerprint, Fingerprint: fingerprint,
} }
_, err := e.Insert(key) err := tx.Create(key).Error
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 {
has, _ := x.Where("key_id = ? AND repo_id = ?", keyID, repoID).Get(new(DeployKey)) err := db.Where("key_id = ? AND repo_id = ?", keyID, repoID).First(new(DeployKey)).Error
return has return err == nil
} }
// AddDeployKey add new deploy key to database and authorized_keys file. // AddDeployKey add new deploy key to database and authorized_keys file.
@@ -657,30 +660,34 @@ func AddDeployKey(repoID int64, name, content string) (*DeployKey, error) {
Mode: AccessModeRead, Mode: AccessModeRead,
Type: KeyTypeDeploy, Type: KeyTypeDeploy,
} }
has, err := x.Get(pkey) err := db.Where("content = ? AND mode = ? AND type = ?", content, AccessModeRead, KeyTypeDeploy).First(pkey).Error
if err != nil { has := err == nil
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err return nil, err
} }
sess := x.NewSession() var key *DeployKey
defer sess.Close() err = db.Transaction(func(tx *gorm.DB) error {
if err = sess.Begin(); err != nil { // First time use this deploy key.
return nil, err if !has {
} if err := addKey(tx, pkey); err != nil {
return errors.Newf("addKey: %v", err)
// First time use this deploy key. }
if !has {
if err = addKey(sess, pkey); err != nil {
return nil, errors.Newf("addKey: %v", err)
} }
}
key, err := addDeployKey(sess, pkey.ID, repoID, name, pkey.Fingerprint) var err error
key, err = addDeployKey(tx, pkey.ID, repoID, name, pkey.Fingerprint)
if err != nil {
return errors.Newf("addDeployKey: %v", err)
}
return nil
})
if err != nil { if err != nil {
return nil, errors.Newf("addDeployKey: %v", err) return nil, err
} }
return key, sess.Commit() return key, nil
} }
var _ errutil.NotFound = (*ErrDeployKeyNotExist)(nil) var _ errutil.NotFound = (*ErrDeployKeyNotExist)(nil)
@@ -705,11 +712,12 @@ 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)
has, err := x.Id(id).Get(key) err := db.Where("id = ?", id).First(key).Error
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
} }
@@ -720,19 +728,19 @@ func GetDeployKeyByRepo(keyID, repoID int64) (*DeployKey, error) {
KeyID: keyID, KeyID: keyID,
RepoID: repoID, RepoID: repoID,
} }
has, err := x.Get(key) err := db.Where("key_id = ? AND repo_id = ?", keyID, repoID).First(key).Error
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 {
_, err := x.Id(key.ID).AllCols().Update(key) return db.Model(key).Where("id = ?", key.ID).Updates(key).Error
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.
@@ -761,31 +769,27 @@ func DeleteDeployKey(doer *User, id int64) error {
} }
} }
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() if err := tx.Where("id = ?", key.ID).Delete(new(DeployKey)).Error; err != nil {
if err = sess.Begin(); err != nil { return errors.Newf("delete deploy key [%d]: %v", key.ID, err)
return err }
}
if _, err = sess.ID(key.ID).Delete(new(DeployKey)); err != nil { // Check if this is the last reference to same key content.
return errors.Newf("delete deploy key [%d]: %v", key.ID, err) err := tx.Where("key_id = ?", key.KeyID).First(new(DeployKey)).Error
} if errors.Is(err, gorm.ErrRecordNotFound) {
if err = deletePublicKeys(tx, key.KeyID); err != nil {
// Check if this is the last reference to same key content. return err
has, err := sess.Where("key_id = ?", key.KeyID).Get(new(DeployKey)) }
if err != nil { } else if err != nil {
return err
} else if !has {
if err = deletePublicKeys(sess, key.KeyID); err != nil {
return err return err
} }
}
return sess.Commit() return nil
})
} }
// 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, x.Where("repo_id = ?", repoID).Find(&keys) return keys, db.Where("repo_id = ?", repoID).Find(&keys).Error
} }

View File

@@ -4,11 +4,13 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gogs.io/gogs/internal/conf"
) )
func TestSSHParsePublicKey(t *testing.T) { func Test_SSHParsePublicKey(t *testing.T) {
tempPath := t.TempDir() // TODO: Refactor SSHKeyGenParsePublicKey to accept a tempPath and remove this init.
conf.MustInit("")
tests := []struct { tests := []struct {
name string name string
content string content string
@@ -51,22 +53,20 @@ func TestSSHParsePublicKey(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)
require.NoError(t, err) if err != nil {
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, tempPath, "ssh-keygen") typ, length, err = SSHKeyGenParsePublicKey(test.content)
require.NoError(t, err) if err != nil {
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,6 +8,7 @@ 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"
@@ -37,20 +38,16 @@ 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) (err error) { func DeleteTwoFactor(userID int64) error {
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() if err := tx.Where("user_id = ?", userID).Delete(new(TwoFactor)).Error; err != nil {
if err = sess.Begin(); err != nil { return errors.Newf("delete two-factor: %v", err)
return err }
} if err := deleteRecoveryCodesByUserID(tx, userID); err != nil {
return errors.Newf("deleteRecoveryCodesByUserID: %v", err)
if _, err = sess.Where("user_id = ?", userID).Delete(new(TwoFactor)); err != nil { }
return errors.Newf("delete two-factor: %v", err) return nil
} else if err = deleteRecoveryCodesByUserID(sess, userID); err != nil { })
return errors.Newf("deleteRecoveryCodesByUserID: %v", err)
}
return sess.Commit()
} }
// TwoFactorRecoveryCode represents a two-factor authentication recovery code. // TwoFactorRecoveryCode represents a two-factor authentication recovery code.
@@ -64,12 +61,11 @@ 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, x.Where("user_id = ?", userID).Find(&recoveryCodes) return recoveryCodes, db.Where("user_id = ?", userID).Find(&recoveryCodes).Error
} }
func deleteRecoveryCodesByUserID(e Engine, userID int64) error { func deleteRecoveryCodesByUserID(e *gorm.DB, userID int64) error {
_, err := e.Where("user_id = ?", userID).Delete(new(TwoFactorRecoveryCode)) return e.Where("user_id = ?", userID).Delete(&TwoFactorRecoveryCode{}).Error
return err
} }
// RegenerateRecoveryCodes regenerates new set of recovery codes for given user. // RegenerateRecoveryCodes regenerates new set of recovery codes for given user.
@@ -79,19 +75,15 @@ func RegenerateRecoveryCodes(userID int64) error {
return errors.Newf("generateRecoveryCodes: %v", err) return errors.Newf("generateRecoveryCodes: %v", err)
} }
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() if err := deleteRecoveryCodesByUserID(tx, userID); err != nil {
if err = sess.Begin(); err != nil { return errors.Newf("deleteRecoveryCodesByUserID: %v", err)
return err }
} if err := tx.Create(recoveryCodes).Error; err != nil {
return errors.Newf("insert new recovery codes: %v", err)
if err = deleteRecoveryCodesByUserID(sess, userID); err != nil { }
return errors.Newf("deleteRecoveryCodesByUserID: %v", err) return nil
} else if _, err = sess.Insert(recoveryCodes); err != nil { })
return errors.Newf("insert new recovery codes: %v", err)
}
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@example.com", CreateUserOptions{}) alice, err := s.Create(ctx, "alice", "alice@exmaple.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@example.com", CreateUserOptions{}) bob, err := s.Create(ctx, "bob", "bob@exmaple.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@example.com", CreateUserOptions{}) cindy, err := s.Create(ctx, "cindy", "cindy@exmaple.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
frank, err := s.Create(ctx, "frank", "frank@example.com", CreateUserOptions{}) frank, err := s.Create(ctx, "frank", "frank@exmaple.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@example.com", CreateUserOptions{}) testUser, err := s.Create(ctx, "testUser", "testUser@exmaple.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@example.com", CreateUserOptions{}) alice, err := s.Create(ctx, "alice", "alice@exmaple.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@example.com", CreateUserOptions{}) bob, err := s.Create(ctx, "bob", "bob@exmaple.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@example.com", CreateUserOptions{Activated: true}) _, err = s.Create(ctx, "cindy", "cindy@exmaple.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@example.com", CreateUserOptions{}) david, err := s.Create(ctx, "david", "david@exmaple.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@example.com", CreateUserOptions{}) org, err := s.Create(ctx, "gogs", "gogs@exmaple.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@example.com", CreateUserOptions{}) alice, err := s.Create(ctx, "alice", "alice@exmaple.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@example.com" email2 := "bob2@exmaple.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@example.com", CreateUserOptions{}) alice, err := s.Create(ctx, "alice", "alice@exmaple.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@example.com", CreateUserOptions{}) alice, err := s.Create(ctx, "alice", "alice@exmaple.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@example.com", CreateUserOptions{}) alice, err := s.Create(ctx, "alice", "alice@exmaple.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@example.com", CreateUserOptions{}) alice, err := s.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{})
require.NoError(t, err) require.NoError(t, err)
bob, err := s.Create(ctx, "bob", "bob@example.com", CreateUserOptions{Activated: true}) bob, err := s.Create(ctx, "bob", "bob@exmaple.com", CreateUserOptions{Activated: true})
require.NoError(t, err) require.NoError(t, err)
_, err = s.Create(ctx, "cindy", "cindy@example.com", CreateUserOptions{Activated: true}) _, err = s.Create(ctx, "cindy", "cindy@exmaple.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,45 +95,42 @@ type Webhook struct {
ID int64 ID int64
RepoID int64 RepoID int64
OrgID int64 OrgID int64
URL string `xorm:"url TEXT"` URL string `gorm:"type:text;column:url"`
ContentType HookContentType ContentType HookContentType
Secret string `xorm:"TEXT"` Secret string `gorm:"type:text"`
Events string `xorm:"TEXT"` Events string `gorm:"type:text"`
*HookEvent `xorm:"-"` // LEGACY [1.0]: Cannot ignore JSON (i.e. json:"-") here, it breaks old backup archive *HookEvent `gorm:"-"` // LEGACY [1.0]: Cannot ignore JSON (i.e. json:"-") here, it breaks old backup archive
IsSSL bool `xorm:"is_ssl"` IsSSL bool `gorm:"column:is_ssl"`
IsActive bool IsActive bool
HookTaskType HookTaskType HookTaskType HookTaskType
Meta string `xorm:"TEXT"` // store hook-specific attributes Meta string `gorm:"type:text"` // store hook-specific attributes
LastStatus HookStatus // Last delivery status LastStatus HookStatus // Last delivery status
Created time.Time `xorm:"-" json:"-" gorm:"-"` Created time.Time `gorm:"-" json:"-"`
CreatedUnix int64 CreatedUnix int64
Updated time.Time `xorm:"-" json:"-" gorm:"-"` Updated time.Time `gorm:"-" json:"-"`
UpdatedUnix int64 UpdatedUnix int64
} }
func (w *Webhook) BeforeInsert() { func (w *Webhook) BeforeCreate(tx *gorm.DB) error {
w.CreatedUnix = time.Now().Unix() w.CreatedUnix = tx.NowFunc().Unix()
w.UpdatedUnix = w.CreatedUnix w.UpdatedUnix = w.CreatedUnix
return nil
} }
func (w *Webhook) BeforeUpdate() { func (w *Webhook) BeforeUpdate(tx *gorm.DB) error {
w.UpdatedUnix = time.Now().Unix() w.UpdatedUnix = tx.NowFunc().Unix()
return nil
} }
func (w *Webhook) AfterSet(colName string, _ xorm.Cell) { func (w *Webhook) AfterFind(tx *gorm.DB) error {
var err error w.HookEvent = &HookEvent{}
switch colName { if err := jsoniter.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
case "events": log.Error("Unmarshal [%d]: %v", w.ID, err)
w.HookEvent = &HookEvent{}
if err = jsoniter.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
log.Error("Unmarshal [%d]: %v", w.ID, err)
}
case "created_unix":
w.Created = time.Unix(w.CreatedUnix, 0).Local()
case "updated_unix":
w.Updated = time.Unix(w.UpdatedUnix, 0).Local()
} }
w.Created = time.Unix(w.CreatedUnix, 0).Local()
w.Updated = time.Unix(w.UpdatedUnix, 0).Local()
return nil
} }
func (w *Webhook) SlackMeta() *SlackMeta { func (w *Webhook) SlackMeta() *SlackMeta {
@@ -231,8 +228,7 @@ 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 {
_, err := x.Insert(w) return db.Create(w).Error
return err
} }
var _ errutil.NotFound = (*ErrWebhookNotExist)(nil) var _ errutil.NotFound = (*ErrWebhookNotExist)(nil)
@@ -257,11 +253,12 @@ 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) {
has, err := x.Get(bean) err := db.Where(bean).First(bean).Error
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
} }
@@ -292,39 +289,34 @@ func GetWebhookByOrgID(orgID, id int64) (*Webhook, error) {
} }
// getActiveWebhooksByRepoID returns all active webhooks of repository. // getActiveWebhooksByRepoID returns all active webhooks of repository.
func getActiveWebhooksByRepoID(e Engine, repoID int64) ([]*Webhook, error) { func getActiveWebhooksByRepoID(tx *gorm.DB, repoID int64) ([]*Webhook, error) {
webhooks := make([]*Webhook, 0, 5) webhooks := make([]*Webhook, 0, 5)
return webhooks, e.Where("repo_id = ?", repoID).And("is_active = ?", true).Find(&webhooks) return webhooks, tx.Where("repo_id = ? AND is_active = ?", repoID, true).Find(&webhooks).Error
} }
// 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, x.Find(&webhooks, &Webhook{RepoID: repoID}) return webhooks, db.Where("repo_id = ?", repoID).Find(&webhooks).Error
} }
// UpdateWebhook updates information of webhook. // UpdateWebhook updates information of webhook.
func UpdateWebhook(w *Webhook) error { func UpdateWebhook(w *Webhook) error {
_, err := x.Id(w.ID).AllCols().Update(w) return db.Model(w).Where("id = ?", w.ID).Updates(w).Error
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) (err error) { func deleteWebhook(bean *Webhook) error {
sess := x.NewSession() return db.Transaction(func(tx *gorm.DB) error {
defer sess.Close() if err := tx.Delete(bean).Error; err != nil {
if err = sess.Begin(); err != nil { return err
return err }
} if err := tx.Where("hook_id = ?", bean.ID).Delete(&HookTask{}).Error; err != nil {
return err
if _, err = sess.Delete(bean); err != nil { }
return err return nil
} else if _, err = sess.Delete(&HookTask{HookID: bean.ID}); err != nil { })
return err
}
return sess.Commit()
} }
// DeleteWebhookOfRepoByID deletes webhook of repository by given ID. // DeleteWebhookOfRepoByID deletes webhook of repository by given ID.
@@ -345,14 +337,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 = x.Find(&ws, &Webhook{OrgID: orgID}) err = db.Where("org_id = ?", orgID).Find(&ws).Error
return ws, err return ws, err
} }
// getActiveWebhooksByOrgID returns all active webhooks for an organization. // getActiveWebhooksByOrgID returns all active webhooks for an organization.
func getActiveWebhooksByOrgID(e Engine, orgID int64) ([]*Webhook, error) { func getActiveWebhooksByOrgID(tx *gorm.DB, orgID int64) ([]*Webhook, error) {
ws := make([]*Webhook, 0, 3) ws := make([]*Webhook, 0, 3)
return ws, e.Where("org_id=?", orgID).And("is_active=?", true).Find(&ws) return ws, tx.Where("org_id = ? AND is_active = ?", orgID, true).Find(&ws).Error
} }
// ___ ___ __ ___________ __ // ___ ___ __ ___________ __
@@ -431,64 +423,56 @@ 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 `xorm:"INDEX"` RepoID int64 `gorm:"index"`
HookID int64 HookID int64
UUID string UUID string
Type HookTaskType Type HookTaskType
URL string `xorm:"TEXT"` URL string `gorm:"type:text"`
Signature string `xorm:"TEXT"` Signature string `gorm:"type:text"`
api.Payloader `xorm:"-" json:"-" gorm:"-"` api.Payloader `gorm:"-" json:"-"`
PayloadContent string `xorm:"TEXT"` PayloadContent string `gorm:"type:text"`
ContentType HookContentType ContentType HookContentType
EventType HookEventType EventType HookEventType
IsSSL bool IsSSL bool
IsDelivered bool IsDelivered bool
Delivered int64 Delivered int64
DeliveredString string `xorm:"-" json:"-" gorm:"-"` DeliveredString string `gorm:"-" json:"-"`
// History info. // History info.
IsSucceed bool IsSucceed bool
RequestContent string `xorm:"TEXT"` RequestContent string `gorm:"type:text"`
RequestInfo *HookRequest `xorm:"-" json:"-" gorm:"-"` RequestInfo *HookRequest `gorm:"-" json:"-"`
ResponseContent string `xorm:"TEXT"` ResponseContent string `gorm:"type:text"`
ResponseInfo *HookResponse `xorm:"-" json:"-" gorm:"-"` ResponseInfo *HookResponse `gorm:"-" json:"-"`
} }
func (t *HookTask) BeforeUpdate() { func (t *HookTask) BeforeUpdate(tx *gorm.DB) error {
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) AfterSet(colName string, _ xorm.Cell) { func (t *HookTask) AfterFind(tx *gorm.DB) error {
var err error t.DeliveredString = time.Unix(0, t.Delivered).Format("2006-01-02 15:04:05 MST")
switch colName {
case "delivered":
t.DeliveredString = time.Unix(0, t.Delivered).Format("2006-01-02 15:04:05 MST")
case "request_content":
if t.RequestContent == "" {
return
}
if t.RequestContent != "" {
t.RequestInfo = &HookRequest{} t.RequestInfo = &HookRequest{}
if err = jsoniter.Unmarshal([]byte(t.RequestContent), t.RequestInfo); err != nil { if err := jsoniter.Unmarshal([]byte(t.RequestContent), t.RequestInfo); err != nil {
log.Error("Unmarshal[%d]: %v", t.ID, err) log.Error("Unmarshal[%d]: %v", t.ID, err)
} }
}
case "response_content": if t.ResponseContent != "" {
if t.ResponseContent == "" {
return
}
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 {
@@ -502,20 +486,19 @@ 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, x.Limit(conf.Webhook.PagingNum, (page-1)*conf.Webhook.PagingNum).Where("hook_id=?", hookID).Desc("id").Find(&tasks) return tasks, db.Where("hook_id = ?", hookID).Order("id DESC").Limit(conf.Webhook.PagingNum).Offset((page - 1) * conf.Webhook.PagingNum).Find(&tasks).Error
} }
// 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(e Engine, t *HookTask) error { func createHookTask(tx *gorm.DB, 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)
_, err = e.Insert(t) return tx.Create(t).Error
return err
} }
var _ errutil.NotFound = (*ErrHookTaskNotExist)(nil) var _ errutil.NotFound = (*ErrHookTaskNotExist)(nil)
@@ -543,23 +526,23 @@ func GetHookTaskOfWebhookByUUID(webhookID int64, uuid string) (*HookTask, error)
HookID: webhookID, HookID: webhookID,
UUID: uuid, UUID: uuid,
} }
has, err := x.Get(hookTask) err := db.Where("hook_id = ? AND uuid = ?", webhookID, uuid).First(hookTask).Error
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 {
_, err := x.Id(t.ID).AllCols().Update(t) return db.Model(t).Where("id = ?", t.ID).Updates(t).Error
return err
} }
// prepareHookTasks adds list of webhooks to task queue. // prepareHookTasks adds list of webhooks to task queue.
func prepareHookTasks(e Engine, repo *Repository, event HookEventType, p api.Payloader, webhooks []*Webhook) (err error) { func prepareHookTasks(tx *gorm.DB, repo *Repository, event HookEventType, p api.Payloader, webhooks []*Webhook) (err error) {
if len(webhooks) == 0 { if len(webhooks) == 0 {
return nil return nil
} }
@@ -633,7 +616,7 @@ func prepareHookTasks(e Engine, repo *Repository, event HookEventType, p api.Pay
signature = hex.EncodeToString(sig.Sum(nil)) signature = hex.EncodeToString(sig.Sum(nil))
} }
if err = createHookTask(e, &HookTask{ if err = createHookTask(tx, &HookTask{
RepoID: repo.ID, RepoID: repo.ID,
HookID: w.ID, HookID: w.ID,
Type: w.HookTaskType, Type: w.HookTaskType,
@@ -655,32 +638,32 @@ func prepareHookTasks(e Engine, repo *Repository, event HookEventType, p api.Pay
return nil return nil
} }
func prepareWebhooks(e Engine, repo *Repository, event HookEventType, p api.Payloader) error { func prepareWebhooks(tx *gorm.DB, repo *Repository, event HookEventType, p api.Payloader) error {
webhooks, err := getActiveWebhooksByRepoID(e, repo.ID) webhooks, err := getActiveWebhooksByRepoID(tx, 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(e).IsOrganization() { if repo.mustOwner(tx).IsOrganization() {
// get hooks for org // get hooks for org
orgws, err := getActiveWebhooksByOrgID(e, repo.OwnerID) orgws, err := getActiveWebhooksByOrgID(tx, 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(e, repo, event, p, webhooks) return prepareHookTasks(tx, 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 x == nil && testutil.InTest { if db == nil && testutil.InTest {
return nil return nil
} }
return prepareWebhooks(x, repo, event, p) return prepareWebhooks(db, 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.
@@ -689,7 +672,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(x, repo, event, p, []*Webhook{webhook}) return prepareHookTasks(db, repo, event, p, []*Webhook{webhook})
} }
func (t *HookTask) deliver() { func (t *HookTask) deliver() {
@@ -784,18 +767,15 @@ 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)
_ = x.Where("is_delivered = ?", false).Iterate(new(HookTask), err := db.Where("is_delivered = ?", false).Find(&tasks).Error
func(idx int, bean any) error { if err != nil {
t := bean.(*HookTask) log.Error("Find undelivered hook tasks: %v", err)
} else {
for _, t := range tasks {
t.deliver() t.deliver()
tasks = append(tasks, t) if err := UpdateHookTask(t); err != nil {
return nil log.Error("UpdateHookTask [%d]: %v", t.ID, err)
}) }
// Update hook task status.
for _, t := range tasks {
if err := UpdateHookTask(t); err != nil {
log.Error("UpdateHookTask [%d]: %v", t.ID, err)
} }
} }
@@ -805,7 +785,7 @@ func DeliverHooks() {
HookQueue.Remove(repoID) HookQueue.Remove(repoID)
tasks = make([]*HookTask, 0, 5) tasks = make([]*HookTask, 0, 5)
if err := x.Where("repo_id = ?", repoID).And("is_delivered = ?", false).Find(&tasks); err != nil { if err := db.Where("repo_id = ? AND is_delivered = ?", repoID, false).Find(&tasks).Error; 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