mirror of
https://github.com/gogs/gogs.git
synced 2026-05-28 21:30:36 +00:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 878caa7378 | |||
| adea243ee8 | |||
| 44f0222a71 | |||
| 26483c41c6 | |||
| 403db931cf | |||
| cd2f94a85b | |||
| 4935e7a63b | |||
| 71dfd3c7ac | |||
| ecb04beadd | |||
| 83a48c286d | |||
| f739682e9a | |||
| d54f98f5a4 | |||
| e7d0cb646d | |||
| dd6be39208 | |||
| c93373baec | |||
| 90790b2966 | |||
| e9310ea08f | |||
| 75f99c9435 | |||
| 343007e78a | |||
| b67c13c6bb | |||
| a3c9f4acef | |||
| 4c80cbc7eb | |||
| bfec14a857 | |||
| cc8036e081 | |||
| 765c0e96db | |||
| f3e563d854 | |||
| 199cf4fd5b | |||
| 0089c4c8e5 | |||
| 6734dd46c3 | |||
| a5d3439e2d | |||
| d7571322a0 | |||
| edc83e6ab2 | |||
| 7297aee50d | |||
| 27e92f8463 | |||
| b36ba5b60e | |||
| 84e23a403e | |||
| b53d316233 | |||
| 47bff199cc | |||
| 998512edfb | |||
| 1ed882b611 | |||
| 391edc74c2 | |||
| 944bfeb57e | |||
| e8b6dea462 | |||
| 2989605fd8 | |||
| df467d8ff1 | |||
| 36d56d5525 | |||
| 5f17b670b3 | |||
| ea682c5bbc | |||
| 9001a68cdd | |||
| 295bfba729 | |||
| ac21150a53 | |||
| a000f0c7a6 | |||
| a976fd2f9c | |||
| 441c64d7bd | |||
| 5c67d47512 | |||
| 94d6e53dc2 | |||
| a1fa62b270 | |||
| 317e28b908 | |||
| 069d3535d6 | |||
| 81ee883644 | |||
| 400ae7bd28 | |||
| 630ae0b3b0 | |||
| 3c358ede6d |
@@ -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,26 +0,0 @@
|
||||
version = 1
|
||||
|
||||
exclude_patterns = ["**/mocks_test.go"]
|
||||
|
||||
[[analyzers]]
|
||||
name = "docker"
|
||||
enabled = true
|
||||
|
||||
[[analyzers]]
|
||||
name = "shell"
|
||||
enabled = true
|
||||
|
||||
[[analyzers]]
|
||||
name = "go"
|
||||
enabled = true
|
||||
|
||||
[analyzers.meta]
|
||||
import_root = "github.com/gogs/gogs"
|
||||
|
||||
[[transformers]]
|
||||
name = "gofumpt"
|
||||
enabled = true
|
||||
|
||||
[[transformers]]
|
||||
name = "gofmt"
|
||||
enabled = true
|
||||
+5
-4
@@ -1,5 +1,3 @@
|
||||
.packager
|
||||
.packager/**
|
||||
scripts
|
||||
scripts/**
|
||||
.github/
|
||||
@@ -11,5 +9,8 @@ scripts/**
|
||||
.gitignore
|
||||
Dockerfile*
|
||||
gogs
|
||||
|
||||
!Taskfile.yml
|
||||
node_modules
|
||||
**/node_modules
|
||||
public/dist
|
||||
**/*.tsbuildinfo
|
||||
**/.vite
|
||||
|
||||
@@ -29,7 +29,7 @@ In addition to the general guides with open source contributions, you would also
|
||||
|
||||
### Ask for help
|
||||
|
||||
Before opening an issue, please make sure the problem you're encountering isn't already addressed on the [Troubleshooting](https://gogs.io/docs/intro/troubleshooting.html) and [FAQs](https://gogs.io/docs/intro/faqs.html) pages.
|
||||
Before opening an issue, please make sure the problem you're encountering isn't already addressed on the [Troubleshooting](https://gogs.io/asking/troubleshooting) and [FAQs](https://gogs.io/asking/faq) pages.
|
||||
|
||||
### Create a new issue
|
||||
|
||||
@@ -65,13 +65,12 @@ Contributing to another codebase is not as simple as code changes, it is also ab
|
||||
|
||||
### Things we do not accept
|
||||
|
||||
1. Updates to locale files (`conf/locale_xx-XX.ini`) other than the `conf/locale_en-US.ini`. Please read the [guide for localizing Gogs](https://gogs.io/docs/features/i18n).
|
||||
1. Updates to locale files (`conf/locale_xx-XX.ini`) other than the `conf/locale_en-US.ini`. Please read the [guide for localizing Gogs](https://gogs.io/advancing/localization).
|
||||
1. Docker compose files.
|
||||
|
||||
### Coding guidelines
|
||||
|
||||
1. Please read the Sourcegraph's [Go style guide](https://docs.sourcegraph.com/dev/background-information/languages/go).
|
||||
1. **NO** direct modifications to `.css` files, `.css` files are all generated by `.less` files. You can regenerate `.css` files by executing `task less`.
|
||||
1. Please read the Sourcegraph's [Go style guide](https://github.com/sourcegraph/sourcegraph-public-snapshot/blob/main/doc/dev/background-information/languages/go.md).
|
||||
|
||||
## Your PR is merged!
|
||||
|
||||
|
||||
@@ -42,5 +42,5 @@ On the `main` branch:
|
||||
- [ ] Send a tweet on the [official Twitter account](https://twitter.com/GogsHQ) for the minor release.
|
||||
- [ ] Close the milestone for the minor release.
|
||||
- [ ] [Bump the hard-coded version](https://github.com/gogs/gogs/commit/a98968436cd5841cf691bb0b80c54c81470d1676) to the new develop version, e.g. `0.14.0+dev` -> `0.15.0+dev`.
|
||||
- [ ] Run `task legacy` to identify deprecated code that is aimed to be removed in current develop version.
|
||||
- [ ] Run `grep -rnw "\(LEGACY\|Deprecated\)" internal` to identify deprecated code that is aimed to be removed in current develop version.
|
||||
- [ ] **After 14 days**, publish [GitHub security advisories](https://github.com/gogs/gogs/security) for security patches included in the release.
|
||||
|
||||
@@ -13,7 +13,7 @@ _This is generated from the [patch release template](https://github.com/gogs/gog
|
||||
On the release branch:
|
||||
|
||||
- [ ] Make sure all commits are cherry-picked from the `main` branch by checking the patch milestone.
|
||||
- Run `task build` for every cherry-picked commit to make sure there is no compilation error.
|
||||
- Run `moon run gogs:build-prod --force` for every cherry-picked commit to make sure there is no compilation error.
|
||||
- [ ] [Update CHANGELOG on the `main` branch](https://github.com/gogs/gogs/commit/f1102a7a7c545ec221d2906f02fa19170d96f96d) to include entries for the current patch release.
|
||||
|
||||
## During release
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
|
||||
@@ -15,65 +15,6 @@ on:
|
||||
types: [ published ]
|
||||
|
||||
jobs:
|
||||
buildx:
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'gogs/gogs' }}
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: write
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
- name: Inspect builder
|
||||
run: |
|
||||
echo "Name: ${{ steps.buildx.outputs.name }}"
|
||||
echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}"
|
||||
echo "Status: ${{ steps.buildx.outputs.status }}"
|
||||
echo "Flags: ${{ steps.buildx.outputs.flags }}"
|
||||
echo "Platforms: ${{ steps.buildx.outputs.platforms }}"
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Login to GitHub Container registry
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push images
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: |
|
||||
gogs/gogs:latest
|
||||
ghcr.io/gogs/gogs:latest
|
||||
- name: Scan for container vulnerabilities
|
||||
uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1
|
||||
with:
|
||||
image-ref: gogs/gogs:latest
|
||||
exit-code: '1'
|
||||
- name: Send email on failure
|
||||
uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
smtp_username: ${{ secrets.SMTP_USERNAME }}
|
||||
smtp_password: ${{ secrets.SMTP_PASSWORD }}
|
||||
|
||||
buildx-next:
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'gogs/gogs' }}
|
||||
concurrency:
|
||||
@@ -85,7 +26,7 @@ jobs:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
- name: Check out code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
@@ -126,13 +67,13 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: |
|
||||
gogs/gogs:next-latest
|
||||
ghcr.io/gogs/gogs:next-latest
|
||||
registry.digitalocean.com/gogs/gogs:next-latest
|
||||
gogs/gogs:edge
|
||||
ghcr.io/gogs/gogs:edge
|
||||
registry.digitalocean.com/gogs/gogs:edge
|
||||
- name: Scan for container vulnerabilities
|
||||
uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
|
||||
with:
|
||||
image-ref: gogs/gogs:next-latest
|
||||
image-ref: gogs/gogs:edge
|
||||
exit-code: '1'
|
||||
- name: Send email on failure
|
||||
uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
|
||||
@@ -173,7 +114,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout code
|
||||
- name: Check out code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
@@ -201,7 +142,7 @@ jobs:
|
||||
tags: |
|
||||
ttl.sh/gogs/gogs-${{ steps.short-sha.outputs.sha }}:7d
|
||||
- name: Scan for container vulnerabilities
|
||||
uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
|
||||
with:
|
||||
image-ref: ttl.sh/gogs/gogs-${{ steps.short-sha.outputs.sha }}:7d
|
||||
exit-code: '1'
|
||||
@@ -212,7 +153,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout code
|
||||
- name: Check out code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
@@ -241,7 +182,7 @@ jobs:
|
||||
tags: |
|
||||
ttl.sh/gogs/gogs-next-${{ steps.short-sha.outputs.sha }}:7d
|
||||
- name: Scan for container vulnerabilities
|
||||
uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
|
||||
with:
|
||||
image-ref: ttl.sh/gogs/gogs-next-${{ steps.short-sha.outputs.sha }}:7d
|
||||
exit-code: '1'
|
||||
@@ -255,6 +196,13 @@ jobs:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
# Full history with tags is required so the next step can determine
|
||||
# whether this release is the highest stable version across the repo.
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
- name: Compute image tags
|
||||
run: |
|
||||
IMAGE_TAG=$(echo $GITHUB_REF_NAME | cut -c 2-)
|
||||
@@ -263,19 +211,34 @@ jobs:
|
||||
TAGS="gogs/gogs:$IMAGE_TAG
|
||||
ghcr.io/gogs/gogs:$IMAGE_TAG"
|
||||
|
||||
# Add minor version tag for stable releases (no prerelease suffix per semver).
|
||||
# For stable releases (no prerelease suffix per semver), add the
|
||||
# minor-version tag only if this release is the highest patch of that
|
||||
# minor line, and add `latest` only if this release is the highest
|
||||
# stable version across the repository. Back-patches on older lines
|
||||
# must not clobber moving tags.
|
||||
if [[ ! "$IMAGE_TAG" =~ - ]]; then
|
||||
STABLE_TAGS=$(git tag --list 'v*' | sed 's/^v//' | grep -v -- '-' || true)
|
||||
HIGHEST_STABLE=$(echo "$STABLE_TAGS" | sort -V | tail -n 1)
|
||||
MINOR_TAG=$(echo "$IMAGE_TAG" | cut -d. -f1,2)
|
||||
TAGS="$TAGS
|
||||
# `|| true` keeps the step running when `grep` finds no matches,
|
||||
# since bash runs with `-e -o pipefail` in GitHub Actions.
|
||||
HIGHEST_IN_MINOR=$(echo "$STABLE_TAGS" | { grep "^${MINOR_TAG}\." || true; } | sort -V | tail -n 1)
|
||||
|
||||
if [[ "$IMAGE_TAG" == "$HIGHEST_IN_MINOR" ]]; then
|
||||
TAGS="$TAGS
|
||||
gogs/gogs:$MINOR_TAG
|
||||
ghcr.io/gogs/gogs:$MINOR_TAG"
|
||||
fi
|
||||
if [[ "$IMAGE_TAG" == "$HIGHEST_STABLE" ]]; then
|
||||
TAGS="$TAGS
|
||||
gogs/gogs:latest
|
||||
ghcr.io/gogs/gogs:latest"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "TAGS<<EOF" >> $GITHUB_ENV
|
||||
echo "$TAGS" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
with:
|
||||
@@ -308,6 +271,11 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: ${{ env.TAGS }}
|
||||
- name: Scan for container vulnerabilities
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
|
||||
with:
|
||||
image-ref: gogs/gogs:${{ env.IMAGE_TAG }}
|
||||
exit-code: '1'
|
||||
- name: Send email on failure
|
||||
uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
|
||||
if: ${{ failure() }}
|
||||
@@ -324,6 +292,13 @@ jobs:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
# Full history with tags is required so the next step can determine
|
||||
# whether this release is the highest stable version across the repo.
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
- name: Compute image tags
|
||||
run: |
|
||||
IMAGE_TAG=$(echo $GITHUB_REF_NAME | cut -c 2-)
|
||||
@@ -332,19 +307,34 @@ jobs:
|
||||
TAGS="gogs/gogs:next-$IMAGE_TAG
|
||||
ghcr.io/gogs/gogs:next-$IMAGE_TAG"
|
||||
|
||||
# Add minor version tag for stable releases (no prerelease suffix per semver).
|
||||
# For stable releases (no prerelease suffix per semver), add the
|
||||
# minor-version tag only if this release is the highest patch of that
|
||||
# minor line, and add `next-latest` only if this release is the
|
||||
# highest stable version across the repository. Back-patches on older
|
||||
# lines must not clobber moving tags.
|
||||
if [[ ! "$IMAGE_TAG" =~ - ]]; then
|
||||
STABLE_TAGS=$(git tag --list 'v*' | sed 's/^v//' | grep -v -- '-' || true)
|
||||
HIGHEST_STABLE=$(echo "$STABLE_TAGS" | sort -V | tail -n 1)
|
||||
MINOR_TAG=$(echo "$IMAGE_TAG" | cut -d. -f1,2)
|
||||
TAGS="$TAGS
|
||||
# `|| true` keeps the step running when `grep` finds no matches,
|
||||
# since bash runs with `-e -o pipefail` in GitHub Actions.
|
||||
HIGHEST_IN_MINOR=$(echo "$STABLE_TAGS" | { grep "^${MINOR_TAG}\." || true; } | sort -V | tail -n 1)
|
||||
|
||||
if [[ "$IMAGE_TAG" == "$HIGHEST_IN_MINOR" ]]; then
|
||||
TAGS="$TAGS
|
||||
gogs/gogs:next-$MINOR_TAG
|
||||
ghcr.io/gogs/gogs:next-$MINOR_TAG"
|
||||
fi
|
||||
if [[ "$IMAGE_TAG" == "$HIGHEST_STABLE" ]]; then
|
||||
TAGS="$TAGS
|
||||
gogs/gogs:next-latest
|
||||
ghcr.io/gogs/gogs:next-latest"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "TAGS<<EOF" >> $GITHUB_ENV
|
||||
echo "$TAGS" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
with:
|
||||
@@ -378,6 +368,11 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: ${{ env.TAGS }}
|
||||
- name: Scan for container vulnerabilities
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
|
||||
with:
|
||||
image-ref: gogs/gogs:next-${{ env.IMAGE_TAG }}
|
||||
exit-code: '1'
|
||||
- name: Send email on failure
|
||||
uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
|
||||
if: ${{ failure() }}
|
||||
|
||||
+12
-16
@@ -29,26 +29,22 @@ jobs:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
- name: Check out code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: 1.25.x
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@b91d5d2c96a56797b48ac1e0e89220bf64044611 # v2.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
go-version: 1.26.x
|
||||
- name: Check Go module tidiness and generated files
|
||||
shell: bash
|
||||
run: |
|
||||
go mod tidy
|
||||
task generate
|
||||
go generate ./...
|
||||
STATUS=$(git status --porcelain)
|
||||
if [ ! -z "$STATUS" ]; then
|
||||
echo "Unstaged files:"
|
||||
echo $STATUS
|
||||
echo "Run 'go mod tidy' or 'task generate' commit them"
|
||||
echo "Run 'go mod tidy' or 'go generate ./...' and commit them"
|
||||
exit 1
|
||||
fi
|
||||
- name: Run golangci-lint
|
||||
@@ -61,11 +57,11 @@ jobs:
|
||||
name: Test
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.25.x ]
|
||||
go-version: [ 1.26.x ]
|
||||
platform: [ ubuntu-latest, macos-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
- name: Check out code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
@@ -89,11 +85,11 @@ jobs:
|
||||
name: Test Windows
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.25.x ]
|
||||
go-version: [ 1.26.x ]
|
||||
platform: [ windows-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
- name: Check out code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
@@ -112,7 +108,7 @@ jobs:
|
||||
name: Postgres
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.25.x ]
|
||||
go-version: [ 1.26.x ]
|
||||
platform: [ ubuntu-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
services:
|
||||
@@ -128,7 +124,7 @@ jobs:
|
||||
ports:
|
||||
- 5432:5432
|
||||
steps:
|
||||
- name: Checkout code
|
||||
- name: Check out code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
@@ -151,13 +147,13 @@ jobs:
|
||||
name: MySQL
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.25.x ]
|
||||
go-version: [ 1.26.x ]
|
||||
platform: [ ubuntu-22.04 ] # Use the lowest version possible for backwards compatibility
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Start MySQL server
|
||||
run: sudo systemctl start mysql
|
||||
- name: Checkout code
|
||||
- name: Check out code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
|
||||
@@ -23,29 +23,41 @@ permissions:
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ matrix.goos }}/${{ matrix.goarch }}${{ matrix.suffix }}
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'gogs/gogs' }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- {goos: linux, goarch: amd64}
|
||||
- {goos: linux, goarch: arm64}
|
||||
- {goos: linux, goarch: "386"}
|
||||
- {goos: darwin, goarch: amd64}
|
||||
- {goos: darwin, goarch: arm64}
|
||||
- {goos: windows, goarch: amd64}
|
||||
- {goos: windows, goarch: arm64}
|
||||
- {goos: windows, goarch: "386"}
|
||||
- {goos: windows, goarch: amd64, suffix: "_mws", tags: minwinsvc}
|
||||
- {goos: windows, goarch: arm64, suffix: "_mws", tags: minwinsvc}
|
||||
- {goos: windows, goarch: "386", suffix: "_mws", tags: minwinsvc}
|
||||
- {goos: linux, goarch: amd64, runner: ubuntu-latest}
|
||||
- {goos: linux, goarch: arm64, runner: ubuntu-latest}
|
||||
- {goos: linux, goarch: "386", runner: ubuntu-latest}
|
||||
- {goos: darwin, goarch: amd64, runner: macos-latest}
|
||||
- {goos: darwin, goarch: arm64, runner: macos-latest}
|
||||
- {goos: windows, goarch: amd64, runner: ubuntu-latest}
|
||||
- {goos: windows, goarch: arm64, runner: ubuntu-latest}
|
||||
- {goos: windows, goarch: "386", runner: ubuntu-latest}
|
||||
- {goos: windows, goarch: amd64, suffix: "_mws", tags: minwinsvc, runner: ubuntu-latest}
|
||||
- {goos: windows, goarch: arm64, suffix: "_mws", tags: minwinsvc, runner: ubuntu-latest}
|
||||
- {goos: windows, goarch: "386", suffix: "_mws", tags: minwinsvc, runner: ubuntu-latest}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
- name: Check out code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Setup Go
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: 1.25.x
|
||||
go-version: 1.26.x
|
||||
- name: Set up pnpm
|
||||
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
- name: Build web assets
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm --filter gogs-web run build
|
||||
- name: Determine version
|
||||
id: version
|
||||
run: |
|
||||
@@ -70,9 +82,9 @@ jobs:
|
||||
BINARY_NAME="gogs.exe"
|
||||
fi
|
||||
|
||||
TAGS_FLAG=""
|
||||
TAGS="prod"
|
||||
if [ -n "${{ matrix.tags }}" ]; then
|
||||
TAGS_FLAG="-tags ${{ matrix.tags }}"
|
||||
TAGS="$TAGS ${{ matrix.tags }}"
|
||||
fi
|
||||
|
||||
go build -v \
|
||||
@@ -80,8 +92,44 @@ jobs:
|
||||
-X \"gogs.io/gogs/internal/conf.BuildTime=$(date -u '+%Y-%m-%d %I:%M:%S %Z')\"
|
||||
-X \"gogs.io/gogs/internal/conf.BuildCommit=$(git rev-parse HEAD)\"
|
||||
" \
|
||||
$TAGS_FLAG \
|
||||
-tags "$TAGS" \
|
||||
-trimpath -o "$BINARY_NAME" ./cmd/gogs
|
||||
- name: Import Apple signing certificate
|
||||
if: ${{ matrix.goos == 'darwin' }}
|
||||
env:
|
||||
APPLE_DEVELOPER_ID_CERTIFICATE_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_BASE64 }}
|
||||
APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }}
|
||||
APPLE_KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
|
||||
run: |
|
||||
if [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" ] || [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" ] || [ -z "$APPLE_KEYCHAIN_PASSWORD" ]; then
|
||||
echo "Missing required Apple signing secrets." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CERTIFICATE_PATH="$RUNNER_TEMP/developer_id_application.p12"
|
||||
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
|
||||
|
||||
printf '%s' "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" | base64 -d > "$CERTIFICATE_PATH"
|
||||
security create-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
|
||||
security unlock-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
security import "$CERTIFICATE_PATH" -P "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
|
||||
security list-keychains -d user -s "$KEYCHAIN_PATH"
|
||||
security default-keychain -s "$KEYCHAIN_PATH"
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
- name: Sign macOS binary
|
||||
if: ${{ matrix.goos == 'darwin' }}
|
||||
env:
|
||||
APPLE_DEVELOPER_IDENTITY: ${{ secrets.APPLE_DEVELOPER_IDENTITY }}
|
||||
run: |
|
||||
if [ -z "$APPLE_DEVELOPER_IDENTITY" ]; then
|
||||
echo "Missing required Apple signing identity secret." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
security find-identity -v -p codesigning
|
||||
codesign --force --options runtime --timestamp --sign "$APPLE_DEVELOPER_IDENTITY" "gogs"
|
||||
codesign --verify --verbose=2 "gogs"
|
||||
- name: Prepare archive contents
|
||||
run: |
|
||||
mkdir -p dist/gogs
|
||||
@@ -90,7 +138,7 @@ jobs:
|
||||
BINARY_NAME="gogs.exe"
|
||||
fi
|
||||
cp "$BINARY_NAME" dist/gogs/
|
||||
cp LICENSE README.md README_ZH.md dist/gogs/
|
||||
cp LICENSE README.md dist/gogs/
|
||||
cp -r scripts dist/gogs/
|
||||
- name: Create archives
|
||||
working-directory: dist
|
||||
@@ -103,6 +151,28 @@ jobs:
|
||||
if [ "${{ matrix.goos }}" = "linux" ]; then
|
||||
tar -czvf "${ARCHIVE_BASE}.tar.gz" gogs
|
||||
fi
|
||||
- name: Notarize macOS archive
|
||||
if: ${{ matrix.goos == 'darwin' }}
|
||||
env:
|
||||
APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
|
||||
APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }}
|
||||
APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
|
||||
run: |
|
||||
if [ -z "$APPLE_NOTARY_ISSUER_ID" ] || [ -z "$APPLE_NOTARY_KEY_BASE64" ] || [ -z "$APPLE_NOTARY_KEY_ID" ]; then
|
||||
echo "Missing required Apple notarization secrets." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
ARCHIVE_PATH="dist/gogs_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}${{ matrix.suffix }}.zip"
|
||||
NOTARY_KEY_PATH="$RUNNER_TEMP/AuthKey_${APPLE_NOTARY_KEY_ID}.p8"
|
||||
|
||||
printf '%s' "$APPLE_NOTARY_KEY_BASE64" | base64 -d > "$NOTARY_KEY_PATH"
|
||||
xcrun notarytool submit "$ARCHIVE_PATH" \
|
||||
--key "$NOTARY_KEY_PATH" \
|
||||
--key-id "$APPLE_NOTARY_KEY_ID" \
|
||||
--issuer "$APPLE_NOTARY_ISSUER_ID" \
|
||||
--wait
|
||||
- name: Upload to release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -112,14 +182,14 @@ jobs:
|
||||
if [ "${{ github.event_name }}" != "release" ]; then
|
||||
git tag -f "$RELEASE_TAG"
|
||||
git push origin "$RELEASE_TAG" --force || true
|
||||
|
||||
RELEASE_TITLE="Release Archive Testing"
|
||||
|
||||
RELEASE_TITLE="Release archive testing"
|
||||
RELEASE_NOTES="Automated testing release for workflow development."
|
||||
if [ "$RELEASE_TAG" = "latest-commit-build" ]; then
|
||||
RELEASE_TITLE="Latest Commit Build"
|
||||
RELEASE_TITLE="Latest commit build"
|
||||
RELEASE_NOTES="Automated build from the latest commit on main branch. This release is updated automatically with every push to main."
|
||||
fi
|
||||
|
||||
|
||||
gh release view "$RELEASE_TAG" || gh release create "$RELEASE_TAG" --title "$RELEASE_TITLE" --notes "$RELEASE_NOTES" --prerelease
|
||||
fi
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
name: Web
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 'release/**'
|
||||
paths:
|
||||
- 'web/**'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- 'package.json'
|
||||
- 'conf/locale/locale_*.ini'
|
||||
- '.github/workflows/web.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'web/**'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- 'package.json'
|
||||
- 'conf/locale/locale_*.ini'
|
||||
- '.github/workflows/web.yml'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
web:
|
||||
name: Lint and build
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
MOON_COLOR: "true"
|
||||
FORCE_COLOR: "1"
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up pnpm
|
||||
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
- name: Set up moon
|
||||
uses: moonrepo/setup-toolchain@261c62cb5b0f580c7be7c8cd0f023a2e96756095 # v0.6.4
|
||||
with:
|
||||
auto-install: false
|
||||
- name: Lint
|
||||
run: moon run web:lint
|
||||
- name: Build
|
||||
run: moon run web:build
|
||||
- name: Check for uncommitted changes
|
||||
run: git diff --exit-code --stat
|
||||
+5
-2
@@ -1,6 +1,10 @@
|
||||
# Build artifacts
|
||||
# Build artifacts and cache
|
||||
.bin/
|
||||
dist/
|
||||
.moon/cache/
|
||||
node_modules/
|
||||
.agents/skills/
|
||||
.claude/skills
|
||||
|
||||
# Runtime data
|
||||
log/
|
||||
@@ -9,7 +13,6 @@ data/
|
||||
|
||||
# Configuration and application files
|
||||
.idea/
|
||||
.task/
|
||||
.envrc
|
||||
|
||||
# System junk
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
$schema: "https://moonrepo.dev/schemas/tasks.json"
|
||||
|
||||
taskOptions:
|
||||
outputStyle: "stream"
|
||||
@@ -0,0 +1,9 @@
|
||||
$schema: "https://moonrepo.dev/schemas/workspace.json"
|
||||
|
||||
projects:
|
||||
gogs: "."
|
||||
web: "web"
|
||||
|
||||
vcs:
|
||||
client: "git"
|
||||
defaultBranch: "main"
|
||||
@@ -1 +0,0 @@
|
||||
main
|
||||
@@ -1 +0,0 @@
|
||||
web: ./gogs web -p ${PORT:=3000}
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
APP_NAME="gogs"
|
||||
CLI="${APP_NAME}"
|
||||
APP_USER=$(${CLI} config:get APP_USER)
|
||||
APP_GROUP=$(${CLI} config:get APP_GROUP)
|
||||
APP_CONFIG="/etc/${APP_NAME}/conf/app.ini"
|
||||
|
||||
mkdir -p "$(dirname ${APP_CONFIG})"
|
||||
chown "${APP_USER}"."${APP_GROUP}" "$(dirname ${APP_CONFIG})"
|
||||
[ -f ${APP_CONFIG} ] || ${CLI} run cp conf/app.ini ${APP_CONFIG}
|
||||
${CLI} config:set USER="${APP_USER}"
|
||||
sed -i "s|RUN_USER = git|RUN_USER = ${APP_USER}|" ${APP_CONFIG}
|
||||
sed -i "s|RUN_MODE = dev|RUN_MODE = prod|" ${APP_CONFIG}
|
||||
|
||||
${CLI} config:set GOGS_CUSTOM=/etc/${APP_NAME}
|
||||
|
||||
# scale
|
||||
${CLI} scale web=1 || true
|
||||
|
||||
# restart the service
|
||||
service gogs restart || true
|
||||
@@ -1,35 +0,0 @@
|
||||
targets:
|
||||
debian-11: &debian
|
||||
build_dependencies:
|
||||
- libpam0g-dev
|
||||
dependencies:
|
||||
- libpam0g
|
||||
- git
|
||||
debian-12:
|
||||
<<: *debian
|
||||
debian-13:
|
||||
<<: *debian
|
||||
debian-14:
|
||||
<<: *debian
|
||||
ubuntu-18.04:
|
||||
<<: *debian
|
||||
ubuntu-20.04:
|
||||
<<: *debian
|
||||
ubuntu-22.04:
|
||||
<<: *debian
|
||||
ubuntu-24.04:
|
||||
<<: *debian
|
||||
centos-9:
|
||||
build_dependencies:
|
||||
- pam-devel
|
||||
# required for Go buildpack
|
||||
- perl-Digest-SHA
|
||||
dependencies:
|
||||
- pam
|
||||
- git
|
||||
before:
|
||||
- mv .packager/Procfile .
|
||||
after:
|
||||
- mv bin/gogs gogs
|
||||
after_install: ./.packager/hooks/postinst
|
||||
buildpack: https://github.com/heroku/heroku-buildpack-go.git#main
|
||||
@@ -9,6 +9,8 @@ This applies to all texts, including but not limited to UI, documentation, code
|
||||
|
||||
- Use sentence case. Preserve original casing for brand names.
|
||||
- End with a period for a full sentence.
|
||||
- Never use em dashes (`—`) or en dashes (`–`) in prose. Rewrite the sentence with a comma, period, colon, or parentheses instead. Exception: em/en dashes are allowed as visual separators in UI design (e.g., between a title and description, in a terminal prompt label) where they function as a graphic element rather than punctuation.
|
||||
- Do not overuse semicolons. Two short sentences are almost always clearer than one sentence joined by a semicolon. Reserve the semicolon for the rare case where the two clauses are so tightly coupled that splitting them loses meaning, never as a default em-dash replacement or a way to chain related thoughts.
|
||||
- Do not add comments that repeat what the code is doing, always prefer more descriptive names. Do add comments for intentions that aren't obvious via reading the code alone. This rule takes precedence over matching existing patterns.
|
||||
|
||||
## Coding guidelines
|
||||
@@ -16,10 +18,21 @@ This applies to all texts, including but not limited to UI, documentation, code
|
||||
- Use `github.com/cockroachdb/errors` for error handling.
|
||||
- Use `github.com/stretchr/testify` for assertions in tests. Be mindful about the choice of `require` and `assert`, the former should be used when the test cannot proceed meaningfully after a failed assertion.
|
||||
|
||||
## Localization
|
||||
|
||||
- Only edit `conf/locale/locale_en-US.ini`. The other `locale_*.ini` files are community-maintained translations. Do not add, remove, or rewrite keys in them, even when removing keys that are dead on the Go/template side.
|
||||
|
||||
## UI guidelines
|
||||
|
||||
- Design mobile-friendly. Every UI must look and work well on narrow viewports before adding desktop refinements via responsive breakpoints. Test at ~375px width before considering a UI done.
|
||||
- Meet WCAG 2.2 AA at minimum. Specifically: every interactive control has a discernible accessible name (visible label or `aria-label`); color is never the sole carrier of information (pair with text, icon, or shape); text and meaningful icons meet 4.5:1 contrast against their background (3:1 for large text and UI components); focus is always visible and never trapped; touch targets are at least 24×24 CSS px (40×40 preferred). When unsure, lean toward more contrast, larger targets, and explicit labels.
|
||||
- For work under `web/`, follow the patterns in [`web/DESIGN.md`](web/DESIGN.md) (typography, color hierarchy, surface chrome, file naming, accessibility specifics). Update that doc when a pattern is used in two places.
|
||||
- When a page needs server data to render, fetch it in the TanStack Router route's `loader` so the page only mounts after the response arrives. Do not fire that fetch from a `useEffect` inside the page component, which causes a flash of empty UI before the data lands.
|
||||
|
||||
## Build instructions
|
||||
|
||||
- Prefer `task` command over vanilla `go` command when available. Use `--force` flag when necessary.
|
||||
- Run `task lint` after every time you finish changing code, and fix all linter errors.
|
||||
- Prefer `moon run <project>:<task>` over vanilla `go` or `pnpm` commands when available (e.g. `moon run gogs:build`, `moon run web:dev`). Pass `--force` to bypass cache when necessary.
|
||||
- Run `moon run gogs:lint` after every time you finish changing Go code, and `moon run web:lint` after changing frontend code; fix all linter errors.
|
||||
|
||||
## Tool-use guidance
|
||||
|
||||
@@ -28,5 +41,6 @@ This applies to all texts, including but not limited to UI, documentation, code
|
||||
## Source code control
|
||||
|
||||
- When pushing changes to a pull request from a fork, use SSH address and do not add remote.
|
||||
- Never automatically executes commands that touches Git history even if the session does not require approvals, including but not limited to `rebase`, `commit`, `push`, `pull`, `reset`, `amend`. Exceptions are only allowed case-by-case.
|
||||
- Do not amend commits unless being explicitly asked to do so.
|
||||
- Never commit on the `main` branch directly unless being explicitly asked to do so. A single ask only grants a single commit action on the `main` branch.
|
||||
- Never amend commits unless being explicitly asked to do so.
|
||||
- When creating a git worktree, the worktree directory name must match its branch name. Do not use random or generated suffixes.
|
||||
|
||||
@@ -4,9 +4,39 @@ All notable changes to Gogs are documented in this file.
|
||||
|
||||
## 0.15.0+dev (`main`)
|
||||
|
||||
### Changed
|
||||
|
||||
- Docker builds from `main` are now published only as `gogs/gogs:edge`, using the next-generation `Dockerfile.next`. The legacy `Dockerfile` no longer produces `main` builds. The `gogs/gogs:latest` and `gogs/gogs:next-latest` tags now always point to the highest published stable release, never to a back-patch on an older line. [#8278](https://github.com/gogs/gogs/pull/8278)
|
||||
|
||||
### Fixed
|
||||
|
||||
- _Security:_ Denial of service in repository and wiki file listing pages via crafted file names. [#8116](https://github.com/gogs/gogs/pull/8116) - [GHSA-3qq3-668m-v9mj](https://github.com/gogs/gogs/security/advisories/GHSA-3qq3-668m-v9mj)
|
||||
- _Security:_ Reverse proxy authentication header was honored from any remote address, allowing user impersonation when Gogs was reachable directly. The header is now only trusted from addresses listed in `[auth] TRUSTED_PROXY_IPS`. [#8264](https://github.com/gogs/gogs/pull/8264) - [GHSA-w6j9-vw59-27wv](https://github.com/gogs/gogs/security/advisories/GHSA-w6j9-vw59-27wv)
|
||||
- _Security:_ Server-side request forgery in webhook deliveries via HTTP redirects to local network addresses. [#8263](https://github.com/gogs/gogs/pull/8263) - [GHSA-c4v7-xg93-qf8g](https://github.com/gogs/gogs/security/advisories/GHSA-c4v7-xg93-qf8g)
|
||||
- _Security:_ The "remember me" auto-login cookie was derived from database columns, so an attacker with a database dump could forge a valid cookie for any user. The auto-login cookie path has been removed entirely. Persistence is now provided by the server-issued session cookie. [#8289](https://github.com/gogs/gogs/pull/8289) - [GHSA-4pph-25p3-pw73](https://github.com/gogs/gogs/security/advisories/GHSA-4pph-25p3-pw73)
|
||||
|
||||
### Removed
|
||||
|
||||
- The `gogs cert` subcommand. [#8153](https://github.com/gogs/gogs/pull/8153)
|
||||
- The `[email] DISABLE_HELO` configuration option. HELO/EHLO is now always sent during SMTP handshake. [#8164](https://github.com/gogs/gogs/pull/8164)
|
||||
- Support for MSSQL as the database backend. Stay on 0.14 for continued usage. [#8173](https://github.com/gogs/gogs/pull/8173)
|
||||
- Support for `memcache` as the cache adapter. Stay on 0.14 for continued usage. [#8270](https://github.com/gogs/gogs/pull/8270)
|
||||
- The `/debug`, `/debug/pprof/*`, `/debug/profile/*`, and `/urlmap.json` endpoints. [#8271](https://github.com/gogs/gogs/pull/8271)
|
||||
|
||||
## 0.14.2
|
||||
|
||||
### Fixed
|
||||
|
||||
- _Security:_ Cross-repository LFS object overwrite via missing content hash verification. [#8166](https://github.com/gogs/gogs/pull/8166) - [GHSA-gmf8-978x-2fg2](https://github.com/gogs/gogs/security/advisories/GHSA-gmf8-978x-2fg2)
|
||||
- _Security:_ Stored XSS via data URI in issue comments. [#8174](https://github.com/gogs/gogs/pull/8174) - [GHSA-xrcr-gmf5-2r8j](https://github.com/gogs/gogs/security/advisories/GHSA-xrcr-gmf5-2r8j)
|
||||
- _Security:_ Release tag option injection in release deletion. [#8175](https://github.com/gogs/gogs/pull/8175) - [GHSA-v9vm-r24h-6rqm](https://github.com/gogs/gogs/security/advisories/GHSA-v9vm-r24h-6rqm)
|
||||
- _Security:_ Stored XSS in branch and wiki views through author and committer names. [#8176](https://github.com/gogs/gogs/pull/8176) - [GHSA-vgvf-m4fw-938j](https://github.com/gogs/gogs/security/advisories/GHSA-vgvf-m4fw-938j)
|
||||
- _Security:_ DOM-based XSS via issue meta selection on the issue page. [#8178](https://github.com/gogs/gogs/pull/8178) - [GHSA-vgjm-2cpf-4g7c](https://github.com/gogs/gogs/security/advisories/GHSA-vgjm-2cpf-4g7c)
|
||||
- Unable to update files via web editor and API. [#8184](https://github.com/gogs/gogs/pull/8184)
|
||||
|
||||
### Removed
|
||||
|
||||
- Support for passing API access tokens via URL query parameters (`token`, `access_token`). Use the `Authorization` header instead. [#8177](https://github.com/gogs/gogs/pull/8177) - [GHSA-x9p5-w45c-7ffc](https://github.com/gogs/gogs/security/advisories/GHSA-x9p5-w45c-7ffc)
|
||||
|
||||
## 0.14.1
|
||||
|
||||
|
||||
+17
-5
@@ -1,4 +1,13 @@
|
||||
FROM golang:alpine3.21 AS binarybuilder
|
||||
FROM --platform=$BUILDPLATFORM node:24-alpine AS webbuilder
|
||||
RUN corepack enable
|
||||
WORKDIR /src
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY web ./web
|
||||
COPY conf/locale ./conf/locale
|
||||
RUN pnpm install --frozen-lockfile
|
||||
RUN pnpm --filter gogs-web run build
|
||||
|
||||
FROM golang:1.26-alpine3.23 AS binarybuilder
|
||||
RUN apk --no-cache --no-progress add --virtual \
|
||||
build-deps \
|
||||
build-base \
|
||||
@@ -7,11 +16,13 @@ RUN apk --no-cache --no-progress add --virtual \
|
||||
|
||||
WORKDIR /gogs.io/gogs
|
||||
COPY . .
|
||||
COPY --from=webbuilder /src/public/dist ./public/dist
|
||||
|
||||
RUN ./docker/build/install-task.sh
|
||||
RUN TAGS="cert pam" task build
|
||||
RUN go build -v -trimpath -tags "pam prod" \
|
||||
-ldflags "-X 'gogs.io/gogs/internal/conf.BuildTime=$(date -u '+%Y-%m-%d %I:%M:%S %Z')' -X 'gogs.io/gogs/internal/conf.BuildCommit=$(git rev-parse HEAD)'" \
|
||||
-o .bin/gogs ./cmd/gogs
|
||||
|
||||
FROM alpine:3.21
|
||||
FROM alpine:3.23
|
||||
RUN apk --no-cache --no-progress add \
|
||||
bash \
|
||||
ca-certificates \
|
||||
@@ -23,7 +34,8 @@ RUN apk --no-cache --no-progress add \
|
||||
shadow \
|
||||
socat \
|
||||
tzdata \
|
||||
rsync
|
||||
rsync \
|
||||
"zlib>1.3.2"
|
||||
|
||||
ENV GOGS_CUSTOM=/data/gogs
|
||||
|
||||
|
||||
+16
-4
@@ -1,4 +1,13 @@
|
||||
FROM golang:alpine3.23 AS binarybuilder
|
||||
FROM --platform=$BUILDPLATFORM node:24-alpine AS webbuilder
|
||||
RUN corepack enable
|
||||
WORKDIR /src
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY web ./web
|
||||
COPY conf/locale ./conf/locale
|
||||
RUN pnpm install --frozen-lockfile
|
||||
RUN pnpm --filter gogs-web run build
|
||||
|
||||
FROM golang:1.26-alpine3.23 AS binarybuilder
|
||||
RUN apk --no-cache --no-progress add --virtual \
|
||||
build-deps \
|
||||
build-base \
|
||||
@@ -7,9 +16,11 @@ RUN apk --no-cache --no-progress add --virtual \
|
||||
|
||||
WORKDIR /gogs.io/gogs
|
||||
COPY . .
|
||||
COPY --from=webbuilder /src/public/dist ./public/dist
|
||||
|
||||
RUN ./docker/build/install-task.sh
|
||||
RUN TAGS="cert pam" task build
|
||||
RUN go build -v -trimpath -tags "pam prod" \
|
||||
-ldflags "-X 'gogs.io/gogs/internal/conf.BuildTime=$(date -u '+%Y-%m-%d %I:%M:%S %Z')' -X 'gogs.io/gogs/internal/conf.BuildCommit=$(git rev-parse HEAD)'" \
|
||||
-o .bin/gogs ./cmd/gogs
|
||||
|
||||
FROM alpine:3.23
|
||||
|
||||
@@ -26,7 +37,8 @@ RUN apk --no-cache --no-progress add \
|
||||
curl \
|
||||
git \
|
||||
linux-pam \
|
||||
openssh-keygen
|
||||
openssh-keygen \
|
||||
"zlib>1.3.2"
|
||||
|
||||
ENV GOGS_CUSTOM=/data/gogs
|
||||
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||

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