mirror of
https://github.com/gogs/gogs.git
synced 2026-03-01 01:30:57 +01:00
Compare commits
44 Commits
copilot/re
...
release/0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5dcb6c64bd | ||
|
|
b4bdb270d1 | ||
|
|
0120bf0c86 | ||
|
|
094b632182 | ||
|
|
a5c2cc0c6e | ||
|
|
41b186cbfd | ||
|
|
51cf4cbe7e | ||
|
|
5e6014c421 | ||
|
|
f5c8030c1f | ||
|
|
8c5c0125c4 | ||
|
|
3f03530042 | ||
|
|
36c26c4ccc | ||
|
|
b68e6886c6 | ||
|
|
ac7ba9c8a7 | ||
|
|
dd862ee058 | ||
|
|
f94042ce6f | ||
|
|
628216d588 | ||
|
|
7306b955a9 | ||
|
|
fc6d1e2055 | ||
|
|
3b01892d85 | ||
|
|
7b7e38c880 | ||
|
|
bb68c0a042 | ||
|
|
68271e6af0 | ||
|
|
4f5b00f8c4 | ||
|
|
5d3ffd132b | ||
|
|
ee65aa89ca | ||
|
|
a1a97de76f | ||
|
|
9963268267 | ||
|
|
49a45290ae | ||
|
|
3cc8e7aa6d | ||
|
|
9f1499f3ab | ||
|
|
77dba1b5ea | ||
|
|
f70f29fdb0 | ||
|
|
ed6109d35d | ||
|
|
54e08ba678 | ||
|
|
87c8faaf08 | ||
|
|
1b226ca48d | ||
|
|
e3bb4165dc | ||
|
|
df3d945a2c | ||
|
|
ae41bab5f2 | ||
|
|
2316b09eaf | ||
|
|
3477bbac0e | ||
|
|
bb3cab921b | ||
|
|
1cdeef2ce8 |
13
.claude/commands/ghsa.md
Normal file
13
.claude/commands/ghsa.md
Normal 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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
16
.github/workflows/digitalocean_gc.yml
vendored
16
.github/workflows/digitalocean_gc.yml
vendored
@@ -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 }}
|
||||
|
||||
130
.github/workflows/docker.yml
vendored
130
.github/workflows/docker.yml
vendored
@@ -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' }}
|
||||
|
||||
51
.github/workflows/go.yml
vendored
51
.github/workflows/go.yml
vendored
@@ -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
146
.github/workflows/release.yml
vendored
Normal 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
24
.gitignore
vendored
@@ -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
|
||||
|
||||
14
AGENTS.md
14
AGENTS.md
@@ -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.
|
||||
|
||||
28
CHANGELOG.md
28
CHANGELOG.md
@@ -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
|
||||
|
||||
|
||||
@@ -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/"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
23
Taskfile.yml
23
Taskfile.yml
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=이메일 인증 필요
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=Имэйлээр баталгаажуулахыг шаардана
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=Требовать подтверждение по электронной почте
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=Вимагає підтвердження електронною поштою
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=注册邮件确认
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||
@@ -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
23
go.mod
@@ -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
175
go.sum
@@ -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=
|
||||
|
||||
2
gogs.go
2
gogs.go
@@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
conf.App.Version = "0.14.0+dev"
|
||||
conf.App.Version = "0.14.2"
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
5
internal/conf/testdata/custom.ini
vendored
5
internal/conf/testdata/custom.ini
vendored
@@ -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
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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{})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"gorm.io/gorm/logger"
|
||||
_ "modernc.org/sqlite"
|
||||
log "unknwon.dev/clog/v2"
|
||||
|
||||
"gogs.io/gogs/internal/testutil"
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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('+')
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user