Compare commits

..

44 Commits

Author SHA1 Message Date
Joe Chen
5dcb6c64bd release: update version to 0.14.2
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 19:23:48 -05:00
JSS
b4bdb270d1 Fix git reset --end-of-options error on file upload and edit (#8184) 2026-02-18 19:13:57 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
0120bf0c86 js: use safe DOM construction for milestone and assignee selection (#8178)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 19:12:35 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
094b632182 context: reject access tokens passed via URL query parameters (#8177) 2026-02-18 19:12:23 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
a5c2cc0c6e template: escape untrusted names in locale strings piped through Safe (#8176)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-18 19:11:55 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
41b186cbfd database: use safe git-module API for tag deletion (#8175)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-18 19:11:43 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
51cf4cbe7e markup: restrict data URI scheme to safe image MIME types (#8174)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 19:10:56 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
5e6014c421 lfs: verify content hash and prevent object overwrite (#8166)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
2026-02-18 19:10:41 -05:00
Joe Chen
f5c8030c1f Fix up tests 2026-01-31 22:28:11 -05:00
Joe Chen
8c5c0125c4 release: update version to 0.14.1 2026-01-31 22:23:40 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
3f03530042 fix(ssh): git clone via built-in SSH server hangs (#8135)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 22:22:03 -05:00
Joe Chen
36c26c4ccc Update version to 0.14.0 2026-01-31 16:32:58 -05:00
Joe Chen
b68e6886c6 release: cut CHANGELOG entries for 0.14.0
[skip ci]
2026-01-31 16:29:15 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
ac7ba9c8a7 locale: sync from Crowdin (#8131) 2026-01-31 16:07:47 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
dd862ee058 ci(docker): auto-tag minor version for stable releases (#8129)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 15:50:42 -05:00
Joe Chen
f94042ce6f chore: update release templates
[skip ci]
2026-01-31 15:17:59 -05:00
ᴊᴏᴇ ᴄʜᴇɴ
628216d588 security: require authentication for attachment uploads (#8128)
https://github.com/gogs/gogs/security/advisories/GHSA-fc3h-92p8-h36f

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

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

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

13
.claude/commands/ghsa.md Normal file
View File

@@ -0,0 +1,13 @@
Analyze and help fix the GitHub Security Advisory (GHSA) at: $ARGUMENTS
Steps:
1. Fetch the GHSA page using `gh api repos/gogs/gogs/security-advisories` and understand the vulnerability details (description, severity, affected versions, CWE).
2. Verify the reported vulnerability actually exists, and why.
3. Identify the affected code in this repository.
4. Propose a fix with a clear explanation of the root cause and how the fix addresses it. Check for prior art in the codebase to stay consistent with existing patterns.
5. Implement the fix. Only add tests when there is something meaningful to test at our layer.
6. Run all the usual build and test commands.
7. If a changelog entry is warranted (user will specify), add it to CHANGELOG.md with a placeholder for the PR link.
8. Create a branch named after the GHSA ID, commit, and push.
9. Create a pull request with a proper title and description, do not reveal too much detail and link the GHSA.
10. If a changelog entry was added, update it with the PR link, then commit and push again.

View File

@@ -1,6 +1,7 @@
---
name: "Dev: Release a minor version"
about: ONLY USED BY MAINTAINERS.
assignees: "unknwon"
title: "Release [VERSION]"
labels: 📸 release
---
@@ -13,30 +14,26 @@ 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).
- [ ] [Sync locales from Crowdin](https://github.com/gogs/gogs/blob/main/docs/dev/import_locale.md).
- [ ] [Update CHANGELOG](https://github.com/gogs/gogs/commit/540134d4436d8da82247dd2cabe9312ca2f5b1f1) to include entries for the current minor release.
- [ ] Cut a new release branch `release/<MAJOR>.<MINOR>`, e.g. `release/0.12`.
- [ ] [Update CHANGELOG](https://github.com/gogs/gogs/commit/f1102a7a7c545ec221d2906f02fa19170d96f96d) 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.14`.
## During release
On the release branch:
- [ ] [Update the hard-coded version](https://github.com/gogs/gogs/commit/f17e7d5a2c36c52a1121d2315f3d75dcd8053b89) to the current release, e.g. `0.12.0+dev` -> `0.12.0`.
- [ ] [Update the hard-coded version](https://github.com/gogs/gogs/commit/f17e7d5a2c36c52a1121d2315f3d75dcd8053b89) to the current release, e.g. `0.14.0+dev` -> `0.14.0`.
- [ ] Wait for GitHub Actions to complete and no failed jobs.
- [ ] Publish new RC releases (e.g. `v0.12.0-rc.1`, `v0.12.0-rc.2`) to ensure Docker workflow succeeds. **Make sure the tag is created on the release branch**.
- Pull down the Docker image and [run through application setup](https://github.com/gogs/gogs/blob/main/docker/README.md) to make sure nothing blows up.
- [ ] Publish a new [GitHub release](https://github.com/gogs/gogs/releases) with entries from [CHANGELOG](https://github.com/gogs/gogs/blob/main/CHANGELOG.md) for the current minor release. **Make sure the tag is created on the release branch**.
- [ ] Publish new RC releases (e.g. `v0.14.0-rc.1`, `v0.14.0-rc.2`) to ensure Docker and release workflows both succeed.
- ⚠️ **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) 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**.
- [ ] [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).
- [ ] 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`.
- [ ] Upload all archives and `checksum_sha256.txt` to https://dl.gogs.io.
## After release
@@ -46,7 +43,7 @@ On the `main` branch:
- [ ] 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).
- [ ] Send a tweet on the [official Twitter account](https://twitter.com/GogsHQ) for the minor release.
- [ ] Publish a new release article on [OSChina](http://my.oschina.net/Obahua/admin/releases).
- [ ] Close the 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`.
- [ ] Close the milestone for the minor release.
- [ ] [Bump the hard-coded version](https://github.com/gogs/gogs/commit/a98968436cd5841cf691bb0b80c54c81470d1676) to the new develop version, e.g. `0.14.0+dev` -> `0.15.0+dev`.
- [ ] Run `task legacy` to identify deprecated code that is aimed to be removed in current develop version.
- [ ] **After 14 days**, publish [GitHub security advisories](https://github.com/gogs/gogs/security) for security patches included in the release.

View File

@@ -1,6 +1,7 @@
---
name: "Dev: Release a patch version"
about: ONLY USED BY MAINTAINERS.
assignees: "unknwon"
title: "Release [VERSION]"
labels: 📸 release
---
@@ -13,7 +14,7 @@ On the release branch:
- [ ] Make sure all commits are cherry-picked from the `main` branch by checking the patch milestone.
- Run `task build` for every cherry-picked commit to make sure there is no compilation error.
- [ ] [Update CHANGELOG on the `main` branch](https://github.com/gogs/gogs/commit/e6c5633f580399c8f4dfc07166a63a01c6c70346) to include entries for the current patch release.
- [ ] [Update CHANGELOG on the `main` branch](https://github.com/gogs/gogs/commit/f1102a7a7c545ec221d2906f02fa19170d96f96d) to include entries for the current patch release.
## During release
@@ -24,6 +25,7 @@ On the release branch:
- [ ] 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.
- ⚠️ **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) 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:
@@ -33,16 +35,8 @@ On the release branch:
- [ ] [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.
- [ ] [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).
- [ ] [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).
- [ ] 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`.
- [ ] Upload all archives and `checksum_sha256.txt` to https://dl.gogs.io.
## After release
@@ -54,5 +48,5 @@ On the `main` branch:
```
- [ ] 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.
- [ ] Close the patch milestone.
- [ ] Close the milestone for the patch release.
- [ ] **After 14 days**, publish [GitHub security advisories](https://github.com/gogs/gogs/security) for security patches included in the release.

View File

@@ -19,18 +19,8 @@ jobs:
# --include-untagged-manifests: Deletes unreferenced manifests to maximize space
doctl registry garbage-collection start --force --include-untagged-manifests
- name: Send email on failure
uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0
uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
if: ${{ failure() }}
with:
server_address: smtp.mailgun.org
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 }}
smtp_username: ${{ secrets.SMTP_USERNAME }}
smtp_password: ${{ secrets.SMTP_PASSWORD }}

View File

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

View File

@@ -79,21 +79,11 @@ jobs:
file: ./coverage
flags: unittests
- name: Send email on failure
uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0
uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
if: ${{ failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
with:
server_address: smtp.mailgun.org
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 }}
smtp_username: ${{ secrets.SMTP_USERNAME }}
smtp_password: ${{ secrets.SMTP_PASSWORD }}
# Running tests with race detection consumes too much memory on Windows,
# see https://github.com/golang/go/issues/46099 for details.
@@ -119,21 +109,11 @@ jobs:
file: ./coverage
flags: unittests
- name: Send email on failure
uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0
uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
if: ${{ failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
with:
server_address: smtp.mailgun.org
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 }}
smtp_username: ${{ secrets.SMTP_USERNAME }}
smtp_password: ${{ secrets.SMTP_PASSWORD }}
postgres:
name: Postgres
@@ -195,22 +175,3 @@ jobs:
MYSQL_PASSWORD: root
MYSQL_HOST: localhost
MYSQL_PORT: 3306
sqlite-go:
name: SQLite - Go
strategy:
matrix:
go-version: [ 1.25.x ]
platform: [ ubuntu-latest ]
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Install Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: ${{ matrix.go-version }}
- name: Run tests with coverage
run: go test -shuffle=on -v -race -parallel=1 -coverprofile=coverage -covermode=atomic ./internal/database/...
env:
GOGS_DATABASE_TYPE: sqlite

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

@@ -0,0 +1,146 @@
name: Release
on:
release:
types: [published]
push:
branches:
- main
pull_request:
paths:
- '.github/workflows/release.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
GOPROXY: "https://proxy.golang.org"
permissions:
contents: write
jobs:
build:
name: Build ${{ matrix.goos }}/${{ matrix.goarch }}${{ matrix.suffix }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- {goos: linux, goarch: amd64}
- {goos: linux, goarch: arm64}
- {goos: linux, goarch: "386"}
- {goos: darwin, goarch: amd64}
- {goos: darwin, goarch: arm64}
- {goos: windows, goarch: amd64}
- {goos: windows, goarch: arm64}
- {goos: windows, goarch: "386"}
- {goos: windows, goarch: amd64, suffix: "_mws", tags: minwinsvc}
- {goos: windows, goarch: arm64, suffix: "_mws", tags: minwinsvc}
- {goos: windows, goarch: "386", suffix: "_mws", tags: minwinsvc}
steps:
- name: Checkout code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: 1.25.x
- name: Determine version
id: version
run: |
if [ "${{ github.event_name }}" = "release" ]; then
echo "version=${{ github.event.release.tag_name }}" >> "$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"
- name: Prepare archive contents
run: |
mkdir -p dist/gogs
BINARY_NAME="gogs"
if [ "${{ matrix.goos }}" = "windows" ]; then
BINARY_NAME="gogs.exe"
fi
cp "$BINARY_NAME" dist/gogs/
cp LICENSE README.md README_ZH.md dist/gogs/
cp -r scripts dist/gogs/
- name: Create archives
working-directory: dist
run: |
VERSION="${{ steps.version.outputs.version }}"
ARCHIVE_BASE="gogs_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}${{ matrix.suffix }}"
zip -r "${ARCHIVE_BASE}.zip" gogs
if [ "${{ matrix.goos }}" = "linux" ]; then
tar -czvf "${ARCHIVE_BASE}.tar.gz" gogs
fi
- name: Upload to release
env:
GH_TOKEN: ${{ github.token }}
run: |
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
if [ "${{ github.event_name }}" != "release" ]; then
git tag -f "$RELEASE_TAG"
git push origin "$RELEASE_TAG" --force || true
RELEASE_TITLE="Release Archive Testing"
RELEASE_NOTES="Automated testing release for workflow development."
if [ "$RELEASE_TAG" = "latest-commit-build" ]; then
RELEASE_TITLE="Latest Commit Build"
RELEASE_NOTES="Automated build from the latest commit on main branch. This release is updated automatically with every push to main."
fi
gh release view "$RELEASE_TAG" || gh release create "$RELEASE_TAG" --title "$RELEASE_TITLE" --notes "$RELEASE_NOTES" --prerelease
fi
PATTERN="_${{ matrix.goos }}_${{ matrix.goarch }}${{ matrix.suffix }}\."
gh release view "$RELEASE_TAG" --json assets --jq ".assets[].name" | grep "$PATTERN" | while read -r asset; do
gh release delete-asset "$RELEASE_TAG" "$asset" --yes || true
done
gh release upload "$RELEASE_TAG" dist/gogs_*.zip --clobber
if [ "${{ matrix.goos }}" = "linux" ]; then
gh release upload "$RELEASE_TAG" dist/gogs_*.tar.gz --clobber
fi
notify-failure:
name: Notify on failure
runs-on: ubuntu-latest
needs: [build]
if: ${{ failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
steps:
- name: Send email on failure
uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
with:
smtp_username: ${{ secrets.SMTP_USERNAME }}
smtp_password: ${{ secrets.SMTP_PASSWORD }}

24
.gitignore vendored
View File

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

View File

@@ -1,6 +1,7 @@
## Core principles
- When you see changes made outside your knowledge, use the current version as your new starting point. Do not blindly overwrite those changes or you suck. Even if you have to update the code, always respect the god damn pattern in the surrounding context!
- Stop telling me "You're right", it just shows how incompetent you are. Do it right on your first try, fact-check and review after changes. If you are not sure, ask for help.
- When you see changes made outside your knowledge, use the current version as your new starting point. Do not blindly overwrite those changes or you suck. Even if you have to update the code, always respect the pattern in the surrounding context!
## Style and mechanics
@@ -15,6 +16,17 @@ This applies to all texts, including but not limited to UI, documentation, code
- Use `github.com/cockroachdb/errors` for error handling.
- Use `github.com/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
- Use `gh` CLI to access information on github.com that is not publicly available.
## Source code control
- When pushing changes to a pull request from a fork, use SSH address and do not add remote.
- Never automatically executes commands that touches Git history even if the session does not require approvals, including but not limited to `rebase`, `commit`, `push`, `pull`, `reset`, `amend`. Exceptions are only allowed case-by-case.
- Do not amend commits unless being explicitly asked to do so.

View File

@@ -2,26 +2,50 @@
All notable changes to Gogs are documented in this file.
## 0.14.0+dev (`main`)
## 0.15.0+dev (`main`)
### Fixed
- _Security:_ Cross-repository LFS object overwrite via missing content hash verification. [#8166](https://github.com/gogs/gogs/pull/8166) - [GHSA-gmf8-978x-2fg2](https://github.com/gogs/gogs/security/advisories/GHSA-gmf8-978x-2fg2)
- _Security:_ DOM-based XSS via issue meta selection on the issue page. [#8178](https://github.com/gogs/gogs/pull/8178) - [GHSA-vgjm-2cpf-4g7c](https://github.com/gogs/gogs/security/advisories/GHSA-vgjm-2cpf-4g7c)
- Unable to update files via web editor and API. [#8184](https://github.com/gogs/gogs/pull/8184)
### Removed
- Support for passing API access tokens via URL query parameters (`token`, `access_token`). Use the `Authorization` header instead. [#8177](https://github.com/gogs/gogs/pull/8177) - [GHSA-x9p5-w45c-7ffc](https://github.com/gogs/gogs/security/advisories/GHSA-x9p5-w45c-7ffc)
- Git clone via the built-in SSH server hangs. [#8132](https://github.com/gogs/gogs/issues/8132)
## 0.14.0
### 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 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)
- Start publishing next-generation, security-focused Docker image via `gogs/gogs:next-latest`, which will become the default image distribution (`gogs/gogs:latest`) starting 0.15.0. While not all container options support have been added in the next-generation image, the use of current legacy Docker image is deprecated, it will be published as `gogs/gogs:legacy-latest` starting 0.15.0, and be completely removed starting 0.16.0. [#8061](https://github.com/gogs/gogs/pull/8061)
- Start publishing next-generation, security-focused Docker image via `gogs/gogs:next-latest`, which will become the default image distribution (`gogs/gogs:latest`) starting 0.16.0. While not all container options support have been added in the next-generation image, the use of current legacy Docker image is deprecated, it will be published as `gogs/gogs:legacy-latest` starting 0.16.0, and be completely removed no earlier than 0.17.0. [#8061](https://github.com/gogs/gogs/pull/8061)
### Changed
- 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)
- 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)
- Halt the repository creation and leave the directory untouched if the repository root already exists. [#8091](https://github.com/gogs/gogs/pull/8091)
### 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)
- 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

1
CLAUDE.md Symbolic link
View File

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

View File

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

View File

@@ -14,6 +14,7 @@ Existing vulnerability reports are being tracked in [GitHub Security Advisories]
1. Report an advisory for the vulnerability.
- 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:
- Ask clarifying questions
- Make sure there was no prior advisory exists for the same vulnerability

View File

@@ -10,8 +10,10 @@ tasks:
web:
desc: Build the binary and start the web server
deps: [build]
env:
GOGS_WORK_DIR: '{{.ROOT_DIR}}'
cmds:
- ./gogs web
- .bin/gogs web
build:
desc: Build the binary
@@ -22,7 +24,7 @@ tasks:
-X "{{.PKG_PATH}}.BuildCommit={{.BUILD_COMMIT}}"
'
-tags '{{.TAGS}}'
-trimpath -o gogs{{.BINARY_EXT}}
-trimpath -o .bin/gogs{{.BINARY_EXT}}
vars:
PKG_PATH: gogs.io/gogs/internal/conf
BUILD_TIME:
@@ -59,18 +61,6 @@ tasks:
cmds:
- 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:
desc: Generate CSS from LESS files
cmds:
@@ -99,3 +89,8 @@ tasks:
dropdb "$dbname"
echo "dropped $dbname"
done
lint:
desc: Run all linters
cmds:
- golangci-lint run

View File

@@ -279,6 +279,8 @@ ACCESS_CONTROL_ALLOW_ORIGIN =
STORAGE = local
; The root path to store LFS objects on local file system.
OBJECTS_PATH = data/lfs-objects
; The path to temporarily store LFS objects during upload verification.
OBJECTS_TEMP_PATH = data/tmp/lfs-objects
[attachment]
; Whether to enabled upload attachments in general.

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Застинали клонове
branches.all=Всички клонове
branches.updated_by=Актуализирани %[1]s от %[2]s
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.upload_file=Качи файл
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Zastaralé větve
branches.all=Všechny větve
branches.updated_by=%[2]s změnil %[1]s
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.upload_file=Nahrát soubor
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Alte Branches
branches.all=Alle Branches
branches.updated_by=Aktualisiert %[1]s von %[2]s
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.upload_file=Datei hochladen
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Fehler beim Senden der Test-E-Mail an '%s': %v
config.email.test_mail_sent=Test-E-Mail wurde an '%s ' gesendet.
config.auth_config=Authentifizierungskonfiguration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Aktivierungscode Lebensdauer
config.auth.reset_password_code_lives=Gültigkeitsdauer Zurücksetzungs-Code
config.auth.require_email_confirm=E-Mail-Bestätigung erforderlich

View File

@@ -501,6 +501,8 @@ branches.stale_branches=Stale Branches
branches.all=All Branches
branches.updated_by=Updated %[1]s by %[2]s
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.upload_file=Upload file
@@ -1346,6 +1348,7 @@ config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives

View File

@@ -494,6 +494,8 @@ branches.stale_branches = Stale Branches
branches.all = All Branches
branches.updated_by = Updated %[1]s by %[2]s
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.upload_file = Upload file

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Ramas Viejas
branches.all=Todas las Ramas
branches.updated_by=%[1]s actualizado por %[2]s
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.upload_file=Subir archivo
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Error al enviar correo electrónico de prueba a '%
config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation

View File

@@ -496,6 +496,8 @@ branches.stale_branches=شاخه های قدیمی
branches.all=همه شاخه
branches.updated_by=%[1]s به روزشده توسط %[2]s
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.upload_file=بارگذاری پرونده
@@ -1276,6 +1278,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Passivoituneet haarat
branches.all=Kaikki haarat
branches.updated_by=Päivitetty %[1]s %[2]s
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.upload_file=Liitä tiedosto
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Testisähköpostin lähettäminen vastaanottajalle
config.email.test_mail_sent=Testi sähköposti on lähetetty vastaanottajalle '%s'.
config.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.reset_password_code_lives=Nollaa salasana koodi elämät
config.auth.require_email_confirm=Vaadi sähköpostivahvistus

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Branches stagnantes
branches.all=Toutes les Branches
branches.updated_by=Mise à jour %[1]s par %[2]s
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.upload_file=Téléverser un fichier
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Impossible d'envoyer un e-mail de test à '%s' :
config.email.test_mail_sent=Un e-mail de test à été envoyé à '%s'.
config.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.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

View File

@@ -495,6 +495,8 @@ branches.stale_branches=Stale Branches
branches.all=All Branches
branches.updated_by=Updated %[1]s by %[2]s
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.upload_file=Subir arquivo
@@ -1275,6 +1277,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Elavult ágak
branches.all=Minden ág
branches.updated_by=Frissítve ekkor: %[1]s %[2]s által
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.upload_file=Fájl feltöltése
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Nem sikerült kiküldeni a teszt e-mailt '%s'-nek:
config.email.test_mail_sent=Teszt e-mail kiküldve '%s'-nek.
config.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.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

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Cabang Basi
branches.all=Semua Cabang
branches.updated_by=Diperbarui %[1]s oleh %[2]s
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.upload_file=Unggah Berkas
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Gagal mengirim surel uji ke '%s': %v
config.email.test_mail_sent=Surel uji telah dikirim ke '%s'.
config.auth_config=Konfigurasi otentikasi
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Perlu konfirmasi surel

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Stale Branches
branches.all=Tutti i rami (branch)
branches.updated_by=Updated %[1]s by %[2]s
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.upload_file=Carica File
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=古いブランチ
branches.all=すべてのブランチ
branches.updated_by=%[1]s が %[2]s によって更新されました
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.upload_file=ファイルをアップロード
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation

View File

@@ -495,6 +495,8 @@ branches.stale_branches=오래된 브랜치
branches.all=모든 브랜치
branches.updated_by=%[2]s이 %[1]s를 업데이트
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.upload_file=파일 업로드
@@ -1276,6 +1278,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent='%s'로 테스트 이메일을 보냈습니다.
config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=이메일 인증 필요

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Pamests atzars
branches.all=Visi atzari
branches.updated_by=%[2]s atjaunoja %[1]s
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.upload_file=Augšupielādēt failu
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Хуучирсан салаанууд
branches.all=Бүх салаанууд
branches.updated_by=Шинэчлэгдсэн %[1]s by %[2]s
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.upload_file=Файл хуулах
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Туршилтын имэйлийг '%s': %v рү
config.email.test_mail_sent=Туршилтын имэйл '%s' рүү илгээгдлээ.
config.auth_config=Authentication тохиргоо
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Кодын ашиглалтыг идэвхжүүлэх
config.auth.reset_password_code_lives=Нууц үгийн кодыг шинэчлэх
config.auth.require_email_confirm=Имэйлээр баталгаажуулахыг шаардана

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Stale Branches
branches.all=All Branches
branches.updated_by=Updated %[1]s by %[2]s
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.upload_file=Bestand uploaden
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Stare gałęzie
branches.all=Wszystkie gałęzie
branches.updated_by=Zaktualizowano %[1]s przez %[2]s
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.upload_file=Załaduj plik
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Nie udało się wysłać wiadomości testowej do '
config.email.test_mail_sent=Wiadomość testowa została wysłana do '%s'.
config.auth_config=Konfiguracja uwierzytelniania
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Wymagaj potwierdzenia adresu e-mail

View File

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

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Ramuri vechi
branches.all=Toate ramurile
branches.updated_by=Actualizat %[1]s prin %[2]s
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.upload_file=Încărcați fișier
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Nu a reușit să trimită un e-mail de test către
config.email.test_mail_sent=E-mailul de test a fost trimis la '%s'.
config.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.reset_password_code_lives=Resetează viețile codului parolei
config.auth.require_email_confirm=Solicită confirmarea prin e-mail

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Устаревшие ветки
branches.all=Все ветки
branches.updated_by=Обновлено %[1]s пользователем %[2]s
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.upload_file=Загрузить файл
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Не удалось отправить тесто
config.email.test_mail_sent=Тестовое письмо было отправлено на %s
config.auth_config=Конфигурация аутентификации
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Срок действия кода сброса пароля
config.auth.require_email_confirm=Требовать подтверждение по электронной почте

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Zastaralé vetvy
branches.all=Všetky vetvy
branches.updated_by=%[2]s zmenil %[1]s
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.upload_file=Nahrať súbor
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Застареле гране
branches.all=Све гране
branches.updated_by=Ажуриран %[1]s од %[2]s
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.upload_file=Отпреми датотеку
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Djärva brancher
branches.all=Alla brancher
branches.updated_by=Uppdaterade %[1]s med %[2]s
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.upload_file=Ladda upp fil
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Eskimiş Bölümler
branches.all=Bütün Bölümler
branches.updated_by=%[2]s tarafından %[1]s güncellendi
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.upload_file=Dosyayı yükle
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Застарілі гілки
branches.all=Усі гілки
branches.updated_by=Оновлено %[1]s з %[2]s
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.upload_file=Завантажити файл
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Помилка відправлення пробн
config.email.test_mail_sent=Пробного листа було відправлено до '%s'.
config.auth_config=Налаштування аутентифікації
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Активувати код підтвердження
config.auth.reset_password_code_lives=Термін придатності кода при скиданні пароля
config.auth.require_email_confirm=Вимагає підтвердження електронною поштою

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Các nhánh cũ
branches.all=Tất cả các nhánh
branches.updated_by=Updated %[1]s by %[2]s
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.upload_file=Tải tập tin lên
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Gửi email kiểm tra đến '%s':%v thất bại
config.email.test_mail_sent=Email kiểm tra đã được gửi đến '%s'.
config.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.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Yêu cầu xác nhận email

View File

@@ -494,6 +494,8 @@ branches.stale_branches=陈旧分支
branches.all=所有分支
branches.updated_by=由 %[2]s 更新于 %[1]s
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.upload_file=上传文件
@@ -1275,6 +1277,7 @@ config.email.test_mail_failed=发送测试邮件至 '%s' 时失败:%v
config.email.test_mail_sent=测试邮件已经发送至 '%s'。
config.auth_config=认证配置
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=激活用户链接有效期
config.auth.reset_password_code_lives=重置密码链接有效期
config.auth.require_email_confirm=注册邮件确认

View File

@@ -494,6 +494,8 @@ branches.stale_branches=Stale Branches
branches.all=All Branches
branches.updated_by=Updated %[1]s by %[2]s
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.upload_file=Upload file
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=Failed to send test email to '%s': %v
config.email.test_mail_sent=Test email has been sent to '%s'.
config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation

View File

@@ -494,6 +494,8 @@ branches.stale_branches=陳舊分支
branches.all=所有分支
branches.updated_by=%[2]s 更新了 %[1]s
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.upload_file=上傳檔案
@@ -1274,6 +1276,7 @@ config.email.test_mail_failed=發送測試郵件至 '%s' 時失敗:%v
config.email.test_mail_sent=測試電子郵件已發送到 '%s'。
config.auth_config=Authentication configuration
config.auth_custom_logout_url=Custom logout URL
config.auth.activate_code_lives=Activate code lives
config.auth.reset_password_code_lives=Reset password code lives
config.auth.require_email_confirm=Require email confirmation

View File

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

View File

@@ -1,10 +1,15 @@
# Docker for Gogs
> [!WARNING]
> This is now the legacy Docker image that lacks modern security best practices. It will be published as `gogs/gogs:legacy-latest` starting 0.15.0, and be completely removed starting 0.16.0.
> This is now the legacy Docker image that lacks modern security best practices. It will be published as `gogs/gogs:legacy-latest` starting 0.16.0, and be completely removed no earlier than 0.17.0.
>
> To use the next-generation, security-focused Docker image, see [docker-next/README.md](../docker-next/README.md).
> [!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)
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,12 +1,12 @@
# 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
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:
@@ -18,7 +18,7 @@ Indexes:
```
FIELD | COLUMN | POSTGRESQL | MYSQL | SQLITE3
--------------+--------------+-----------------------------+-----------------------------+------------------------------
ID | id | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER
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
@@ -36,7 +36,7 @@ Indexes:
```
FIELD | COLUMN | POSTGRESQL | MYSQL | SQLITE3
---------------+----------------+--------------------------------+--------------------------------+---------------------------------
ID | id | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER
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
@@ -60,7 +60,7 @@ Indexes:
```
FIELD | COLUMN | POSTGRESQL | MYSQL | SQLITE3
--------------+--------------+--------------------------------+--------------------------------+---------------------------------
ID | id | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER
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
@@ -74,11 +74,11 @@ Indexes:
# 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
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:
@@ -102,16 +102,16 @@ 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
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
```
@@ -119,12 +119,12 @@ 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
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
```

23
go.mod
View File

@@ -7,6 +7,8 @@ require (
github.com/cockroachdb/errors v1.12.0
github.com/derision-test/go-mockgen/v2 v2.1.1
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3
github.com/glebarez/go-sqlite v1.21.2
github.com/glebarez/sqlite v1.11.0
github.com/go-ldap/ldap/v3 v3.4.11
github.com/go-macaron/binding v1.2.0
github.com/go-macaron/cache v0.0.0-20190810181446-10f7c57e2196
@@ -18,7 +20,7 @@ require (
github.com/go-macaron/toolbox v0.0.0-20190813233741-94defb8383c6
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14
github.com/gogs/git-module v1.8.4
github.com/gogs/git-module v1.8.7
github.com/gogs/go-gogs-client v0.0.0-20200128182646-c69cb7680fd4
github.com/gogs/go-libravatar v0.0.0-20191106065024-33a75213d0a0
github.com/gogs/minwinsvc v0.0.0-20170301035411-95be6356811a
@@ -49,19 +51,19 @@ require (
gopkg.in/DATA-DOG/go-sqlmock.v2 v2.0.0-20180914054222-c19298f520d0
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.67.0
gopkg.in/macaron.v1 v1.5.0
gopkg.in/macaron.v1 v1.5.1
gorm.io/driver/mysql v1.5.2
gorm.io/driver/postgres v1.6.0
gorm.io/driver/sqlite v1.4.2
gorm.io/driver/sqlserver v1.4.1
gorm.io/gorm v1.25.12
modernc.org/sqlite v1.38.2
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 (
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/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
@@ -72,6 +74,7 @@ require (
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/denisenkom/go-mssqldb v0.12.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/djherbis/buffer v1.2.0 // indirect
github.com/djherbis/nio/v3 v3.0.1 // indirect
@@ -83,7 +86,7 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-macaron/inject v0.0.0-20200308113650-138e5925c53b // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
@@ -101,12 +104,13 @@ require (
github.com/klauspost/compress v1.18.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lib/pq v1.10.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mattn/go-sqlite3 v1.14.32 // indirect
github.com/mattn/go-sqlite3 v1.14.24 // indirect
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 // indirect
github.com/microsoft/go-mssqldb v1.8.2 // indirect
github.com/microsoft/go-mssqldb v0.17.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
@@ -127,7 +131,7 @@ require (
go.opentelemetry.io/otel/trace v1.11.0 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.38.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
@@ -137,6 +141,7 @@ require (
modernc.org/libc v1.66.3 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.39.0 // indirect
)
// +heroku goVersion go1.25

175
go.sum
View File

@@ -1,35 +1,33 @@
bitbucket.org/creachadair/shell v0.0.7 h1:Z96pB6DkSb7F3Y3BBnJeOZH2gazyMTWlvecSD4vDqfk=
bitbucket.org/creachadair/shell v0.0.7/go.mod h1:oqtXSSvSYr4624lnnabXHaBsYW6RD80caLi2b3hJk0U=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e/go.mod h1:uJEsN4LQpeGYRCjuPXPZBClU7N5pWzGuyF4uqLpE/e0=
gitea.com/lunny/nodb v0.0.0-20200923032308-3238c4655727/go.mod h1:h0OwsgcpJLSYtHcM5+Xciw9OEeuxi6ty4HDiO8C7aIY=
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
github.com/Azure/azure-sdk-for-go/sdk/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 v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0/go.mod h1:+6sju8gk8FRmSajX3Oz4G5Gm7P+mbqE9FVaXXFYTkCM=
github.com/Azure/azure-sdk-for-go/sdk/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 v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/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/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 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 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/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/alecthomas/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-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/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/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/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
@@ -42,6 +40,7 @@ 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/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/errors v1.12.0 h1:d7oCs6vuIMUQRVbi6jWWWEJZahLCfJpnJSVobd1/sUo=
github.com/cockroachdb/errors v1.12.0/go.mod h1:SvzfYNNBshAVbZ8wzNc/UPK3w1vf0dKDUP41ucAIf7g=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
@@ -61,6 +60,9 @@ github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGii
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/denisenkom/go-mssqldb v0.12.0 h1:VtrkII767ttSPNRfFekePK3sctr+joXgO58stqQbtUA=
github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU=
github.com/derision-test/go-mockgen/v2 v2.1.1 h1:MXG9rzyvsrDBfa1a1GatvHjCrbmEug3hVt0rSOXipCw=
github.com/derision-test/go-mockgen/v2 v2.1.1/go.mod h1:cDK2Y9IF5roTJgugWV23IvlOJsllhDN5zxRDN+g4cZo=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
@@ -74,6 +76,9 @@ 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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3 h1:XVUp6qW3BIkmM3/1EkrHpa6bL56APOynfXcZEmIgOhs=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3/go.mod h1:ThHVc+hqbUsmE1wmK/MASpQEhCleWu1JDJDNhUOMy0c=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
@@ -87,12 +92,18 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
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/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/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.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
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.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -121,18 +132,22 @@ github.com/go-macaron/toolbox v0.0.0-20190813233741-94defb8383c6/go.mod h1:YFNJ/
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
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/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
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/cron v0.0.0-20171120032916-9f6c956d3e14 h1:yXtpJr/LV6PFu4nTLgfjQdcMdzjbqqXMEnHfq0Or6p8=
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14/go.mod h1:jPoNZLWDAqA5N3G5amEoiNbhVrmM+ZQEcnQvNQ2KaZk=
github.com/gogs/git-module v1.8.4 h1:oSt8sOL4NWOGrSo/CwbS+C4YXtk76QvxyPofem/ViTU=
github.com/gogs/git-module v1.8.4/go.mod h1:bQY0aoMK5Q5+NKgy4jXe3K1GFW+GnsSk0SJK0jh6yD0=
github.com/gogs/git-module v1.8.7 h1:GDyfzB1Z8ytld3LajTfUE4PuIcGcuCHpWB6j8/oD7Tk=
github.com/gogs/git-module v1.8.7/go.mod h1:IiMSJqi8XH62Kjqjt5Rw8IawSo+DHfM2dDjkSzWLjhs=
github.com/gogs/go-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-libravatar v0.0.0-20191106065024-33a75213d0a0 h1:K02vod+sn3M1OOkdqi2tPxN2+xESK4qyITVQ3JkGEv4=
@@ -140,16 +155,17 @@ github.com/gogs/go-libravatar v0.0.0-20191106065024-33a75213d0a0/go.mod h1:Zas3B
github.com/gogs/minwinsvc v0.0.0-20170301035411-95be6356811a h1:8DZwxETOVWIinYxDK+i6L+rMb7eGATGaakD6ZucfHVk=
github.com/gogs/minwinsvc v0.0.0-20170301035411-95be6356811a/go.mod h1:TUIZ+29jodWQ8Gk6Pvtg4E09aMsc3C/VLZiVYfUhWQU=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
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/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-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/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.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -163,6 +179,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/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/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.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -175,6 +193,8 @@ github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+u
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
@@ -182,14 +202,19 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
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-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
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/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/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/go.mod h1:sQO+mQUCVfxOKPht+ipDSkJ2SCJ7BNJVHZexsXqWMx4=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
@@ -235,13 +260,17 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
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/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/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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -251,7 +280,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ=
github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af/go.mod h1:Cqz6pqow14VObJ7peltM+2n3PWOz7yTrfUuGbVFkzN0=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -270,17 +302,15 @@ github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWV
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.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
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/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 h1:YocNLcTBdEdvY3iDK6jfWXvEaM5OCKkjxPKoJRdB3Gg=
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE=
github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ=
github.com/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-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -292,6 +322,7 @@ github.com/msteinert/pam v1.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE=
github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
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/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
@@ -321,15 +352,17 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
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.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/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/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/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.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -337,14 +370,22 @@ 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/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
@@ -366,22 +407,27 @@ github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBf
github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d/go.mod h1:vq0tzqLRu6TS7Id0wMo2N5QzJoKedVeovOpHjnykSzY=
github.com/siddontang/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/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-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/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 v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
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/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/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
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.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@@ -407,8 +453,11 @@ github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
github.com/yuin/goldmark v1.1.27/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/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/go.mod h1:H2KtuEphyMvlhZ+F7tg9GRhAOe60moNx61Ex+WmiKkk=
go.opentelemetry.io/otel/sdk v1.11.0 h1:ZnKIL9V9Ztaq+ME43IUi/eo22mNsb6a7tGfzaOWB5fo=
@@ -417,7 +466,9 @@ go.opentelemetry.io/otel/trace v1.11.0 h1:20U/Vj42SX+mASlXLmSGBg6jpI1jQtv682lZtT
go.opentelemetry.io/otel/trace v1.11.0/go.mod h1:nyYjis9jy0gytE9LXGU+/m1sHTKbRY0fX0hulNNDP1U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
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-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-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -428,13 +479,23 @@ golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.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/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.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
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-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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
@@ -445,18 +506,27 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-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-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
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-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-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.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -491,13 +561,19 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
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-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-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190802220118-1d1727260058/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
@@ -511,8 +587,17 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
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-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.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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -525,6 +610,7 @@ google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9x
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/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/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e h1:wGA78yza6bu/mWcc4QfBuIEHEtc06xdiU0X8sY36yUU=
@@ -544,8 +630,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.5/go.mod h1:uMZCFccv9yr5TipIalVOyAyZQuOH3OkmXvgcWwhJuP4=
gopkg.in/macaron.v1 v1.4.0/go.mod h1:uMZCFccv9yr5TipIalVOyAyZQuOH3OkmXvgcWwhJuP4=
gopkg.in/macaron.v1 v1.5.0 h1:/dXJaeQagWLjVjCrKH8dgSSU7yG4qTv6rBKpqhYaCyc=
gopkg.in/macaron.v1 v1.5.0/go.mod h1:sAYUd2r8Q+jLnCN4/ZmdAYHzQn67agV5sAqKFQgrRrw=
gopkg.in/macaron.v1 v1.5.1 h1:0ytdXYcf6//a8bzedl1fVXzPeIXblEqoNPntWAo9YLU=
gopkg.in/macaron.v1 v1.5.1/go.mod h1:AiquOw8YeZJC8sUe11vIO6NeA1/TKSlzQXuJ7Tc4cCQ=
gopkg.in/redis.v2 v2.3.2 h1:GPVIIB/JnL1wvfULefy3qXmPu1nfNu2d0yA09FHgwfs=
gopkg.in/redis.v2 v2.3.2/go.mod h1:4wl9PJ/CqzeHk3LVq1hNLHH8krm3+AXEgut4jVc++LU=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
@@ -566,14 +652,15 @@ gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs=
gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8=
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
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/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig=
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
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/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
@@ -594,8 +681,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
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/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
modernc.org/sqlite v1.39.0 h1:6bwu9Ooim0yVYA7IZn9demiQk/Ejp0BtTjBWFLymSeY=
modernc.org/sqlite v1.39.0/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
@@ -604,3 +691,9 @@ mvdan.cc/gofumpt v0.4.0 h1:JVf4NN1mIpHogBj7ABpgOyZc65/UUOkKQFkoURsz4MM=
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/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

@@ -14,7 +14,7 @@ import (
)
func init() {
conf.App.Version = "0.14.0+dev"
conf.App.Version = "0.14.2"
}
func main() {

View File

@@ -329,9 +329,12 @@ func runWeb(c *cli.Context) error {
return
}
})
}, ignSignIn)
m.Group("", func() {
m.Post("/issues/attachments", repo.UploadIssueAttachment)
m.Post("/releases/attachments", repo.UploadReleaseAttachment)
}, ignSignIn)
}, reqSignIn)
m.Group("/:username", func() {
m.Post("/action/:action", user.Action)
@@ -476,7 +479,7 @@ func runWeb(c *cli.Context) error {
m.Get("/milestones", repo.Milestones)
}, ignSignIn, context.RepoAssignment(true))
m.Group("/:username/:reponame", func() {
// FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
// FIXME: should use different URLs but mostly same logic for comments of issue and pull request.
// So they can apply their own enable/disable logic on routers.
m.Group("/issues", func() {
m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue).
@@ -501,7 +504,7 @@ func runWeb(c *cli.Context) error {
}, ignSignIn, context.RepoAssignment(false, true))
m.Group("/:username/:reponame", func() {
// FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
// FIXME: should use different URLs but mostly same logic for comments of issue and pull request.
// So they can apply their own enable/disable logic on routers.
m.Group("/issues", func() {
m.Group("/:index", func() {

View File

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

View File

@@ -1,11 +1,15 @@
package conf
import (
"fmt"
"net/url"
"os"
"time"
"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.
@@ -357,8 +361,9 @@ type DatabaseOpts struct {
var Database DatabaseOpts
type LFSOpts struct {
Storage string
ObjectsPath string
Storage string
ObjectsPath string
ObjectsTempPath string
}
// LFS settings
@@ -430,6 +435,83 @@ func handleDeprecated() {
// }
}
// checkInvalidOptions checks invalid (renamed/deleted) configuration sections
// and options and returns a list of warnings.
//
// LEGACY [0.15]: Delete this function.
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{
{"security", "REVERSE_PROXY_AUTHENTICATION_USER"}: {"auth", "REVERSE_PROXY_AUTHENTICATION_HEADER"},
{"auth", "ACTIVE_CODE_LIVE_MINUTES"}: {"auth", "ACTIVATE_CODE_LIVES"},
{"auth", "RESET_PASSWD_CODE_LIVE_MINUTES"}: {"auth", "RESET_PASSWORD_CODE_LIVES"},
{"auth", "ENABLE_CAPTCHA"}: {"auth", "ENABLE_REGISTRATION_CAPTCHA"},
{"auth", "ENABLE_NOTIFY_MAIL"}: {"user", "ENABLE_EMAIL_NOTIFICATION"},
{"auth", "REGISTER_EMAIL_CONFIRM"}: {"auth", "REQUIRE_EMAIL_CONFIRMATION"},
{"session", "GC_INTERVAL_TIME"}: {"session", "GC_INTERVAL"},
{"session", "SESSION_LIFE_TIME"}: {"session", "MAX_LIFE_TIME"},
{"server", "ROOT_URL"}: {"server", "EXTERNAL_URL"},
{"server", "LANDING_PAGE"}: {"server", "LANDING_URL"},
{"database", "DB_TYPE"}: {"database", "TYPE"},
{"database", "PASSWD"}: {"database", "PASSWORD"},
}
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.
// All operations should be done synchronously to prevent program exits before finishing.
//

View File

@@ -1,9 +1,11 @@
package conf
import (
"sort"
"testing"
"github.com/stretchr/testify/assert"
"gopkg.in/ini.v1"
)
func Test_i18n_DateLang(t *testing.T) {
@@ -29,3 +31,48 @@ func Test_i18n_DateLang(t *testing.T) {
})
}
}
func TestCheckInvalidOptions(t *testing.T) {
cfg := ini.Empty()
_, _ = cfg.Section("mailer").NewKey("ENABLED", "true")
_, _ = cfg.Section("service").NewKey("START_TYPE", "true")
_, _ = cfg.Section("security").NewKey("REVERSE_PROXY_AUTHENTICATION_USER", "true")
_, _ = cfg.Section("auth").NewKey("ACTIVE_CODE_LIVE_MINUTES", "10")
_, _ = cfg.Section("auth").NewKey("RESET_PASSWD_CODE_LIVE_MINUTES", "10")
_, _ = cfg.Section("auth").NewKey("ENABLE_CAPTCHA", "true")
_, _ = cfg.Section("auth").NewKey("ENABLE_NOTIFY_MAIL", "true")
_, _ = cfg.Section("auth").NewKey("REGISTER_EMAIL_CONFIRM", "true")
_, _ = cfg.Section("session").NewKey("GC_INTERVAL_TIME", "10")
_, _ = cfg.Section("session").NewKey("SESSION_LIFE_TIME", "10")
_, _ = cfg.Section("server").NewKey("ROOT_URL", "true")
_, _ = cfg.Section("server").NewKey("LANDING_PAGE", "true")
_, _ = cfg.Section("database").NewKey("DB_TYPE", "true")
_, _ = cfg.Section("database").NewKey("PASSWD", "true")
_, _ = cfg.Section("other").NewKey("SHOW_FOOTER_BRANDING", "true")
_, _ = cfg.Section("other").NewKey("SHOW_FOOTER_TEMPLATE_LOAD_TIME", "true")
_, _ = cfg.Section("email").NewKey("ENABLED", "true")
_, _ = cfg.Section("server").NewKey("NONEXISTENT_OPTION", "true")
wantWarnings := []string{
"option [auth] ACTIVE_CODE_LIVE_MINUTES is invalid, use [auth] ACTIVATE_CODE_LIVES instead",
"option [auth] ENABLE_CAPTCHA is invalid, use [auth] ENABLE_REGISTRATION_CAPTCHA instead",
"option [auth] ENABLE_NOTIFY_MAIL is invalid, use [user] ENABLE_EMAIL_NOTIFICATION instead",
"option [auth] REGISTER_EMAIL_CONFIRM is invalid, use [auth] REQUIRE_EMAIL_CONFIRMATION instead",
"option [auth] RESET_PASSWD_CODE_LIVE_MINUTES is invalid, use [auth] RESET_PASSWORD_CODE_LIVES instead",
"option [database] DB_TYPE is invalid, use [database] TYPE instead",
"option [database] PASSWD is invalid, use [database] PASSWORD instead",
"option [security] REVERSE_PROXY_AUTHENTICATION_USER is invalid, use [auth] REVERSE_PROXY_AUTHENTICATION_HEADER instead",
"option [session] GC_INTERVAL_TIME is invalid, use [session] GC_INTERVAL instead",
"option [session] SESSION_LIFE_TIME is invalid, use [session] MAX_LIFE_TIME instead",
"section [mailer] is invalid, use [email] instead",
"section [service] is invalid, use [auth] instead",
"option [server] ROOT_URL is invalid, use [server] EXTERNAL_URL instead",
"option [server] LANDING_PAGE is invalid, use [server] LANDING_URL instead",
"option [server] NONEXISTENT_OPTION is invalid",
}
gotWarnings := checkInvalidOptions(cfg)
sort.Strings(wantWarnings)
sort.Strings(gotWarnings)
assert.Equal(t, wantWarnings, gotWarnings)
}

View File

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

View File

@@ -146,18 +146,12 @@ func authenticatedUserID(store AuthStore, c *macaron.Context, sess session.Store
// Check access token.
if isAPIPath(c.Req.URL.Path) {
tokenSHA := c.Query("token")
if len(tokenSHA) <= 0 {
tokenSHA = c.Query("access_token")
}
if tokenSHA == "" {
// Well, check with header again.
auHead := c.Req.Header.Get("Authorization")
if len(auHead) > 0 {
auths := strings.Fields(auHead)
if len(auths) == 2 && auths[0] == "token" {
tokenSHA = auths[1]
}
var tokenSHA string
auHead := c.Req.Header.Get("Authorization")
if auHead != "" {
auths := strings.Fields(auHead)
if len(auths) == 2 && auths[0] == "token" {
tokenSHA = auths[1]
}
}

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.
func (c *Context) RenderWithErr(msg, tpl string, f any) {
func (c *Context) RenderWithErr(msg string, status int, tpl string, f any) {
if f != nil {
form.Assign(f, c.Data)
}
c.Flash.ErrorMsg = msg
c.Data["Flash"] = c.Flash
c.HTML(http.StatusOK, tpl)
c.HTML(status, tpl)
}
// NotFound renders the 404 page.

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,8 +9,8 @@ import (
"github.com/cockroachdb/errors"
"github.com/unknwon/com"
"gorm.io/gorm"
log "unknwon.dev/clog/v2"
"xorm.io/xorm"
"github.com/gogs/git-module"
api "github.com/gogs/go-gogs-client"
@@ -71,31 +71,35 @@ func (pr *PullRequest) BeforeUpdate() {
}
// Note: don't try to get Issue because will end up recursive querying.
func (pr *PullRequest) AfterFind(tx *gorm.DB) error {
if pr.HasMerged {
func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) {
switch colName {
case "merged_unix":
if !pr.HasMerged {
return
}
pr.Merged = time.Unix(pr.MergedUnix, 0).Local()
}
return nil
}
// Note: don't try to get Issue because will end up recursive querying.
func (pr *PullRequest) loadAttributes(db *gorm.DB) (err error) {
func (pr *PullRequest) loadAttributes(e Engine) (err error) {
if pr.HeadRepo == nil {
pr.HeadRepo, err = getRepositoryByID(db, pr.HeadRepoID)
pr.HeadRepo, err = getRepositoryByID(e, pr.HeadRepoID)
if err != nil && !IsErrRepoNotExist(err) {
return errors.Newf("get head repository by ID: %v", err)
}
}
if pr.BaseRepo == nil {
pr.BaseRepo, err = getRepositoryByID(db, pr.BaseRepoID)
pr.BaseRepo, err = getRepositoryByID(e, pr.BaseRepoID)
if err != nil {
return errors.Newf("get base repository by ID: %v", err)
}
}
if pr.HasMerged && pr.Merger == nil {
pr.Merger, err = getUserByID(db, pr.MergerID)
pr.Merger, err = getUserByID(e, pr.MergerID)
if IsErrUserNotExist(err) {
pr.MergerID = -1
pr.Merger = NewGhostUser()
@@ -108,7 +112,7 @@ func (pr *PullRequest) loadAttributes(db *gorm.DB) (err error) {
}
func (pr *PullRequest) LoadAttributes() error {
return pr.loadAttributes(db)
return pr.loadAttributes(x)
}
func (pr *PullRequest) LoadIssue() (err error) {
@@ -195,190 +199,198 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false)
}()
return db.Transaction(func(tx *gorm.DB) error {
if err := pr.Issue.changeStatus(tx, doer, pr.Issue.Repo, true); err != nil {
return errors.Newf("Issue.changeStatus: %v", err)
}
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
headGitRepo, err := git.Open(headRepoPath)
if err != nil {
return errors.Newf("open repository: %v", err)
}
if err = pr.Issue.changeStatus(sess, doer, pr.Issue.Repo, true); err != nil {
return errors.Newf("Issue.changeStatus: %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))
}()
headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
headGitRepo, err := git.Open(headRepoPath)
if err != nil {
return errors.Newf("open repository: %v", err)
}
// 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)
}
// 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))
}()
// Add remote which points to the head repository.
// 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 remote add): %s", tmpBasePath),
"git", "remote", "add", "head_repo", headRepoPath); err != nil {
return errors.Newf("git remote add [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
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)
}
// Fetch information from head repository to the temporary copy.
// Create a merge commit for the base branch.
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)
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)
}
remoteHeadBranch := "head_repo/" + pr.HeadBranch
case MergeStyleRebase: // Rebase before merging
// 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.
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
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)
}
case MergeStyleRebase: // Rebase before merging
// Rebase head branch based on base branch, this creates a non-branch commit state.
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)
}
// Name non-branch commit state to a new temporary branch in order to save changes.
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)
}
// Check out the base branch to be operated on.
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
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.
// Rebase head branch based on base branch, this creates a non-branch commit state.
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)
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)
}
pr.MergedCommitID, err = headGitRepo.BranchCommitID(pr.HeadBranch)
if err != nil {
return errors.Newf("get head branch %q commit ID: %v", pr.HeadBranch, err)
// Name non-branch commit state to a new temporary branch in order to save changes.
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)
}
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)
// Check out the base branch to be operated on.
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath),
"git", "checkout", pr.BaseBranch); err != nil {
return errors.Newf("git checkout '%s': %s", pr.BaseBranch, stderr)
}
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)
// 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)
}
// 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
}
default:
return errors.Newf("unknown merge style: %s", mergeStyle)
}
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
}
// 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)
}
// 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...)
}
pr.MergedCommitID, err = headGitRepo.BranchCommitID(pr.HeadBranch)
if err != nil {
return errors.Newf("get head branch %q commit ID: %v", pr.HeadBranch, err)
}
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
}
pr.HasMerged = true
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)
}
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
}
if err = sess.Commit(); err != nil {
return errors.Newf("commit: %v", err)
}
if err = Handle.Actions().MergePullRequest(ctx, doer, pr.Issue.Repo.Owner, pr.Issue.Repo, pr.Issue); err != nil {
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
}
// testPatch checks if patch can be merged to base repository without conflict.
@@ -431,42 +443,45 @@ func (pr *PullRequest) testPatch() (err error) {
// 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) {
err = db.Transaction(func(tx *gorm.DB) error {
if err := newIssue(tx, 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 := tx.Create(pr).Error; err != nil {
return errors.Newf("insert pull repo: %v", err)
}
return nil
})
if err != nil {
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
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{
ActUserID: pull.Poster.ID,
ActUserName: pull.Poster.Name,
@@ -502,20 +517,18 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
// by given head/base and repo/branch.
func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) {
pr := new(PullRequest)
err := db.Joins("INNER JOIN issue ON issue.id = pull_request.issue_id").
Where("pull_request.head_repo_id = ? AND pull_request.head_branch = ? AND pull_request.base_repo_id = ? AND pull_request.base_branch = ? AND pull_request.has_merged = ? AND issue.is_closed = ?",
headRepoID, headBranch, baseRepoID, baseBranch, false, false).
First(pr).Error
has, err := x.Where("head_repo_id=? AND head_branch=? AND base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
headRepoID, headBranch, baseRepoID, baseBranch, false, false).
Join("INNER", "issue", "issue.id=pull_request.issue_id").Get(pr)
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
} else if !has {
return nil, ErrPullRequestNotExist{args: map[string]any{
"headRepoID": headRepoID,
"baseRepoID": baseRepoID,
"headBranch": headBranch,
"baseBranch": baseBranch,
}}
}
return pr, nil
@@ -525,22 +538,18 @@ func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch
// by given head information (repo and branch).
func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequest, error) {
prs := make([]*PullRequest, 0, 2)
err := db.Joins("INNER JOIN issue ON issue.id = pull_request.issue_id").
Where("pull_request.head_repo_id = ? AND pull_request.head_branch = ? AND pull_request.has_merged = ? AND issue.is_closed = ?",
repoID, branch, false, false).
Find(&prs).Error
return prs, err
return prs, x.Where("head_repo_id = ? AND head_branch = ? AND has_merged = ? AND issue.is_closed = ?",
repoID, branch, false, false).
Join("INNER", "issue", "issue.id = pull_request.issue_id").Find(&prs)
}
// GetUnmergedPullRequestsByBaseInfo returns all pull requests that are open and has not been merged
// by given base information (repo and branch).
func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequest, error) {
prs := make([]*PullRequest, 0, 2)
err := db.Joins("INNER JOIN issue ON issue.id = pull_request.issue_id").
Where("pull_request.base_repo_id = ? AND pull_request.base_branch = ? AND pull_request.has_merged = ? AND issue.is_closed = ?",
repoID, branch, false, false).
Find(&prs).Error
return prs, err
return prs, x.Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
repoID, branch, false, false).
Join("INNER", "issue", "issue.id=pull_request.issue_id").Find(&prs)
}
var _ errutil.NotFound = (*ErrPullRequestNotExist)(nil)
@@ -562,65 +571,50 @@ func (ErrPullRequestNotExist) NotFound() bool {
return true
}
func getPullRequestByID(db *gorm.DB, id int64) (*PullRequest, error) {
func getPullRequestByID(e Engine, id int64) (*PullRequest, error) {
pr := new(PullRequest)
err := db.First(pr, id).Error
has, err := e.ID(id).Get(pr)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrPullRequestNotExist{args: map[string]any{"pullRequestID": id}}
}
return nil, err
} else if !has {
return nil, ErrPullRequestNotExist{args: map[string]any{"pullRequestID": id}}
}
return pr, pr.loadAttributes(db)
return pr, pr.loadAttributes(e)
}
// GetPullRequestByID returns a pull request by given ID.
func GetPullRequestByID(id int64) (*PullRequest, error) {
return getPullRequestByID(db, id)
return getPullRequestByID(x, id)
}
func getPullRequestByIssueID(db *gorm.DB, issueID int64) (*PullRequest, error) {
pr := &PullRequest{}
err := db.Where("issue_id = ?", issueID).First(pr).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrPullRequestNotExist{args: map[string]any{"issueID": issueID}}
}
return nil, err
func getPullRequestByIssueID(e Engine, issueID int64) (*PullRequest, error) {
pr := &PullRequest{
IssueID: issueID,
}
return pr, pr.loadAttributes(db)
has, err := e.Get(pr)
if err != nil {
return nil, err
} else if !has {
return nil, ErrPullRequestNotExist{args: map[string]any{"issueID": issueID}}
}
return pr, pr.loadAttributes(e)
}
// GetPullRequestByIssueID returns pull request by given issue ID.
func GetPullRequestByIssueID(issueID int64) (*PullRequest, error) {
return getPullRequestByIssueID(db, issueID)
return getPullRequestByIssueID(x, issueID)
}
// Update updates all fields of pull request.
func (pr *PullRequest) Update() error {
return db.Model(&PullRequest{}).Where("id = ?", pr.ID).Updates(pr).Error
_, err := x.Id(pr.ID).AllCols().Update(pr)
return err
}
// Update updates specific fields of pull request.
func (pr *PullRequest) UpdateCols(cols ...string) error {
updates := make(map[string]any)
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
_, err := x.Id(pr.ID).Cols(cols...).Update(pr)
return err
}
// UpdatePatch generates and saves a new patch.
@@ -717,7 +711,7 @@ func (pr *PullRequest) AddToTaskQueue() {
type PullRequestList []*PullRequest
func (prs PullRequestList) loadAttributes(db *gorm.DB) (err error) {
func (prs PullRequestList) loadAttributes(e Engine) (err error) {
if len(prs) == 0 {
return nil
}
@@ -732,7 +726,7 @@ func (prs PullRequestList) loadAttributes(db *gorm.DB) (err error) {
issueIDs = append(issueIDs, issueID)
}
issues := make([]*Issue, 0, len(issueIDs))
if err = db.Where("id IN ?", issueIDs).Find(&issues).Error; err != nil {
if err = e.Where("id > 0").In("id", issueIDs).Find(&issues); err != nil {
return errors.Newf("find issues: %v", err)
}
for i := range issues {
@@ -744,7 +738,7 @@ func (prs PullRequestList) loadAttributes(db *gorm.DB) (err error) {
// Load attributes
for i := range prs {
if err = prs[i].loadAttributes(db); err != nil {
if err = prs[i].loadAttributes(e); err != nil {
return errors.Newf("loadAttributes [%d]: %v", prs[i].ID, err)
}
}
@@ -753,7 +747,7 @@ func (prs PullRequestList) loadAttributes(db *gorm.DB) (err error) {
}
func (prs PullRequestList) LoadAttributes() error {
return prs.loadAttributes(db)
return prs.loadAttributes(x)
}
func addHeadRepoTasks(prs []*PullRequest) {
@@ -844,23 +838,25 @@ func (pr *PullRequest) checkAndUpdateStatus() {
// TestPullRequests checks and tests untested patches of pull requests.
// TODO: test more pull requests at same time.
func TestPullRequests() {
var prs []*PullRequest
_ = db.Where("status = ?", PullRequestStatusChecking).FindInBatches(&prs, 100, func(tx *gorm.DB, batch int) error {
for i := range prs {
pr := prs[i]
prs := make([]*PullRequest, 0, 10)
_ = x.Iterate(PullRequest{
Status: PullRequestStatusChecking,
},
func(idx int, bean any) error {
pr := bean.(*PullRequest)
if err := pr.LoadAttributes(); err != nil {
log.Error("LoadAttributes: %v", err)
continue
return nil
}
if err := pr.testPatch(); err != nil {
log.Error("testPatch: %v", err)
continue
return nil
}
}
return nil
})
prs = append(prs, pr)
return nil
})
// Update pull request status.
for _, pr := range prs {

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"strings"
"time"
"github.com/cockroachdb/errors"
api "github.com/gogs/go-gogs-client"
@@ -27,6 +28,13 @@ func (r *Repository) BeforeUpdate(tx *gorm.DB) error {
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 {
Permission *api.Permission
Parent *api.Repository

View File

@@ -8,11 +8,11 @@ import (
"strings"
"github.com/cockroachdb/errors"
"github.com/glebarez/sqlite"
"github.com/olekukonko/tablewriter"
"gopkg.in/DATA-DOG/go-sqlmock.v2"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/schema"
@@ -65,11 +65,13 @@ func main() {
table.SetHeader([]string{"Field", "Column", "PostgreSQL", "MySQL", "SQLite3"})
table.SetBorder(false)
for j, f := range ti.Fields {
sqlite3Type := strings.ToUpper(collected[2][i].Fields[j].Type)
sqlite3Type = strings.ReplaceAll(sqlite3Type, "PRIMARY KEY ", "")
table.Append([]string{
f.Name, f.Column,
strings.ToUpper(f.Type), // PostgreSQL
strings.ToUpper(collected[1][i].Fields[j].Type), // MySQL
strings.ToUpper(collected[2][i].Fields[j].Type), // SQLite3
sqlite3Type,
})
}
table.Render()

View File

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

View File

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

View File

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

View File

@@ -465,7 +465,7 @@ func usersDeleteByID(t *testing.T, ctx context.Context, s *UsersStore) {
reposStore := newReposStore(s.db)
t.Run("user still has repository ownership", func(t *testing.T) {
alice, err := s.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{})
alice, err := s.Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err)
_, 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) {
bob, err := s.Create(ctx, "bob", "bob@exmaple.com", CreateUserOptions{})
bob, err := s.Create(ctx, "bob", "bob@example.com", CreateUserOptions{})
require.NoError(t, err)
// 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)
})
cindy, err := s.Create(ctx, "cindy", "cindy@exmaple.com", CreateUserOptions{})
cindy, err := s.Create(ctx, "cindy", "cindy@example.com", CreateUserOptions{})
require.NoError(t, err)
frank, err := s.Create(ctx, "frank", "frank@exmaple.com", CreateUserOptions{})
frank, err := s.Create(ctx, "frank", "frank@example.com", CreateUserOptions{})
require.NoError(t, err)
repo2, err := reposStore.Create(ctx, cindy.ID, CreateRepoOptions{Name: "repo2"})
require.NoError(t, err)
testUser, err := s.Create(ctx, "testUser", "testUser@exmaple.com", CreateUserOptions{})
testUser, err := s.Create(ctx, "testUser", "testUser@example.com", CreateUserOptions{})
require.NoError(t, err)
// 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) {
// User with repository ownership should be skipped
alice, err := s.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{})
alice, err := s.Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err)
reposStore := newReposStore(s.db)
_, err = reposStore.Create(ctx, alice.ID, CreateRepoOptions{Name: "repo1"})
require.NoError(t, err)
// User with organization membership should be skipped
bob, err := s.Create(ctx, "bob", "bob@exmaple.com", CreateUserOptions{})
bob, err := s.Create(ctx, "bob", "bob@example.com", CreateUserOptions{})
require.NoError(t, err)
// TODO: Use Orgs.Create to replace SQL hack when the method is available.
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)
// User activated state should be skipped
_, err = s.Create(ctx, "cindy", "cindy@exmaple.com", CreateUserOptions{Activated: true})
_, err = s.Create(ctx, "cindy", "cindy@example.com", CreateUserOptions{Activated: true})
require.NoError(t, err)
// User meant to be deleted
david, err := s.Create(ctx, "david", "david@exmaple.com", CreateUserOptions{})
david, err := s.Create(ctx, "david", "david@example.com", CreateUserOptions{})
require.NoError(t, err)
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) {
// TODO: Use Orgs.Create to replace SQL hack when the method is available.
org, err := s.Create(ctx, "gogs", "gogs@exmaple.com", CreateUserOptions{})
org, err := s.Create(ctx, "gogs", "gogs@example.com", CreateUserOptions{})
require.NoError(t, err)
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) {
alice, err := s.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{})
alice, err := s.Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err)
_, err = s.GetByEmail(ctx, alice.Email)
@@ -760,7 +760,7 @@ func usersGetByEmail(t *testing.T, ctx context.Context, s *UsersStore) {
require.NoError(t, err)
// TODO: Use UserEmails.Create to replace SQL hack when the method is available.
email2 := "bob2@exmaple.com"
email2 := "bob2@example.com"
err = s.db.Exec(`INSERT INTO email_address (uid, email) VALUES (?, ?)`, bob.ID, email2).Error
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) {
alice, err := s.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{})
alice, err := s.Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err)
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) {
alice, err := s.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{})
alice, err := s.Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err)
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) {
alice, err := s.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{})
alice, err := s.Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err)
// 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) {
alice, err := s.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{})
alice, err := s.Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
require.NoError(t, err)
bob, err := s.Create(ctx, "bob", "bob@exmaple.com", CreateUserOptions{Activated: true})
bob, err := s.Create(ctx, "bob", "bob@example.com", CreateUserOptions{Activated: true})
require.NoError(t, err)
_, err = s.Create(ctx, "cindy", "cindy@exmaple.com", CreateUserOptions{Activated: true})
_, err = s.Create(ctx, "cindy", "cindy@example.com", CreateUserOptions{Activated: true})
require.NoError(t, err)
got, err := s.GetMailableEmailsByUsernames(ctx, []string{alice.Name, bob.Name, "ignore-non-exist"})

View File

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

View File

@@ -96,19 +96,6 @@ func NewDB(t *testing.T, suite string, tables ...any) *gorm.DB {
_, _ = sqlDB.Exec(fmt.Sprintf(`DROP DATABASE %q`, dbName))
_ = sqlDB.Close()
}
case "sqlite":
dbName = filepath.Join(os.TempDir(), fmt.Sprintf("gogs-%s-%d.db", suite, time.Now().Unix()))
dbOpts = conf.DatabaseOpts{
Type: "sqlite",
Path: dbName,
}
cleanup = func(db *gorm.DB) {
sqlDB, err := db.DB()
if err == nil {
_ = sqlDB.Close()
}
_ = os.Remove(dbName)
}
default:
dbName = filepath.Join(os.TempDir(), fmt.Sprintf("gogs-%s-%d.db", suite, time.Now().Unix()))
dbOpts = conf.DatabaseOpts{

View File

@@ -5,9 +5,9 @@ import (
"strings"
"github.com/cockroachdb/errors"
"github.com/glebarez/sqlite"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/driver/sqlserver"
"gorm.io/gorm"
@@ -73,7 +73,7 @@ func NewDSN(opts conf.DatabaseOpts) (dsn string, err error) {
dsn = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;",
host, port, opts.Name, opts.User, opts.Password)
case "sqlite3", "sqlite":
case "sqlite3":
dsn = "file:" + opts.Path + "?cache=shared&mode=rwc"
default:
@@ -101,9 +101,6 @@ func OpenDB(opts conf.DatabaseOpts, cfg *gorm.Config) (*gorm.DB, error) {
dialector = sqlserver.Open(dsn)
case "sqlite3":
dialector = sqlite.Open(dsn)
case "sqlite":
dialector = sqlite.Open(dsn)
dialector.(*sqlite.Dialector).DriverName = "sqlite"
default:
panic("unreachable")
}

View File

@@ -73,7 +73,7 @@ func (s *DiffSection) ComputedInlineDiffFor(line *git.DiffLine) template.HTML {
func diffsToHTML(diffs []diffmatchpatch.Diff, lineType git.DiffLineType) template.HTML {
buf := bytes.NewBuffer(nil)
// Reproduce signs which are cutted for inline diff before.
// Reproduce signs which are cut for inline diff before.
switch lineType {
case git.DiffLineAdd:
buf.WriteByte('+')

View File

@@ -1,6 +1,8 @@
package lfsutil
import (
"crypto/sha256"
"encoding/hex"
"io"
"os"
"path/filepath"
@@ -10,7 +12,10 @@ import (
"gogs.io/gogs/internal/osutil"
)
var ErrObjectNotExist = errors.New("Object does not exist")
var (
ErrObjectNotExist = errors.New("object does not exist")
ErrOIDMismatch = errors.New("content hash does not match OID")
)
// Storager is an storage backend for uploading and downloading LFS objects.
type Storager interface {
@@ -39,6 +44,8 @@ var _ Storager = (*LocalStorage)(nil)
type LocalStorage struct {
// The root path for storing LFS objects.
Root string
// The path for storing temporary files during upload verification.
TempDir string
}
func (*LocalStorage) Storage() Storage {
@@ -58,29 +65,50 @@ func (s *LocalStorage) Upload(oid OID, rc io.ReadCloser) (int64, error) {
return 0, ErrInvalidOID
}
var err error
fpath := s.storagePath(oid)
defer func() {
rc.Close()
dir := filepath.Dir(fpath)
if err != nil {
_ = os.Remove(fpath)
}
}()
defer rc.Close()
err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm)
if err != nil {
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
return 0, errors.Wrap(err, "create directories")
}
w, err := os.Create(fpath)
if err != nil {
return 0, errors.Wrap(err, "create file")
}
defer w.Close()
written, err := io.Copy(w, rc)
// If the object file already exists, skip the upload and return the
// existing file's size.
if fi, err := os.Stat(fpath); err == nil {
_, _ = io.Copy(io.Discard, rc)
return fi.Size(), nil
}
// Write to a temp file and verify the content hash before publishing.
// This ensures the final path always contains a complete, hash-verified
// file, even when concurrent uploads of the same OID race.
if err := os.MkdirAll(s.TempDir, os.ModePerm); err != nil {
return 0, errors.Wrap(err, "create temp directory")
}
tmp, err := os.CreateTemp(s.TempDir, "upload-*")
if err != nil {
return 0, errors.Wrap(err, "copy file")
return 0, errors.Wrap(err, "create temp file")
}
tmpPath := tmp.Name()
defer os.Remove(tmpPath)
hash := sha256.New()
written, err := io.Copy(tmp, io.TeeReader(rc, hash))
if closeErr := tmp.Close(); err == nil && closeErr != nil {
err = closeErr
}
if err != nil {
return 0, errors.Wrap(err, "write object file")
}
if computed := hex.EncodeToString(hash.Sum(nil)); computed != string(oid) {
return 0, ErrOIDMismatch
}
if err := os.Rename(tmpPath, fpath); err != nil && !os.IsExist(err) {
return 0, errors.Wrap(err, "publish object file")
}
return written, nil
}

View File

@@ -10,6 +10,9 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gogs.io/gogs/internal/osutil"
)
func TestLocalStorage_storagePath(t *testing.T) {
@@ -46,50 +49,54 @@ func TestLocalStorage_storagePath(t *testing.T) {
}
func TestLocalStorage_Upload(t *testing.T) {
base := t.TempDir()
s := &LocalStorage{
Root: filepath.Join(os.TempDir(), "lfs-objects"),
Root: filepath.Join(base, "lfs-objects"),
TempDir: filepath.Join(base, "tmp", "lfs"),
}
t.Cleanup(func() {
_ = os.RemoveAll(s.Root)
const helloWorldOID = OID("c0535e4be2b79ffd93291305436bf889314e4a3faec05ecffcbb7df31ad9e51a") // "Hello world!"
t.Run("invalid OID", func(t *testing.T) {
written, err := s.Upload("bad_oid", io.NopCloser(strings.NewReader("")))
assert.Equal(t, int64(0), written)
assert.Equal(t, ErrInvalidOID, err)
})
tests := []struct {
name string
oid OID
content string
expWritten int64
expErr error
}{
{
name: "invalid oid",
oid: "bad_oid",
expErr: ErrInvalidOID,
},
t.Run("valid OID", func(t *testing.T) {
written, err := s.Upload(helloWorldOID, io.NopCloser(strings.NewReader("Hello world!")))
require.NoError(t, err)
assert.Equal(t, int64(12), written)
})
{
name: "valid oid",
oid: "ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f",
content: "Hello world!",
expWritten: 12,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
written, err := s.Upload(test.oid, io.NopCloser(strings.NewReader(test.content)))
assert.Equal(t, test.expWritten, written)
assert.Equal(t, test.expErr, err)
})
}
t.Run("valid OID but wrong content", func(t *testing.T) {
oid := OID("ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f")
written, err := s.Upload(oid, io.NopCloser(strings.NewReader("Hello world!")))
assert.Equal(t, int64(0), written)
assert.Equal(t, ErrOIDMismatch, err)
// File should have been cleaned up.
assert.False(t, osutil.IsFile(s.storagePath(oid)))
})
t.Run("duplicate upload returns existing size", func(t *testing.T) {
written, err := s.Upload(helloWorldOID, io.NopCloser(strings.NewReader("should be ignored")))
require.NoError(t, err)
assert.Equal(t, int64(12), written)
// Verify original content is preserved.
var buf bytes.Buffer
err = s.Download(helloWorldOID, &buf)
require.NoError(t, err)
assert.Equal(t, "Hello world!", buf.String())
})
}
func TestLocalStorage_Download(t *testing.T) {
oid := OID("ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f")
s := &LocalStorage{
Root: filepath.Join(os.TempDir(), "lfs-objects"),
Root: filepath.Join(t.TempDir(), "lfs-objects"),
}
t.Cleanup(func() {
_ = os.RemoveAll(s.Root)
})
fpath := s.storagePath(oid)
err := os.MkdirAll(filepath.Dir(fpath), os.ModePerm)

View File

@@ -1,6 +1,8 @@
package markup
import (
"net/url"
"strings"
"sync"
"github.com/microcosm-cc/bluemonday"
@@ -32,14 +34,28 @@ func NewSanitizer() {
sanitizer.policy.AllowAttrs("type").Matching(lazyregexp.New(`^checkbox$`).Regexp()).OnElements("input")
sanitizer.policy.AllowAttrs("checked", "disabled").OnElements("input")
// Data URLs
sanitizer.policy.AllowURLSchemes("data")
// Only allow data URIs with safe image MIME types to prevent XSS via
// "data:text/html" payloads.
sanitizer.policy.AllowURLSchemeWithCustomPolicy("data", isSafeDataURI)
// Custom URL-Schemes
sanitizer.policy.AllowURLSchemes(conf.Markdown.CustomURLSchemes...)
})
}
// isSafeDataURI returns whether the given data URI uses a safe image MIME type.
func isSafeDataURI(u *url.URL) bool {
// The opaque data of a data URI has the form "mediatype;base64,data" or
// "mediatype,data". We only allow common image MIME types.
mediatype, _, _ := strings.Cut(u.Opaque, ";")
mediatype, _, _ = strings.Cut(mediatype, ",")
switch strings.TrimSpace(strings.ToLower(mediatype)) {
case "image/png", "image/jpeg", "image/gif", "image/webp", "image/x-icon":
return true
}
return false
}
// Sanitize takes a string that contains a HTML fragment or document and applies policy whitelist.
func Sanitize(s string) string {
return sanitizer.policy.Sanitize(s)

View File

@@ -26,6 +26,20 @@ func Test_Sanitizer(t *testing.T) {
{input: `<input type="hidden">`, expVal: ``},
{input: `<input type="checkbox">`, expVal: `<input type="checkbox">`},
{input: `<input checked disabled autofocus>`, expVal: `<input checked="" disabled="">`},
// Data URIs: safe image types should be allowed
{input: `<img src="data:image/png;base64,abc">`, expVal: `<img src="data:image/png;base64,abc">`},
{input: `<img src="data:image/jpeg;base64,abc">`, expVal: `<img src="data:image/jpeg;base64,abc">`},
{input: `<img src="data:image/gif;base64,abc">`, expVal: `<img src="data:image/gif;base64,abc">`},
{input: `<img src="data:image/webp;base64,abc">`, expVal: `<img src="data:image/webp;base64,abc">`},
// Data URIs: text/html must be stripped to prevent XSS (GHSA-xrcr-gmf5-2r8j)
{input: `<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">Click</a>`, expVal: `Click`},
{input: `<a href="data:text/html,<script>alert(1)</script>">XSS</a>`, expVal: `XSS`},
{input: `<img src="data:text/html;base64,abc">`, expVal: ``},
// Data URIs: SVG must be stripped (can contain embedded JavaScript)
{input: `<img src="data:image/svg+xml;base64,abc">`, expVal: ``},
}
for _, test := range tests {
t.Run(test.input, func(t *testing.T) {

View File

@@ -151,7 +151,7 @@ func NewAuthSourcePost(c *context.Context, f form.Authentication) {
c.Data["HasTLS"] = hasTLS
if c.HasError() {
c.Success(tmplAdminAuthNew)
c.HTML(http.StatusBadRequest, tmplAdminAuthNew)
return
}
@@ -167,7 +167,7 @@ func NewAuthSourcePost(c *context.Context, f form.Authentication) {
if err != nil {
if database.IsErrLoginSourceAlreadyExist(err) {
c.FormErr("Name")
c.RenderWithErr(c.Tr("admin.auths.login_source_exist", f.Name), tmplAdminAuthNew, f)
c.RenderWithErr(c.Tr("admin.auths.login_source_exist", f.Name), http.StatusUnprocessableEntity, tmplAdminAuthNew, f)
} else {
c.Error(err, "create login source")
}
@@ -223,7 +223,7 @@ func EditAuthSourcePost(c *context.Context, f form.Authentication) {
c.Data["HasTLS"] = source.Provider.HasTLS()
if c.HasError() {
c.Success(tmplAdminAuthEdit)
c.HTML(http.StatusBadRequest, tmplAdminAuthEdit)
return
}

View File

@@ -1,6 +1,7 @@
package admin
import (
"net/http"
"strconv"
"strings"
@@ -68,7 +69,7 @@ func NewUserPost(c *context.Context, f form.AdminCrateUser) {
c.Data["CanSendEmail"] = conf.Email.Enabled
if c.HasError() {
c.Success(tmplAdminUserNew)
c.HTML(http.StatusBadRequest, tmplAdminUserNew)
return
}
@@ -89,13 +90,13 @@ func NewUserPost(c *context.Context, f form.AdminCrateUser) {
switch {
case database.IsErrUserAlreadyExist(err):
c.Data["Err_UserName"] = true
c.RenderWithErr(c.Tr("form.username_been_taken"), tmplAdminUserNew, &f)
c.RenderWithErr(c.Tr("form.username_been_taken"), http.StatusUnprocessableEntity, tmplAdminUserNew, &f)
case database.IsErrEmailAlreadyUsed(err):
c.Data["Err_Email"] = true
c.RenderWithErr(c.Tr("form.email_been_used"), tmplAdminUserNew, &f)
c.RenderWithErr(c.Tr("form.email_been_used"), http.StatusUnprocessableEntity, tmplAdminUserNew, &f)
case database.IsErrNameNotAllowed(err):
c.Data["Err_UserName"] = true
c.RenderWithErr(c.Tr("user.form.name_not_allowed", err.(database.ErrNameNotAllowed).Value()), tmplAdminUserNew, &f)
c.RenderWithErr(c.Tr("user.form.name_not_allowed", err.(database.ErrNameNotAllowed).Value()), http.StatusBadRequest, tmplAdminUserNew, &f)
default:
c.Error(err, "create user")
}
@@ -166,7 +167,7 @@ func EditUserPost(c *context.Context, f form.AdminEditUser) {
}
if c.HasError() {
c.Success(tmplAdminUserEdit)
c.HTML(http.StatusBadRequest, tmplAdminUserEdit)
return
}
@@ -203,7 +204,7 @@ func EditUserPost(c *context.Context, f form.AdminEditUser) {
if err != nil {
if database.IsErrEmailAlreadyUsed(err) {
c.Data["Err_Email"] = true
c.RenderWithErr(c.Tr("form.email_been_used"), tmplAdminUserEdit, &f)
c.RenderWithErr(c.Tr("form.email_been_used"), http.StatusUnprocessableEntity, tmplAdminUserEdit, &f)
} else {
c.Error(err, "update user")
}

View File

@@ -137,13 +137,13 @@ func GetContents(c *context.APIContext) {
}
// The entry is a directory
dir, err := gitRepo.LsTree(entry.ID().String())
dir, err := gitRepo.LsTree(entry.ID().String(), git.LsTreeOptions{Verbatim: true})
if err != nil {
c.NotFoundOrError(gitutil.NewError(err), "get tree")
return
}
entries, err := dir.Entries()
entries, err := dir.Entries(git.LsTreeOptions{Verbatim: true})
if err != nil {
c.NotFoundOrError(gitutil.NewError(err), "list entries")
return

View File

@@ -88,6 +88,17 @@ func EditIssueComment(c *context.APIContext, form api.EditIssueCommentOption) {
return
}
issue, err := database.GetIssueByID(comment.IssueID)
if err != nil {
c.NotFoundOrError(err, "get issue by ID")
return
}
if issue.RepoID != c.Repo.Repository.ID {
c.NotFound()
return
}
if c.User.ID != comment.PosterID && !c.Repo.IsAdmin() {
c.Status(http.StatusForbidden)
return
@@ -112,6 +123,17 @@ func DeleteIssueComment(c *context.APIContext) {
return
}
issue, err := database.GetIssueByID(comment.IssueID)
if err != nil {
c.NotFoundOrError(err, "get issue by ID")
return
}
if issue.RepoID != c.Repo.Repository.ID {
c.NotFound()
return
}
if c.User.ID != comment.PosterID && !c.Repo.IsAdmin() {
c.Status(http.StatusForbidden)
return

View File

@@ -45,6 +45,11 @@ func GetDeployKey(c *context.APIContext) {
return
}
if key.RepoID != c.Repo.Repository.ID {
c.NotFound()
return
}
if err = key.GetContent(); err != nil {
c.Error(err, "get content")
return
@@ -94,7 +99,18 @@ func CreateDeployKey(c *context.APIContext, form api.CreateKeyOption) {
// https://github.com/gogs/go-gogs-client/wiki/Repositories-Deploy-Keys#remove-a-deploy-key
func DeleteDeploykey(c *context.APIContext) {
if err := database.DeleteDeployKey(c.User, c.ParamsInt64(":id")); err != nil {
key, err := database.GetDeployKeyByID(c.ParamsInt64(":id"))
if err != nil {
c.NotFoundOrError(err, "get deploy key by ID")
return
}
if key.RepoID != c.Repo.Repository.ID {
c.NotFound()
return
}
if err := database.DeleteDeployKey(c.User, key.ID); err != nil {
if database.IsErrKeyAccessDenied(err) {
c.ErrorStatus(http.StatusForbidden, errors.New("You do not have access to this key"))
} else {

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