mirror of
https://github.com/go-gitea/gitea.git
synced 2025-11-03 08:02:36 +09:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cccd54999a | ||
|
|
3e7cb5b639 | ||
|
|
ec146b4200 | ||
|
|
51fa86f0fa | ||
|
|
e67b004535 | ||
|
|
a4cc867401 | ||
|
|
8e5aa8fb1e | ||
|
|
046fc8684c | ||
|
|
e3e705200a | ||
|
|
a9d5ab8f88 | ||
|
|
5546b4279c | ||
|
|
4f6d09fb68 | ||
|
|
4e5aca62ee | ||
|
|
030ed9462d | ||
|
|
60f175f7ff | ||
|
|
260816d64a | ||
|
|
a01f56a61a | ||
|
|
c01459a504 | ||
|
|
6e4e3ca012 | ||
|
|
ed0e8865f3 | ||
|
|
dc5adce70d | ||
|
|
328ce0485f | ||
|
|
39b6abf955 | ||
|
|
3eda79647b | ||
|
|
97171be1b4 | ||
|
|
9ef2a338d8 | ||
|
|
66ccae8b90 | ||
|
|
0e6489317e | ||
|
|
67dc1ff926 | ||
|
|
4ee4c06b07 | ||
|
|
3063e37802 | ||
|
|
a40e15a116 | ||
|
|
8f75f61b64 | ||
|
|
25e409e025 | ||
|
|
f8f24d83cf | ||
|
|
15e93a751c | ||
|
|
5a9b3bfa50 | ||
|
|
dd901983c0 | ||
|
|
7f962a16c9 | ||
|
|
5d81f6d73f | ||
|
|
eee4a752a5 | ||
|
|
b3516767fb | ||
|
|
8d1be2a9c5 | ||
|
|
e46f9ff534 | ||
|
|
690e810bcc | ||
|
|
35983ac0a8 | ||
|
|
4b3400bd9c |
26
.github/workflows/release-nightly.yml
vendored
26
.github/workflows/release-nightly.yml
vendored
@@ -59,6 +59,8 @@ jobs:
|
|||||||
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
||||||
nightly-docker-rootful:
|
nightly-docker-rootful:
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
@@ -85,17 +87,27 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: fetch go modules
|
- name: fetch go modules
|
||||||
run: make vendor
|
run: make vendor
|
||||||
- name: build rootful docker image
|
- name: build rootful docker image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
push: true
|
push: true
|
||||||
tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}
|
tags: |-
|
||||||
|
gitea/gitea:${{ steps.clean_name.outputs.branch }}
|
||||||
|
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}
|
||||||
nightly-docker-rootless:
|
nightly-docker-rootless:
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
@@ -122,6 +134,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: fetch go modules
|
- name: fetch go modules
|
||||||
run: make vendor
|
run: make vendor
|
||||||
- name: build rootless docker image
|
- name: build rootless docker image
|
||||||
@@ -131,4 +149,6 @@ jobs:
|
|||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
file: Dockerfile.rootless
|
file: Dockerfile.rootless
|
||||||
tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
|
tags: |-
|
||||||
|
gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
|
||||||
|
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
|
||||||
|
|||||||
28
.github/workflows/release-tag-rc.yml
vendored
28
.github/workflows/release-tag-rc.yml
vendored
@@ -69,6 +69,8 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
docker-rootful:
|
docker-rootful:
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
@@ -79,7 +81,9 @@ jobs:
|
|||||||
- uses: docker/metadata-action@v5
|
- uses: docker/metadata-action@v5
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: gitea/gitea
|
images: |-
|
||||||
|
gitea/gitea
|
||||||
|
ghcr.io/go-gitea/gitea
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=false
|
latest=false
|
||||||
# 1.2.3-rc0
|
# 1.2.3-rc0
|
||||||
@@ -90,16 +94,24 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build rootful docker image
|
- name: build rootful docker image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
docker-rootless:
|
docker-rootless:
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
@@ -110,7 +122,9 @@ jobs:
|
|||||||
- uses: docker/metadata-action@v5
|
- uses: docker/metadata-action@v5
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: gitea/gitea
|
images: |-
|
||||||
|
gitea/gitea
|
||||||
|
ghcr.io/go-gitea/gitea
|
||||||
# each tag below will have the suffix of -rootless
|
# each tag below will have the suffix of -rootless
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=false
|
latest=false
|
||||||
@@ -123,11 +137,17 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build rootless docker image
|
- name: build rootless docker image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
push: true
|
push: true
|
||||||
file: Dockerfile.rootless
|
file: Dockerfile.rootless
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
|||||||
28
.github/workflows/release-tag-version.yml
vendored
28
.github/workflows/release-tag-version.yml
vendored
@@ -14,6 +14,8 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
binary:
|
binary:
|
||||||
runs-on: namespace-profile-gitea-release-binary
|
runs-on: namespace-profile-gitea-release-binary
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
@@ -71,6 +73,8 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
docker-rootful:
|
docker-rootful:
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
@@ -81,7 +85,9 @@ jobs:
|
|||||||
- uses: docker/metadata-action@v5
|
- uses: docker/metadata-action@v5
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: gitea/gitea
|
images: |-
|
||||||
|
gitea/gitea
|
||||||
|
ghcr.io/go-gitea/gitea
|
||||||
# this will generate tags in the following format:
|
# this will generate tags in the following format:
|
||||||
# latest
|
# latest
|
||||||
# 1
|
# 1
|
||||||
@@ -96,11 +102,17 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build rootful docker image
|
- name: build rootful docker image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
@@ -116,7 +128,9 @@ jobs:
|
|||||||
- uses: docker/metadata-action@v5
|
- uses: docker/metadata-action@v5
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: gitea/gitea
|
images: |-
|
||||||
|
gitea/gitea
|
||||||
|
ghcr.io/go-gitea/gitea
|
||||||
# each tag below will have the suffix of -rootless
|
# each tag below will have the suffix of -rootless
|
||||||
flavor: |
|
flavor: |
|
||||||
suffix=-rootless,onlatest=true
|
suffix=-rootless,onlatest=true
|
||||||
@@ -134,11 +148,17 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build rootless docker image
|
- name: build rootless docker image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
push: true
|
push: true
|
||||||
file: Dockerfile.rootless
|
file: Dockerfile.rootless
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
|||||||
53
CHANGELOG.md
53
CHANGELOG.md
@@ -4,6 +4,59 @@ This changelog goes through the changes that have been made in each release
|
|||||||
without substantial changes to our git log; to see the highlights of what has
|
without substantial changes to our git log; to see the highlights of what has
|
||||||
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||||
|
|
||||||
|
## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/1.23.8) - 2025-05-11
|
||||||
|
|
||||||
|
* SECURITY
|
||||||
|
* Fix a bug when uploading file via lfs ssh command (#34408) (#34411)
|
||||||
|
* Update net package (#34228) (#34232)
|
||||||
|
* BUGFIXES
|
||||||
|
* Fix releases sidebar navigation link (#34436) #34439
|
||||||
|
* Fix bug webhook milestone is not right. (#34419) #34429
|
||||||
|
* Fix two missed null value checks on the wiki page. (#34205) (#34215)
|
||||||
|
* Swift files can be passed either as file or as form value (#34068) (#34236)
|
||||||
|
* Fix bug when API get pull changed files for deleted head repository (#34333) (#34368)
|
||||||
|
* Upgrade github v61 -> v71 to fix migrating bug (#34389)
|
||||||
|
* Fix bug when visiting comparation page (#34334) (#34364)
|
||||||
|
* Fix wrong review requests when updating the pull request (#34286) (#34304)
|
||||||
|
* Fix github migration error when using multiple tokens (#34144) (#34302)
|
||||||
|
* Explicitly not update indexes when sync database schemas (#34281) (#34295)
|
||||||
|
* Fix panic when comment is nil (#34257) (#34277)
|
||||||
|
* Fix project board links to related Pull Requests (#34213) (#34222)
|
||||||
|
* Don't assume the default wiki branch is master in the wiki API (#34244) (#34245)
|
||||||
|
* DOCUMENTATION
|
||||||
|
* Update token creation API swagger documentation (#34288) (#34296)
|
||||||
|
* MISC
|
||||||
|
* Fix CI Build (#34315)
|
||||||
|
* Add riscv64 support (#34199) (#34204)
|
||||||
|
* Bump go version in go.mod (#34160)
|
||||||
|
* remove hardcoded 'code' string in clone_panel.tmpl (#34153) (#34158)
|
||||||
|
|
||||||
|
## [1.23.7](https://github.com/go-gitea/gitea/releases/tag/1.23.7) - 2025-04-07
|
||||||
|
|
||||||
|
* Enhancements
|
||||||
|
* Add a config option to block "expensive" pages for anonymous users (#34024) (#34071)
|
||||||
|
* Also check default ssh-cert location for host (#34099) (#34100) (#34116)
|
||||||
|
* BUGFIXES
|
||||||
|
* Fix discord webhook 400 status code when description limit is exceeded (#34084) (#34124)
|
||||||
|
* Get changed files based on merge base when checking `pull_request` actions trigger (#34106) (#34120)
|
||||||
|
* Fix invalid version in RPM package path (#34112) (#34115)
|
||||||
|
* Return default avatar url when user id is zero rather than updating database (#34094) (#34095)
|
||||||
|
* Add additional ReplaceAll in pathsep to cater for different pathsep (#34061) (#34070)
|
||||||
|
* Try to fix check-attr bug (#34029) (#34033)
|
||||||
|
* Git client will follow 301 but 307 (#34005) (#34010)
|
||||||
|
* Fix block expensive for 1.23 (#34127)
|
||||||
|
* Fix markdown frontmatter rendering (#34102) (#34107)
|
||||||
|
* Add new CLI flags to set name and scopes when creating a user with access token (#34080) (#34103)
|
||||||
|
* Do not show 500 error when default branch doesn't exist (#34096) (#34097)
|
||||||
|
* Hide activity contributors, recent commits and code frequrency left tabs if there is no code permission (#34053) (#34065)
|
||||||
|
* Simplify emoji rendering (#34048) (#34049)
|
||||||
|
* Adjust the layout of the toolbar on the Issues/Projects page (#33667) (#34047)
|
||||||
|
* Pull request updates will also trigger code owners review requests (#33744) (#34045)
|
||||||
|
* Fix org repo creation being limited by user limits (#34030) (#34044)
|
||||||
|
* Fix git client accessing renamed repo (#34034) (#34043)
|
||||||
|
* Fix the issue with error message logging for the `check-attr` command on Windows OS. (#34035) (#34036)
|
||||||
|
* Polyfill WeakRef (#34025) (#34028)
|
||||||
|
|
||||||
## [1.23.6](https://github.com/go-gitea/gitea/releases/tag/v1.23.6) - 2025-03-24
|
## [1.23.6](https://github.com/go-gitea/gitea/releases/tag/v1.23.6) - 2025-03-24
|
||||||
|
|
||||||
* SECURITY
|
* SECURITY
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -109,7 +109,7 @@ endif
|
|||||||
|
|
||||||
LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
|
LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
|
||||||
|
|
||||||
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
|
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/riscv64
|
||||||
|
|
||||||
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
||||||
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
|
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
|
||||||
|
|||||||
4
assets/go-licenses.json
generated
4
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
@@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
@@ -61,6 +62,16 @@ var microcmdUserCreate = &cli.Command{
|
|||||||
Name: "access-token",
|
Name: "access-token",
|
||||||
Usage: "Generate access token for the user",
|
Usage: "Generate access token for the user",
|
||||||
},
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "access-token-name",
|
||||||
|
Usage: `Name of the generated access token`,
|
||||||
|
Value: "gitea-admin",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "access-token-scopes",
|
||||||
|
Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`,
|
||||||
|
Value: "all",
|
||||||
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "restricted",
|
Name: "restricted",
|
||||||
Usage: "Make a restricted user account",
|
Usage: "Make a restricted user account",
|
||||||
@@ -162,23 +173,39 @@ func runCreateUser(c *cli.Context) error {
|
|||||||
IsRestricted: restricted,
|
IsRestricted: restricted,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var accessTokenName string
|
||||||
|
var accessTokenScope auth_model.AccessTokenScope
|
||||||
|
if c.IsSet("access-token") {
|
||||||
|
accessTokenName = strings.TrimSpace(c.String("access-token-name"))
|
||||||
|
if accessTokenName == "" {
|
||||||
|
return errors.New("access-token-name cannot be empty")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
accessTokenScope, err = auth_model.AccessTokenScope(c.String("access-token-scopes")).Normalize()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid access token scope provided: %w", err)
|
||||||
|
}
|
||||||
|
if !accessTokenScope.HasPermissionScope() {
|
||||||
|
return errors.New("access token does not have any permission")
|
||||||
|
}
|
||||||
|
} else if c.IsSet("access-token-name") || c.IsSet("access-token-scopes") {
|
||||||
|
return errors.New("access-token-name and access-token-scopes flags are only valid when access-token flag is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// arguments should be prepared before creating the user & access token, in case there is anything wrong
|
||||||
|
|
||||||
|
// create the user
|
||||||
if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil {
|
if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil {
|
||||||
return fmt.Errorf("CreateUser: %w", err)
|
return fmt.Errorf("CreateUser: %w", err)
|
||||||
}
|
}
|
||||||
|
// create the access token
|
||||||
if c.Bool("access-token") {
|
if accessTokenScope != "" {
|
||||||
t := &auth_model.AccessToken{
|
t := &auth_model.AccessToken{Name: accessTokenName, UID: u.ID, Scope: accessTokenScope}
|
||||||
Name: "gitea-admin",
|
|
||||||
UID: u.ID,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := auth_model.NewAccessToken(ctx, t); err != nil {
|
if err := auth_model.NewAccessToken(ctx, t); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Access token was successfully created... %s\n", t.Token)
|
fmt.Printf("Access token was successfully created... %s\n", t.Token)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("New user '%s' has been successfully created!\n", username)
|
fmt.Printf("New user '%s' has been successfully created!\n", username)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,37 +8,97 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAdminUserCreate(t *testing.T) {
|
func TestAdminUserCreate(t *testing.T) {
|
||||||
app := NewMainApp(AppVersion{})
|
app := NewMainApp(AppVersion{})
|
||||||
|
|
||||||
reset := func() {
|
reset := func() {
|
||||||
assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
|
require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
|
||||||
assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
|
require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
|
||||||
|
require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{}))
|
||||||
}
|
}
|
||||||
|
t.Run("MustChangePassword", func(t *testing.T) {
|
||||||
type createCheck struct{ IsAdmin, MustChangePassword bool }
|
type check struct{ IsAdmin, MustChangePassword bool }
|
||||||
createUser := func(name, args string) createCheck {
|
createCheck := func(name, args string) check {
|
||||||
assert.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args))))
|
assert.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args))))
|
||||||
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
|
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
|
||||||
return createCheck{u.IsAdmin, u.MustChangePassword}
|
return check{u.IsAdmin, u.MustChangePassword}
|
||||||
}
|
}
|
||||||
reset()
|
reset()
|
||||||
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u", ""), "first non-admin user doesn't need to change password")
|
assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u", ""), "first non-admin user doesn't need to change password")
|
||||||
|
|
||||||
reset()
|
reset()
|
||||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u", "--admin"), "first admin user doesn't need to change password")
|
assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u", "--admin"), "first admin user doesn't need to change password")
|
||||||
|
|
||||||
reset()
|
reset()
|
||||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u", "--admin --must-change-password"))
|
assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u", "--admin --must-change-password"))
|
||||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u2", "--admin"))
|
assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u2", "--admin"))
|
||||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u3", "--admin --must-change-password=false"))
|
assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u3", "--admin --must-change-password=false"))
|
||||||
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: true}, createUser("u4", ""))
|
assert.Equal(t, check{IsAdmin: false, MustChangePassword: true}, createCheck("u4", ""))
|
||||||
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u5", "--must-change-password=false"))
|
assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u5", "--must-change-password=false"))
|
||||||
|
})
|
||||||
|
|
||||||
|
createUser := func(name, args string) error {
|
||||||
|
return app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s", name, name, args)))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("AccessToken", func(t *testing.T) {
|
||||||
|
// no generated access token
|
||||||
|
reset()
|
||||||
|
assert.NoError(t, createUser("u", "--random-password"))
|
||||||
|
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
|
|
||||||
|
// using "--access-token" only means "all" access
|
||||||
|
reset()
|
||||||
|
assert.NoError(t, createUser("u", "--random-password --access-token"))
|
||||||
|
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
|
||||||
|
assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
|
accessToken := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"})
|
||||||
|
hasScopes, err := accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, hasScopes)
|
||||||
|
|
||||||
|
// using "--access-token" with name & scopes
|
||||||
|
reset()
|
||||||
|
assert.NoError(t, createUser("u", "--random-password --access-token --access-token-name new-token-name --access-token-scopes read:issue,read:user"))
|
||||||
|
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
|
||||||
|
assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
|
accessToken = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "new-token-name"})
|
||||||
|
hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadUser)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, hasScopes)
|
||||||
|
hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, hasScopes)
|
||||||
|
|
||||||
|
// using "--access-token-name" without "--access-token"
|
||||||
|
reset()
|
||||||
|
err = createUser("u", "--random-password --access-token-name new-token-name")
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
|
assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
|
||||||
|
|
||||||
|
// using "--access-token-scopes" without "--access-token"
|
||||||
|
reset()
|
||||||
|
err = createUser("u", "--random-password --access-token-scopes read:issue")
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
|
assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
|
||||||
|
|
||||||
|
// empty permission
|
||||||
|
reset()
|
||||||
|
err = createUser("u", "--random-password --access-token --access-token-scopes public-only")
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
|
assert.ErrorContains(t, err, "access token does not have any permission")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ var microcmdUserGenerateAccessToken = &cli.Command{
|
|||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "scopes",
|
Name: "scopes",
|
||||||
Value: "",
|
Value: "all",
|
||||||
Usage: "Comma separated list of scopes to apply to access token",
|
Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: runGenerateAccessToken,
|
Action: runGenerateAccessToken,
|
||||||
@@ -43,7 +43,7 @@ var microcmdUserGenerateAccessToken = &cli.Command{
|
|||||||
|
|
||||||
func runGenerateAccessToken(c *cli.Context) error {
|
func runGenerateAccessToken(c *cli.Context) error {
|
||||||
if !c.IsSet("username") {
|
if !c.IsSet("username") {
|
||||||
return errors.New("You must provide a username to generate a token for")
|
return errors.New("you must provide a username to generate a token for")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals()
|
||||||
@@ -77,6 +77,9 @@ func runGenerateAccessToken(c *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid access token scope provided: %w", err)
|
return fmt.Errorf("invalid access token scope provided: %w", err)
|
||||||
}
|
}
|
||||||
|
if !accessTokenScope.HasPermissionScope() {
|
||||||
|
return errors.New("access token does not have any permission")
|
||||||
|
}
|
||||||
t.Scope = accessTokenScope
|
t.Scope = accessTokenScope
|
||||||
|
|
||||||
// create the token
|
// create the token
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/google/go-github/v61/github"
|
"github.com/google/go-github/v71/github"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -774,6 +774,9 @@ LEVEL = Info
|
|||||||
;ALLOW_ONLY_EXTERNAL_REGISTRATION = false
|
;ALLOW_ONLY_EXTERNAL_REGISTRATION = false
|
||||||
;;
|
;;
|
||||||
;; User must sign in to view anything.
|
;; User must sign in to view anything.
|
||||||
|
;; After 1.23.7, it could be set to "expensive" to block anonymous users accessing some pages which consume a lot of resources,
|
||||||
|
;; for example: block anonymous AI crawlers from accessing repo code pages.
|
||||||
|
;; The "expensive" mode is experimental and subject to change.
|
||||||
;REQUIRE_SIGNIN_VIEW = false
|
;REQUIRE_SIGNIN_VIEW = false
|
||||||
;;
|
;;
|
||||||
;; Mail notification
|
;; Mail notification
|
||||||
|
|||||||
@@ -22,3 +22,8 @@ manifests:
|
|||||||
architecture: arm64
|
architecture: arm64
|
||||||
os: linux
|
os: linux
|
||||||
variant: v8
|
variant: v8
|
||||||
|
-
|
||||||
|
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}nightly{{/if}}-linux-riscv64-rootless
|
||||||
|
platform:
|
||||||
|
architecture: riscv64
|
||||||
|
os: linux
|
||||||
|
|||||||
@@ -22,3 +22,8 @@ manifests:
|
|||||||
architecture: arm64
|
architecture: arm64
|
||||||
os: linux
|
os: linux
|
||||||
variant: v8
|
variant: v8
|
||||||
|
-
|
||||||
|
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}nightly{{/if}}-linux-riscv64
|
||||||
|
platform:
|
||||||
|
architecture: riscv64
|
||||||
|
os: linux
|
||||||
|
|||||||
@@ -31,6 +31,21 @@ if [ -e /data/ssh/ssh_host_ecdsa_cert ]; then
|
|||||||
SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_cert"}
|
SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_cert"}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# In case someone wants to sign the `{keyname}.pub` key by `ssh-keygen -s ca -I identity ...` to
|
||||||
|
# make use of the ssh-key certificate authority feature (see ssh-keygen CERTIFICATES section),
|
||||||
|
# the generated key file name is `{keyname}-cert.pub`
|
||||||
|
if [ -e /data/ssh/ssh_host_ed25519_key-cert.pub ]; then
|
||||||
|
SSH_ED25519_CERT=${SSH_ED25519_CERT:-"/data/ssh/ssh_host_ed25519_key-cert.pub"}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e /data/ssh/ssh_host_rsa_key-cert.pub ]; then
|
||||||
|
SSH_RSA_CERT=${SSH_RSA_CERT:-"/data/ssh/ssh_host_rsa_key-cert.pub"}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e /data/ssh/ssh_host_ecdsa_key-cert.pub ]; then
|
||||||
|
SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_key-cert.pub"}
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -d /etc/ssh ]; then
|
if [ -d /etc/ssh ]; then
|
||||||
SSH_PORT=${SSH_PORT:-"22"} \
|
SSH_PORT=${SSH_PORT:-"22"} \
|
||||||
SSH_LISTEN_PORT=${SSH_LISTEN_PORT:-"${SSH_PORT}"} \
|
SSH_LISTEN_PORT=${SSH_LISTEN_PORT:-"${SSH_PORT}"} \
|
||||||
|
|||||||
8
go.mod
8
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module code.gitea.io/gitea
|
module code.gitea.io/gitea
|
||||||
|
|
||||||
go 1.23.6
|
go 1.23.8
|
||||||
|
|
||||||
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
|
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
|
||||||
// But some CAs use negative serial number, just relax the check. related:
|
// But some CAs use negative serial number, just relax the check. related:
|
||||||
@@ -65,7 +65,7 @@ require (
|
|||||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
||||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||||
github.com/google/go-github/v61 v61.0.0
|
github.com/google/go-github/v71 v71.0.0
|
||||||
github.com/google/licenseclassifier/v2 v2.0.0
|
github.com/google/licenseclassifier/v2 v2.0.0
|
||||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db
|
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
@@ -120,7 +120,7 @@ require (
|
|||||||
github.com/yuin/goldmark-meta v1.1.0
|
github.com/yuin/goldmark-meta v1.1.0
|
||||||
golang.org/x/crypto v0.36.0
|
golang.org/x/crypto v0.36.0
|
||||||
golang.org/x/image v0.21.0
|
golang.org/x/image v0.21.0
|
||||||
golang.org/x/net v0.37.0
|
golang.org/x/net v0.38.0
|
||||||
golang.org/x/oauth2 v0.27.0
|
golang.org/x/oauth2 v0.27.0
|
||||||
golang.org/x/sync v0.12.0
|
golang.org/x/sync v0.12.0
|
||||||
golang.org/x/sys v0.31.0
|
golang.org/x/sys v0.31.0
|
||||||
@@ -325,6 +325,8 @@ replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-tra
|
|||||||
// TODO: This could be removed after https://github.com/mholt/archiver/pull/396 merged
|
// TODO: This could be removed after https://github.com/mholt/archiver/pull/396 merged
|
||||||
replace github.com/mholt/archiver/v3 => github.com/anchore/archiver/v3 v3.5.2
|
replace github.com/mholt/archiver/v3 => github.com/anchore/archiver/v3 v3.5.2
|
||||||
|
|
||||||
|
replace git.sr.ht/~mariusor/go-xsd-duration => gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078
|
||||||
|
|
||||||
exclude github.com/gofrs/uuid v3.2.0+incompatible
|
exclude github.com/gofrs/uuid v3.2.0+incompatible
|
||||||
|
|
||||||
exclude github.com/gofrs/uuid v4.0.0+incompatible
|
exclude github.com/gofrs/uuid v4.0.0+incompatible
|
||||||
|
|||||||
15
go.sum
15
go.sum
@@ -14,12 +14,12 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
|||||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg=
|
|
||||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
|
|
||||||
gitea.com/gitea/act v0.261.3 h1:BhiYpGJQKGq0XMYYICCYAN4KnsEWHyLbA6dxhZwFcV4=
|
gitea.com/gitea/act v0.261.3 h1:BhiYpGJQKGq0XMYYICCYAN4KnsEWHyLbA6dxhZwFcV4=
|
||||||
gitea.com/gitea/act v0.261.3/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
|
gitea.com/gitea/act v0.261.3/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
|
||||||
gitea.com/gitea/git-lfs-transfer v0.2.0 h1:baHaNoBSRaeq/xKayEXwiDQtlIjps4Ac/Ll4KqLMB40=
|
gitea.com/gitea/git-lfs-transfer v0.2.0 h1:baHaNoBSRaeq/xKayEXwiDQtlIjps4Ac/Ll4KqLMB40=
|
||||||
gitea.com/gitea/git-lfs-transfer v0.2.0/go.mod h1:UrXUCm3xLQkq15fu7qlXHUMlrhdlXHoi13KH2Dfiits=
|
gitea.com/gitea/git-lfs-transfer v0.2.0/go.mod h1:UrXUCm3xLQkq15fu7qlXHUMlrhdlXHoi13KH2Dfiits=
|
||||||
|
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:BAFmdZpRW7zMQZQDClaCWobRj9uL1MR3MzpCVJvc5s4=
|
||||||
|
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
|
||||||
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso=
|
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso=
|
||||||
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw=
|
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw=
|
||||||
gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g=
|
gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g=
|
||||||
@@ -410,10 +410,11 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
|||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-github/v61 v61.0.0 h1:VwQCBwhyE9JclCI+22/7mLB1PuU9eowCXKY5pNlu1go=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-github/v61 v61.0.0/go.mod h1:0WR+KmsWX75G2EbpyGsGmradjo3IiciuI4BmdVCobQY=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/go-github/v71 v71.0.0 h1:Zi16OymGKZZMm8ZliffVVJ/Q9YZreDKONCr+WUd0Z30=
|
||||||
|
github.com/google/go-github/v71 v71.0.0/go.mod h1:URZXObp2BLlMjwu0O8g4y6VBneUj2bCHgnI8FfgZ51M=
|
||||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
github.com/google/go-tpm v0.9.1 h1:0pGc4X//bAlmZzMKf8iz6IsDo1nYTbYJ6FZN/rg4zdM=
|
github.com/google/go-tpm v0.9.1 h1:0pGc4X//bAlmZzMKf8iz6IsDo1nYTbYJ6FZN/rg4zdM=
|
||||||
@@ -870,8 +871,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
|||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||||
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ func (s AccessTokenScope) Normalize() (AccessTokenScope, error) {
|
|||||||
return bitmap.toScope(), nil
|
return bitmap.toScope(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s AccessTokenScope) HasPermissionScope() bool {
|
||||||
|
return s != "" && s != AccessTokenScopePublicOnly
|
||||||
|
}
|
||||||
|
|
||||||
// PublicOnly checks if this token scope is limited to public resources
|
// PublicOnly checks if this token scope is limited to public resources
|
||||||
func (s AccessTokenScope) PublicOnly() (bool, error) {
|
func (s AccessTokenScope) PublicOnly() (bool, error) {
|
||||||
bitmap, err := s.parse()
|
bitmap, err := s.parse()
|
||||||
|
|||||||
@@ -173,6 +173,18 @@ func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, e
|
|||||||
return &branch, nil
|
return &branch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsBranchExist returns true if the branch exists in the repository.
|
||||||
|
func IsBranchExist(ctx context.Context, repoID int64, branchName string) (bool, error) {
|
||||||
|
var branch Branch
|
||||||
|
has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).And("name=?", branchName).Get(&branch)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
} else if !has {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return !branch.IsDeleted, nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetBranches(ctx context.Context, repoID int64, branchNames []string, includeDeleted bool) ([]*Branch, error) {
|
func GetBranches(ctx context.Context, repoID int64, branchNames []string, includeDeleted bool) ([]*Branch, error) {
|
||||||
branches := make([]*Branch, 0, len(branchNames))
|
branches := make([]*Branch, 0, len(branchNames))
|
||||||
|
|
||||||
|
|||||||
@@ -663,7 +663,7 @@ func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_mo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if review != nil {
|
if review != nil {
|
||||||
// skip it when reviewer hase been request to review
|
// skip it when reviewer has been request to review
|
||||||
if review.Type == ReviewTypeRequest {
|
if review.Type == ReviewTypeRequest {
|
||||||
return nil, committer.Commit() // still commit the transaction, or committer.Close() will rollback it, even if it's a reused transaction.
|
return nil, committer.Commit() // still commit the transaction, or committer.Close() will rollback it, even if it's a reused transaction.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,5 +14,9 @@ func AddContentVersionToIssueAndComment(x *xorm.Engine) error {
|
|||||||
ContentVersion int `xorm:"NOT NULL DEFAULT 0"`
|
ContentVersion int `xorm:"NOT NULL DEFAULT 0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
return x.Sync(new(Comment), new(Issue))
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
|
IgnoreConstrains: true,
|
||||||
|
IgnoreIndices: true,
|
||||||
|
}, new(Comment), new(Issue))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,9 @@ func AddForcePushBranchProtection(x *xorm.Engine) error {
|
|||||||
ForcePushAllowlistTeamIDs []int64 `xorm:"JSON TEXT"`
|
ForcePushAllowlistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||||
ForcePushAllowlistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
|
ForcePushAllowlistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
}
|
}
|
||||||
return x.Sync(new(ProtectedBranch))
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
|
IgnoreConstrains: true,
|
||||||
|
IgnoreIndices: true,
|
||||||
|
}, new(ProtectedBranch))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,5 +10,9 @@ func AddSkipSecondaryAuthColumnToOAuth2ApplicationTable(x *xorm.Engine) error {
|
|||||||
type oauth2Application struct {
|
type oauth2Application struct {
|
||||||
SkipSecondaryAuthorization bool `xorm:"NOT NULL DEFAULT FALSE"`
|
SkipSecondaryAuthorization bool `xorm:"NOT NULL DEFAULT FALSE"`
|
||||||
}
|
}
|
||||||
return x.Sync(new(oauth2Application))
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
|
IgnoreConstrains: true,
|
||||||
|
IgnoreIndices: true,
|
||||||
|
}, new(oauth2Application))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,5 +19,9 @@ func AddCommentMetaDataColumn(x *xorm.Engine) error {
|
|||||||
CommentMetaData *CommentMetaData `xorm:"JSON TEXT"` // put all non-index metadata in a single field
|
CommentMetaData *CommentMetaData `xorm:"JSON TEXT"` // put all non-index metadata in a single field
|
||||||
}
|
}
|
||||||
|
|
||||||
return x.Sync(new(Comment))
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
|
IgnoreConstrains: true,
|
||||||
|
IgnoreIndices: true,
|
||||||
|
}, new(Comment))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,5 +9,9 @@ func AddBlockAdminMergeOverrideBranchProtection(x *xorm.Engine) error {
|
|||||||
type ProtectedBranch struct {
|
type ProtectedBranch struct {
|
||||||
BlockAdminMergeOverride bool `xorm:"NOT NULL DEFAULT false"`
|
BlockAdminMergeOverride bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
}
|
}
|
||||||
return x.Sync(new(ProtectedBranch))
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
|
IgnoreConstrains: true,
|
||||||
|
IgnoreIndices: true,
|
||||||
|
}, new(ProtectedBranch))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,5 +12,9 @@ func AddPriorityToProtectedBranch(x *xorm.Engine) error {
|
|||||||
Priority int64 `xorm:"NOT NULL DEFAULT 0"`
|
Priority int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
return x.Sync(new(ProtectedBranch))
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
|
IgnoreConstrains: true,
|
||||||
|
IgnoreIndices: true,
|
||||||
|
}, new(ProtectedBranch))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ func AddTimeEstimateColumnToIssueTable(x *xorm.Engine) error {
|
|||||||
type Issue struct {
|
type Issue struct {
|
||||||
TimeEstimate int64 `xorm:"NOT NULL DEFAULT 0"`
|
TimeEstimate int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||||
}
|
}
|
||||||
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
return x.Sync(new(Issue))
|
IgnoreConstrains: true,
|
||||||
|
IgnoreIndices: true,
|
||||||
|
}, new(Issue))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -279,9 +279,7 @@ func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
|
|||||||
default:
|
default:
|
||||||
e.Desc("package_version.created_unix")
|
e.Desc("package_version.created_unix")
|
||||||
}
|
}
|
||||||
|
e.Desc("package_version.id") // Sort by id for stable order with duplicates in the other field
|
||||||
// Sort by id for stable order with duplicates in the other field
|
|
||||||
e.Asc("package_version.id")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchVersions gets all versions of packages matching the search options
|
// SearchVersions gets all versions of packages matching the search options
|
||||||
|
|||||||
@@ -61,7 +61,9 @@ func GenerateRandomAvatar(ctx context.Context, u *User) error {
|
|||||||
|
|
||||||
// AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size
|
// AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size
|
||||||
func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
|
func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
|
||||||
if u.IsGhost() || u.IsGiteaActions() {
|
// ghost user was deleted, Gitea actions is a bot user, 0 means the user should be a virtual user
|
||||||
|
// which comes from git configure information
|
||||||
|
if u.IsGhost() || u.IsGiteaActions() || u.ID <= 0 {
|
||||||
return avatars.DefaultAvatarLink()
|
return avatars.DefaultAvatarLink()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -463,7 +463,7 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
|
|||||||
matchTimes++
|
matchTimes++
|
||||||
}
|
}
|
||||||
case "paths":
|
case "paths":
|
||||||
filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.Base.Ref)
|
filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.MergeBase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err)
|
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err)
|
||||||
} else {
|
} else {
|
||||||
@@ -476,7 +476,7 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "paths-ignore":
|
case "paths-ignore":
|
||||||
filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.Base.Ref)
|
filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.MergeBase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err)
|
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -350,9 +350,10 @@ func (c *Command) Run(opts *RunOpts) error {
|
|||||||
// We need to check if the context is canceled by the program on Windows.
|
// We need to check if the context is canceled by the program on Windows.
|
||||||
// This is because Windows does not have signal checking when terminating the process.
|
// This is because Windows does not have signal checking when terminating the process.
|
||||||
// It always returns exit code 1, unlike Linux, which has many exit codes for signals.
|
// It always returns exit code 1, unlike Linux, which has many exit codes for signals.
|
||||||
|
// `err.Error()` returns "exit status 1" when using the `git check-attr` command after the context is canceled.
|
||||||
if runtime.GOOS == "windows" &&
|
if runtime.GOOS == "windows" &&
|
||||||
err != nil &&
|
err != nil &&
|
||||||
err.Error() == "" &&
|
(err.Error() == "" || err.Error() == "exit status 1") &&
|
||||||
cmd.ProcessState.ExitCode() == 1 &&
|
cmd.ProcessState.ExitCode() == 1 &&
|
||||||
ctx.Err() == context.Canceled {
|
ctx.Err() == context.Canceled {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ func (wr *nulSeparatedAttributeWriter) Write(p []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
wr.tmp = append(wr.tmp, p...)
|
wr.tmp = append(wr.tmp, p...)
|
||||||
return len(p), nil
|
return l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wr *nulSeparatedAttributeWriter) ReadAttribute() <-chan attributeTriple {
|
func (wr *nulSeparatedAttributeWriter) ReadAttribute() <-chan attributeTriple {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package markdown
|
package markdown
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"html/template"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/yuin/goldmark/ast"
|
"github.com/yuin/goldmark/ast"
|
||||||
@@ -29,9 +30,7 @@ func (n *Details) Kind() ast.NodeKind {
|
|||||||
|
|
||||||
// NewDetails returns a new Paragraph node.
|
// NewDetails returns a new Paragraph node.
|
||||||
func NewDetails() *Details {
|
func NewDetails() *Details {
|
||||||
return &Details{
|
return &Details{}
|
||||||
BaseBlock: ast.BaseBlock{},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Summary is a block that contains the summary of details block
|
// Summary is a block that contains the summary of details block
|
||||||
@@ -54,9 +53,7 @@ func (n *Summary) Kind() ast.NodeKind {
|
|||||||
|
|
||||||
// NewSummary returns a new Summary node.
|
// NewSummary returns a new Summary node.
|
||||||
func NewSummary() *Summary {
|
func NewSummary() *Summary {
|
||||||
return &Summary{
|
return &Summary{}
|
||||||
BaseBlock: ast.BaseBlock{},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TaskCheckBoxListItem is a block that represents a list item of a markdown block with a checkbox
|
// TaskCheckBoxListItem is a block that represents a list item of a markdown block with a checkbox
|
||||||
@@ -95,29 +92,6 @@ type Icon struct {
|
|||||||
Name []byte
|
Name []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dump implements Node.Dump .
|
|
||||||
func (n *Icon) Dump(source []byte, level int) {
|
|
||||||
m := map[string]string{}
|
|
||||||
m["Name"] = string(n.Name)
|
|
||||||
ast.DumpHelper(n, source, level, m, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// KindIcon is the NodeKind for Icon
|
|
||||||
var KindIcon = ast.NewNodeKind("Icon")
|
|
||||||
|
|
||||||
// Kind implements Node.Kind.
|
|
||||||
func (n *Icon) Kind() ast.NodeKind {
|
|
||||||
return KindIcon
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIcon returns a new Paragraph node.
|
|
||||||
func NewIcon(name string) *Icon {
|
|
||||||
return &Icon{
|
|
||||||
BaseInline: ast.BaseInline{},
|
|
||||||
Name: []byte(name),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ColorPreview is an inline for a color preview
|
// ColorPreview is an inline for a color preview
|
||||||
type ColorPreview struct {
|
type ColorPreview struct {
|
||||||
ast.BaseInline
|
ast.BaseInline
|
||||||
@@ -175,3 +149,24 @@ func NewAttention(attentionType string) *Attention {
|
|||||||
AttentionType: attentionType,
|
AttentionType: attentionType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var KindRawHTML = ast.NewNodeKind("RawHTML")
|
||||||
|
|
||||||
|
type RawHTML struct {
|
||||||
|
ast.BaseBlock
|
||||||
|
rawHTML template.HTML
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *RawHTML) Dump(source []byte, level int) {
|
||||||
|
m := map[string]string{}
|
||||||
|
m["RawHTML"] = string(n.rawHTML)
|
||||||
|
ast.DumpHelper(n, source, level, m, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *RawHTML) Kind() ast.NodeKind {
|
||||||
|
return KindRawHTML
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRawHTML(rawHTML template.HTML) *RawHTML {
|
||||||
|
return &RawHTML{rawHTML: rawHTML}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,25 +4,24 @@
|
|||||||
package markdown
|
package markdown
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/htmlutil"
|
||||||
|
"code.gitea.io/gitea/modules/svg"
|
||||||
|
|
||||||
"github.com/yuin/goldmark/ast"
|
"github.com/yuin/goldmark/ast"
|
||||||
east "github.com/yuin/goldmark/extension/ast"
|
east "github.com/yuin/goldmark/extension/ast"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func nodeToTable(meta *yaml.Node) ast.Node {
|
func nodeToTable(meta *yaml.Node) ast.Node {
|
||||||
for {
|
for meta != nil && meta.Kind == yaml.DocumentNode {
|
||||||
|
meta = meta.Content[0]
|
||||||
|
}
|
||||||
if meta == nil {
|
if meta == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
switch meta.Kind {
|
switch meta.Kind {
|
||||||
case yaml.DocumentNode:
|
|
||||||
meta = meta.Content[0]
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
switch meta.Kind {
|
|
||||||
case yaml.MappingNode:
|
case yaml.MappingNode:
|
||||||
return mappingNodeToTable(meta)
|
return mappingNodeToTable(meta)
|
||||||
case yaml.SequenceNode:
|
case yaml.SequenceNode:
|
||||||
@@ -72,12 +71,28 @@ func sequenceNodeToTable(meta *yaml.Node) ast.Node {
|
|||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeToDetails(meta *yaml.Node, icon string) ast.Node {
|
func nodeToDetails(g *ASTTransformer, meta *yaml.Node) ast.Node {
|
||||||
|
for meta != nil && meta.Kind == yaml.DocumentNode {
|
||||||
|
meta = meta.Content[0]
|
||||||
|
}
|
||||||
|
if meta == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if meta.Kind != yaml.MappingNode {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var keys []string
|
||||||
|
for i := 0; i < len(meta.Content); i += 2 {
|
||||||
|
if meta.Content[i].Kind == yaml.ScalarNode {
|
||||||
|
keys = append(keys, meta.Content[i].Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
details := NewDetails()
|
details := NewDetails()
|
||||||
|
details.SetAttributeString(g.renderInternal.SafeAttr("class"), g.renderInternal.SafeValue("frontmatter-content"))
|
||||||
summary := NewSummary()
|
summary := NewSummary()
|
||||||
summary.AppendChild(summary, NewIcon(icon))
|
summaryInnerHTML := htmlutil.HTMLFormat("%s %s", svg.RenderHTML("octicon-table", 12), strings.Join(keys, ", "))
|
||||||
|
summary.AppendChild(summary, NewRawHTML(summaryInnerHTML))
|
||||||
details.AppendChild(details, summary)
|
details.AppendChild(details, summary)
|
||||||
details.AppendChild(details, nodeToTable(meta))
|
details.AppendChild(details, nodeToTable(meta))
|
||||||
|
|
||||||
return details
|
return details
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,6 @@ package markdown
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
@@ -51,7 +48,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
|
|||||||
|
|
||||||
tocList := make([]Header, 0, 20)
|
tocList := make([]Header, 0, 20)
|
||||||
if rc.yamlNode != nil {
|
if rc.yamlNode != nil {
|
||||||
metaNode := rc.toMetaNode()
|
metaNode := rc.toMetaNode(g)
|
||||||
if metaNode != nil {
|
if metaNode != nil {
|
||||||
node.InsertBefore(node, firstChild, metaNode)
|
node.InsertBefore(node, firstChild, metaNode)
|
||||||
}
|
}
|
||||||
@@ -111,11 +108,6 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// it is copied from old code, which is quite doubtful whether it is correct
|
|
||||||
var reValidIconName = sync.OnceValue(func() *regexp.Regexp {
|
|
||||||
return regexp.MustCompile(`^[-\w]+$`) // old: regexp.MustCompile("^[a-z ]+$")
|
|
||||||
})
|
|
||||||
|
|
||||||
// NewHTMLRenderer creates a HTMLRenderer to render in the gitea form.
|
// NewHTMLRenderer creates a HTMLRenderer to render in the gitea form.
|
||||||
func NewHTMLRenderer(renderInternal *internal.RenderInternal, opts ...html.Option) renderer.NodeRenderer {
|
func NewHTMLRenderer(renderInternal *internal.RenderInternal, opts ...html.Option) renderer.NodeRenderer {
|
||||||
r := &HTMLRenderer{
|
r := &HTMLRenderer{
|
||||||
@@ -140,11 +132,11 @@ func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
|||||||
reg.Register(ast.KindDocument, r.renderDocument)
|
reg.Register(ast.KindDocument, r.renderDocument)
|
||||||
reg.Register(KindDetails, r.renderDetails)
|
reg.Register(KindDetails, r.renderDetails)
|
||||||
reg.Register(KindSummary, r.renderSummary)
|
reg.Register(KindSummary, r.renderSummary)
|
||||||
reg.Register(KindIcon, r.renderIcon)
|
|
||||||
reg.Register(ast.KindCodeSpan, r.renderCodeSpan)
|
reg.Register(ast.KindCodeSpan, r.renderCodeSpan)
|
||||||
reg.Register(KindAttention, r.renderAttention)
|
reg.Register(KindAttention, r.renderAttention)
|
||||||
reg.Register(KindTaskCheckBoxListItem, r.renderTaskCheckBoxListItem)
|
reg.Register(KindTaskCheckBoxListItem, r.renderTaskCheckBoxListItem)
|
||||||
reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox)
|
reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox)
|
||||||
|
reg.Register(KindRawHTML, r.renderRawHTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
@@ -206,30 +198,14 @@ func (r *HTMLRenderer) renderSummary(w util.BufWriter, source []byte, node ast.N
|
|||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *HTMLRenderer) renderIcon(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *HTMLRenderer) renderRawHTML(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
if !entering {
|
if !entering {
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
n := node.(*RawHTML)
|
||||||
n := node.(*Icon)
|
_, err := w.WriteString(string(r.renderInternal.ProtectSafeAttrs(n.rawHTML)))
|
||||||
|
|
||||||
name := strings.TrimSpace(strings.ToLower(string(n.Name)))
|
|
||||||
|
|
||||||
if len(name) == 0 {
|
|
||||||
// skip this
|
|
||||||
return ast.WalkContinue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reValidIconName().MatchString(name) {
|
|
||||||
// skip this
|
|
||||||
return ast.WalkContinue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: the "icon xxx" is from Fomantic UI, it's really questionable whether it still works correctly
|
|
||||||
err := r.renderInternal.FormatWithSafeAttrs(w, `<i class="icon %s"></i>`, name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ast.WalkStop, err
|
return ast.WalkStop, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,11 +184,7 @@ func render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
|
|||||||
// Preserve original length.
|
// Preserve original length.
|
||||||
bufWithMetadataLength := len(buf)
|
bufWithMetadataLength := len(buf)
|
||||||
|
|
||||||
rc := &RenderConfig{
|
rc := &RenderConfig{Meta: markup.RenderMetaAsDetails}
|
||||||
Meta: markup.RenderMetaAsDetails,
|
|
||||||
Icon: "table",
|
|
||||||
Lang: "",
|
|
||||||
}
|
|
||||||
buf, _ = ExtractMetadataBytes(buf, rc)
|
buf, _ = ExtractMetadataBytes(buf, rc)
|
||||||
|
|
||||||
metaLength := bufWithMetadataLength - len(buf)
|
metaLength := bufWithMetadataLength - len(buf)
|
||||||
|
|||||||
@@ -383,18 +383,74 @@ func TestColorPreview(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTaskList(t *testing.T) {
|
func TestMarkdownFrontmatter(t *testing.T) {
|
||||||
testcases := []struct {
|
testcases := []struct {
|
||||||
testcase string
|
name string
|
||||||
|
input string
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
"MapInFrontmatter",
|
||||||
|
`---
|
||||||
|
key1: val1
|
||||||
|
key2: val2
|
||||||
|
---
|
||||||
|
test
|
||||||
|
`,
|
||||||
|
`<details class="frontmatter-content"><summary><span>octicon-table(12/)</span> key1, key2</summary><table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>key1</th>
|
||||||
|
<th>key2</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>val1</td>
|
||||||
|
<td>val2</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</details><p>test</p>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"ListInFrontmatter",
|
||||||
|
`---
|
||||||
|
- item1
|
||||||
|
- item2
|
||||||
|
---
|
||||||
|
test
|
||||||
|
`,
|
||||||
|
`- item1
|
||||||
|
- item2
|
||||||
|
|
||||||
|
<p>test</p>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"StringInFrontmatter",
|
||||||
|
`---
|
||||||
|
anything
|
||||||
|
---
|
||||||
|
test
|
||||||
|
`,
|
||||||
|
`anything
|
||||||
|
|
||||||
|
<p>test</p>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
// data-source-position should take into account YAML frontmatter.
|
// data-source-position should take into account YAML frontmatter.
|
||||||
|
"ListAfterFrontmatter",
|
||||||
`---
|
`---
|
||||||
foo: bar
|
foo: bar
|
||||||
---
|
---
|
||||||
- [ ] task 1`,
|
- [ ] task 1`,
|
||||||
`<details><summary><i class="icon table"></i></summary><table>
|
`<details class="frontmatter-content"><summary><span>octicon-table(12/)</span> foo</summary><table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>foo</th>
|
<th>foo</th>
|
||||||
@@ -414,9 +470,9 @@ foo: bar
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testcases {
|
for _, test := range testcases {
|
||||||
res, err := markdown.RenderString(markup.NewTestRenderContext(), test.testcase)
|
res, err := markdown.RenderString(markup.NewTestRenderContext(), test.input)
|
||||||
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
|
assert.NoError(t, err, "Unexpected error in testcase: %q", test.name)
|
||||||
assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
|
assert.Equal(t, test.expected, string(res), "Unexpected result in testcase %q", test.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
// RenderConfig represents rendering configuration for this file
|
// RenderConfig represents rendering configuration for this file
|
||||||
type RenderConfig struct {
|
type RenderConfig struct {
|
||||||
Meta markup.RenderMetaMode
|
Meta markup.RenderMetaMode
|
||||||
Icon string
|
|
||||||
TOC string // "false": hide, "side"/empty: in sidebar, "main"/"true": in main view
|
TOC string // "false": hide, "side"/empty: in sidebar, "main"/"true": in main view
|
||||||
Lang string
|
Lang string
|
||||||
yamlNode *yaml.Node
|
yamlNode *yaml.Node
|
||||||
@@ -74,7 +73,7 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
|
|
||||||
type yamlRenderConfig struct {
|
type yamlRenderConfig struct {
|
||||||
Meta *string `yaml:"meta"`
|
Meta *string `yaml:"meta"`
|
||||||
Icon *string `yaml:"details_icon"`
|
Icon *string `yaml:"details_icon"` // deprecated, because there is no font icon, so no custom icon
|
||||||
TOC *string `yaml:"include_toc"`
|
TOC *string `yaml:"include_toc"`
|
||||||
Lang *string `yaml:"lang"`
|
Lang *string `yaml:"lang"`
|
||||||
}
|
}
|
||||||
@@ -96,10 +95,6 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
rc.Meta = renderMetaModeFromString(*cfg.Gitea.Meta)
|
rc.Meta = renderMetaModeFromString(*cfg.Gitea.Meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Gitea.Icon != nil {
|
|
||||||
rc.Icon = strings.TrimSpace(strings.ToLower(*cfg.Gitea.Icon))
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Gitea.Lang != nil && *cfg.Gitea.Lang != "" {
|
if cfg.Gitea.Lang != nil && *cfg.Gitea.Lang != "" {
|
||||||
rc.Lang = *cfg.Gitea.Lang
|
rc.Lang = *cfg.Gitea.Lang
|
||||||
}
|
}
|
||||||
@@ -111,7 +106,7 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RenderConfig) toMetaNode() ast.Node {
|
func (rc *RenderConfig) toMetaNode(g *ASTTransformer) ast.Node {
|
||||||
if rc.yamlNode == nil {
|
if rc.yamlNode == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -119,7 +114,7 @@ func (rc *RenderConfig) toMetaNode() ast.Node {
|
|||||||
case markup.RenderMetaAsTable:
|
case markup.RenderMetaAsTable:
|
||||||
return nodeToTable(rc.yamlNode)
|
return nodeToTable(rc.yamlNode)
|
||||||
case markup.RenderMetaAsDetails:
|
case markup.RenderMetaAsDetails:
|
||||||
return nodeToDetails(rc.yamlNode, rc.Icon)
|
return nodeToDetails(g, rc.yamlNode)
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,42 +20,36 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"empty", &RenderConfig{
|
"empty", &RenderConfig{
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
Lang: "",
|
Lang: "",
|
||||||
}, "",
|
}, "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"lang", &RenderConfig{
|
"lang", &RenderConfig{
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
Lang: "test",
|
Lang: "test",
|
||||||
}, "lang: test",
|
}, "lang: test",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"metatable", &RenderConfig{
|
"metatable", &RenderConfig{
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
Lang: "",
|
Lang: "",
|
||||||
}, "gitea: table",
|
}, "gitea: table",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"metanone", &RenderConfig{
|
"metanone", &RenderConfig{
|
||||||
Meta: "none",
|
Meta: "none",
|
||||||
Icon: "table",
|
|
||||||
Lang: "",
|
Lang: "",
|
||||||
}, "gitea: none",
|
}, "gitea: none",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"metadetails", &RenderConfig{
|
"metadetails", &RenderConfig{
|
||||||
Meta: "details",
|
Meta: "details",
|
||||||
Icon: "table",
|
|
||||||
Lang: "",
|
Lang: "",
|
||||||
}, "gitea: details",
|
}, "gitea: details",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"metawrong", &RenderConfig{
|
"metawrong", &RenderConfig{
|
||||||
Meta: "details",
|
Meta: "details",
|
||||||
Icon: "table",
|
|
||||||
Lang: "",
|
Lang: "",
|
||||||
}, "gitea: wrong",
|
}, "gitea: wrong",
|
||||||
},
|
},
|
||||||
@@ -62,7 +57,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||||||
"toc", &RenderConfig{
|
"toc", &RenderConfig{
|
||||||
TOC: "true",
|
TOC: "true",
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
Lang: "",
|
Lang: "",
|
||||||
}, "include_toc: true",
|
}, "include_toc: true",
|
||||||
},
|
},
|
||||||
@@ -70,14 +64,12 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||||||
"tocfalse", &RenderConfig{
|
"tocfalse", &RenderConfig{
|
||||||
TOC: "false",
|
TOC: "false",
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
Lang: "",
|
Lang: "",
|
||||||
}, "include_toc: false",
|
}, "include_toc: false",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"toclang", &RenderConfig{
|
"toclang", &RenderConfig{
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
TOC: "true",
|
TOC: "true",
|
||||||
Lang: "testlang",
|
Lang: "testlang",
|
||||||
}, `
|
}, `
|
||||||
@@ -88,7 +80,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"complexlang", &RenderConfig{
|
"complexlang", &RenderConfig{
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
Lang: "testlang",
|
Lang: "testlang",
|
||||||
}, `
|
}, `
|
||||||
gitea:
|
gitea:
|
||||||
@@ -98,7 +89,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"complexlang2", &RenderConfig{
|
"complexlang2", &RenderConfig{
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
Lang: "testlang",
|
Lang: "testlang",
|
||||||
}, `
|
}, `
|
||||||
lang: notright
|
lang: notright
|
||||||
@@ -109,7 +99,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"complexlang", &RenderConfig{
|
"complexlang", &RenderConfig{
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
Lang: "testlang",
|
Lang: "testlang",
|
||||||
}, `
|
}, `
|
||||||
gitea:
|
gitea:
|
||||||
@@ -121,7 +110,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||||||
Lang: "two",
|
Lang: "two",
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
TOC: "true",
|
TOC: "true",
|
||||||
Icon: "smiley",
|
|
||||||
}, `
|
}, `
|
||||||
lang: one
|
lang: one
|
||||||
include_toc: true
|
include_toc: true
|
||||||
@@ -137,7 +125,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got := &RenderConfig{
|
got := &RenderConfig{
|
||||||
Meta: "table",
|
Meta: "table",
|
||||||
Icon: "table",
|
|
||||||
Lang: "",
|
Lang: "",
|
||||||
}
|
}
|
||||||
if err := yaml.Unmarshal([]byte(strings.ReplaceAll(tt.args, "\t", " ")), got); err != nil {
|
if err := yaml.Unmarshal([]byte(strings.ReplaceAll(tt.args, "\t", " ")), got); err != nil {
|
||||||
@@ -145,18 +132,9 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if got.Meta != tt.expected.Meta {
|
assert.Equal(t, tt.expected.Meta, got.Meta)
|
||||||
t.Errorf("Meta Expected %s Got %s", tt.expected.Meta, got.Meta)
|
assert.Equal(t, tt.expected.Lang, got.Lang)
|
||||||
}
|
assert.Equal(t, tt.expected.TOC, got.TOC)
|
||||||
if got.Icon != tt.expected.Icon {
|
|
||||||
t.Errorf("Icon Expected %s Got %s", tt.expected.Icon, got.Icon)
|
|
||||||
}
|
|
||||||
if got.Lang != tt.expected.Lang {
|
|
||||||
t.Errorf("Lang Expected %s Got %s", tt.expected.Lang, got.Lang)
|
|
||||||
}
|
|
||||||
if got.TOC != tt.expected.TOC {
|
|
||||||
t.Errorf("TOC Expected %q Got %q", tt.expected.TOC, got.TOC)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ type ConfigKey interface {
|
|||||||
In(defaultVal string, candidates []string) string
|
In(defaultVal string, candidates []string) string
|
||||||
String() string
|
String() string
|
||||||
Strings(delim string) []string
|
Strings(delim string) []string
|
||||||
|
Bool() (bool, error)
|
||||||
|
|
||||||
MustString(defaultVal string) string
|
MustString(defaultVal string) string
|
||||||
MustBool(defaultVal ...bool) bool
|
MustBool(defaultVal ...bool) bool
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ var Service = struct {
|
|||||||
ShowRegistrationButton bool
|
ShowRegistrationButton bool
|
||||||
EnablePasswordSignInForm bool
|
EnablePasswordSignInForm bool
|
||||||
ShowMilestonesDashboardPage bool
|
ShowMilestonesDashboardPage bool
|
||||||
RequireSignInView bool
|
RequireSignInViewStrict bool
|
||||||
|
BlockAnonymousAccessExpensive bool
|
||||||
EnableNotifyMail bool
|
EnableNotifyMail bool
|
||||||
EnableBasicAuth bool
|
EnableBasicAuth bool
|
||||||
EnablePasskeyAuth bool
|
EnablePasskeyAuth bool
|
||||||
@@ -159,7 +160,18 @@ func loadServiceFrom(rootCfg ConfigProvider) {
|
|||||||
Service.EmailDomainBlockList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_BLOCKLIST")
|
Service.EmailDomainBlockList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_BLOCKLIST")
|
||||||
Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!(Service.DisableRegistration || Service.AllowOnlyExternalRegistration))
|
Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!(Service.DisableRegistration || Service.AllowOnlyExternalRegistration))
|
||||||
Service.ShowMilestonesDashboardPage = sec.Key("SHOW_MILESTONES_DASHBOARD_PAGE").MustBool(true)
|
Service.ShowMilestonesDashboardPage = sec.Key("SHOW_MILESTONES_DASHBOARD_PAGE").MustBool(true)
|
||||||
Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool()
|
|
||||||
|
// boolean values are considered as "strict"
|
||||||
|
var err error
|
||||||
|
Service.RequireSignInViewStrict, err = sec.Key("REQUIRE_SIGNIN_VIEW").Bool()
|
||||||
|
if s := sec.Key("REQUIRE_SIGNIN_VIEW").String(); err != nil && s != "" {
|
||||||
|
// non-boolean value only supports "expensive" at the moment
|
||||||
|
Service.BlockAnonymousAccessExpensive = s == "expensive"
|
||||||
|
if !Service.BlockAnonymousAccessExpensive {
|
||||||
|
log.Error("Invalid config option: REQUIRE_SIGNIN_VIEW = %s", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Service.EnableBasicAuth = sec.Key("ENABLE_BASIC_AUTHENTICATION").MustBool(true)
|
Service.EnableBasicAuth = sec.Key("ENABLE_BASIC_AUTHENTICATION").MustBool(true)
|
||||||
Service.EnablePasswordSignInForm = sec.Key("ENABLE_PASSWORD_SIGNIN_FORM").MustBool(true)
|
Service.EnablePasswordSignInForm = sec.Key("ENABLE_PASSWORD_SIGNIN_FORM").MustBool(true)
|
||||||
Service.EnablePasskeyAuth = sec.Key("ENABLE_PASSKEY_AUTHENTICATION").MustBool(true)
|
Service.EnablePasskeyAuth = sec.Key("ENABLE_PASSKEY_AUTHENTICATION").MustBool(true)
|
||||||
|
|||||||
@@ -7,16 +7,14 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
"github.com/gobwas/glob"
|
"github.com/gobwas/glob"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLoadServices(t *testing.T) {
|
func TestLoadServices(t *testing.T) {
|
||||||
oldService := Service
|
defer test.MockVariableValue(&Service)()
|
||||||
defer func() {
|
|
||||||
Service = oldService
|
|
||||||
}()
|
|
||||||
|
|
||||||
cfg, err := NewConfigProviderFromData(`
|
cfg, err := NewConfigProviderFromData(`
|
||||||
[service]
|
[service]
|
||||||
@@ -48,10 +46,7 @@ EMAIL_DOMAIN_BLOCKLIST = d3, *.b
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadServiceVisibilityModes(t *testing.T) {
|
func TestLoadServiceVisibilityModes(t *testing.T) {
|
||||||
oldService := Service
|
defer test.MockVariableValue(&Service)()
|
||||||
defer func() {
|
|
||||||
Service = oldService
|
|
||||||
}()
|
|
||||||
|
|
||||||
kases := map[string]func(){
|
kases := map[string]func(){
|
||||||
`
|
`
|
||||||
@@ -130,3 +125,33 @@ ALLOWED_USER_VISIBILITY_MODES = public, limit, privated
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadServiceRequireSignInView(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&Service)()
|
||||||
|
|
||||||
|
cfg, err := NewConfigProviderFromData(`
|
||||||
|
[service]
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
loadServiceFrom(cfg)
|
||||||
|
assert.False(t, Service.RequireSignInViewStrict)
|
||||||
|
assert.False(t, Service.BlockAnonymousAccessExpensive)
|
||||||
|
|
||||||
|
cfg, err = NewConfigProviderFromData(`
|
||||||
|
[service]
|
||||||
|
REQUIRE_SIGNIN_VIEW = true
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
loadServiceFrom(cfg)
|
||||||
|
assert.True(t, Service.RequireSignInViewStrict)
|
||||||
|
assert.False(t, Service.BlockAnonymousAccessExpensive)
|
||||||
|
|
||||||
|
cfg, err = NewConfigProviderFromData(`
|
||||||
|
[service]
|
||||||
|
REQUIRE_SIGNIN_VIEW = expensive
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
loadServiceFrom(cfg)
|
||||||
|
assert.False(t, Service.RequireSignInViewStrict)
|
||||||
|
assert.True(t, Service.BlockAnonymousAccessExpensive)
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,9 +23,11 @@ type AccessToken struct {
|
|||||||
type AccessTokenList []*AccessToken
|
type AccessTokenList []*AccessToken
|
||||||
|
|
||||||
// CreateAccessTokenOption options when create access token
|
// CreateAccessTokenOption options when create access token
|
||||||
|
// swagger:model CreateAccessTokenOption
|
||||||
type CreateAccessTokenOption struct {
|
type CreateAccessTokenOption struct {
|
||||||
// required: true
|
// required: true
|
||||||
Name string `json:"name" binding:"Required"`
|
Name string `json:"name" binding:"Required"`
|
||||||
|
// example: ["all", "read:activitypub","read:issue", "write:misc", "read:notification", "read:organization", "read:package", "read:repository", "read:user"]
|
||||||
Scopes []string `json:"scopes"`
|
Scopes []string `json:"scopes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
applet
|
applet
|
||||||
application.linux-arm64
|
application.linux-arm64
|
||||||
application.linux-armv6hf
|
application.linux-armv6hf
|
||||||
|
application.linux-riscv64
|
||||||
application.linux32
|
application.linux32
|
||||||
application.linux64
|
application.linux64
|
||||||
application.windows32
|
application.windows32
|
||||||
|
|||||||
@@ -2699,6 +2699,7 @@ branch.restore_success = Branch "%s" has been restored.
|
|||||||
branch.restore_failed = Failed to restore branch "%s".
|
branch.restore_failed = Failed to restore branch "%s".
|
||||||
branch.protected_deletion_failed = Branch "%s" is protected. It cannot be deleted.
|
branch.protected_deletion_failed = Branch "%s" is protected. It cannot be deleted.
|
||||||
branch.default_deletion_failed = Branch "%s" is the default branch. It cannot be deleted.
|
branch.default_deletion_failed = Branch "%s" is the default branch. It cannot be deleted.
|
||||||
|
branch.default_branch_not_exist = Default branch "%s" does not exist.
|
||||||
branch.restore = Restore Branch "%s"
|
branch.restore = Restore Branch "%s"
|
||||||
branch.download = Download Branch "%s"
|
branch.download = Download Branch "%s"
|
||||||
branch.rename = Rename Branch "%s"
|
branch.rename = Rename Branch "%s"
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func apiError(ctx *context.Context, status int, obj any) {
|
|||||||
|
|
||||||
// https://rust-lang.github.io/rfcs/2789-sparse-index.html
|
// https://rust-lang.github.io/rfcs/2789-sparse-index.html
|
||||||
func RepositoryConfig(ctx *context.Context) {
|
func RepositoryConfig(ctx *context.Context) {
|
||||||
ctx.JSON(http.StatusOK, cargo_service.BuildConfig(ctx.Package.Owner, setting.Service.RequireSignInView || ctx.Package.Owner.Visibility != structs.VisibleTypePublic))
|
ctx.JSON(http.StatusOK, cargo_service.BuildConfig(ctx.Package.Owner, setting.Service.RequireSignInViewStrict || ctx.Package.Owner.Visibility != structs.VisibleTypePublic))
|
||||||
}
|
}
|
||||||
|
|
||||||
func EnumeratePackageVersions(ctx *context.Context) {
|
func EnumeratePackageVersions(ctx *context.Context) {
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ func apiUnauthorizedError(ctx *context.Context) {
|
|||||||
|
|
||||||
// ReqContainerAccess is a middleware which checks the current user valid (real user or ghost if anonymous access is enabled)
|
// ReqContainerAccess is a middleware which checks the current user valid (real user or ghost if anonymous access is enabled)
|
||||||
func ReqContainerAccess(ctx *context.Context) {
|
func ReqContainerAccess(ctx *context.Context) {
|
||||||
if ctx.Doer == nil || (setting.Service.RequireSignInView && ctx.Doer.IsGhost()) {
|
if ctx.Doer == nil || (setting.Service.RequireSignInViewStrict && ctx.Doer.IsGhost()) {
|
||||||
apiUnauthorizedError(ctx)
|
apiUnauthorizedError(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,7 +152,7 @@ func Authenticate(ctx *context.Context) {
|
|||||||
u := ctx.Doer
|
u := ctx.Doer
|
||||||
packageScope := auth_service.GetAccessScope(ctx.Data)
|
packageScope := auth_service.GetAccessScope(ctx.Data)
|
||||||
if u == nil {
|
if u == nil {
|
||||||
if setting.Service.RequireSignInView {
|
if setting.Service.RequireSignInViewStrict {
|
||||||
apiUnauthorizedError(ctx)
|
apiUnauthorizedError(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -290,7 +290,24 @@ func DownloadManifest(ctx *context.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-6
|
// formFileOptionalReadCloser returns (nil, nil) if the formKey is not present.
|
||||||
|
func formFileOptionalReadCloser(ctx *context.Context, formKey string) (io.ReadCloser, error) {
|
||||||
|
multipartFile, _, err := ctx.Req.FormFile(formKey)
|
||||||
|
if err != nil && !errors.Is(err, http.ErrMissingFile) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if multipartFile != nil {
|
||||||
|
return multipartFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
content := ctx.Req.FormValue(formKey)
|
||||||
|
if content == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return io.NopCloser(strings.NewReader(ctx.Req.FormValue(formKey))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadPackageFile refers to https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-6
|
||||||
func UploadPackageFile(ctx *context.Context) {
|
func UploadPackageFile(ctx *context.Context) {
|
||||||
packageScope := ctx.PathParam("scope")
|
packageScope := ctx.PathParam("scope")
|
||||||
packageName := ctx.PathParam("name")
|
packageName := ctx.PathParam("name")
|
||||||
@@ -304,9 +321,9 @@ func UploadPackageFile(ctx *context.Context) {
|
|||||||
|
|
||||||
packageVersion := v.Core().String()
|
packageVersion := v.Core().String()
|
||||||
|
|
||||||
file, _, err := ctx.Req.FormFile("source-archive")
|
file, err := formFileOptionalReadCloser(ctx, "source-archive")
|
||||||
if err != nil {
|
if file == nil || err != nil {
|
||||||
apiError(ctx, http.StatusBadRequest, err)
|
apiError(ctx, http.StatusBadRequest, "unable to read source-archive file")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
@@ -318,10 +335,13 @@ func UploadPackageFile(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
defer buf.Close()
|
defer buf.Close()
|
||||||
|
|
||||||
var mr io.Reader
|
mr, err := formFileOptionalReadCloser(ctx, "metadata")
|
||||||
metadata := ctx.Req.FormValue("metadata")
|
if err != nil {
|
||||||
if metadata != "" {
|
apiError(ctx, http.StatusBadRequest, "unable to read metadata file")
|
||||||
mr = strings.NewReader(metadata)
|
return
|
||||||
|
}
|
||||||
|
if mr != nil {
|
||||||
|
defer mr.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
pck, err := swift_module.ParsePackage(buf, buf.Size(), mr)
|
pck, err := swift_module.ParsePackage(buf, buf.Size(), mr)
|
||||||
|
|||||||
@@ -356,7 +356,7 @@ func reqToken() func(ctx *context.APIContext) {
|
|||||||
|
|
||||||
func reqExploreSignIn() func(ctx *context.APIContext) {
|
func reqExploreSignIn() func(ctx *context.APIContext) {
|
||||||
return func(ctx *context.APIContext) {
|
return func(ctx *context.APIContext) {
|
||||||
if (setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView) && !ctx.IsSigned {
|
if (setting.Service.RequireSignInViewStrict || setting.Service.Explore.RequireSigninView) && !ctx.IsSigned {
|
||||||
ctx.Error(http.StatusUnauthorized, "reqExploreSignIn", "you must be signed in to search for users")
|
ctx.Error(http.StatusUnauthorized, "reqExploreSignIn", "you must be signed in to search for users")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -874,7 +874,7 @@ func Routes() *web.Router {
|
|||||||
m.Use(apiAuth(buildAuthGroup()))
|
m.Use(apiAuth(buildAuthGroup()))
|
||||||
|
|
||||||
m.Use(verifyAuthWithOptions(&common.VerifyOptions{
|
m.Use(verifyAuthWithOptions(&common.VerifyOptions{
|
||||||
SignInRequired: setting.Service.RequireSignInView,
|
SignInRequired: setting.Service.RequireSignInViewStrict,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
addActionsRoutes := func(
|
addActionsRoutes := func(
|
||||||
|
|||||||
@@ -895,6 +895,15 @@ func EditIssue(ctx *context.APIContext) {
|
|||||||
issue.MilestoneID != *form.Milestone {
|
issue.MilestoneID != *form.Milestone {
|
||||||
oldMilestoneID := issue.MilestoneID
|
oldMilestoneID := issue.MilestoneID
|
||||||
issue.MilestoneID = *form.Milestone
|
issue.MilestoneID = *form.Milestone
|
||||||
|
if issue.MilestoneID > 0 {
|
||||||
|
issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, *form.Milestone)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
issue.Milestone = nil
|
||||||
|
}
|
||||||
if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
|
if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "ChangeMilestoneAssign", err)
|
ctx.Error(http.StatusInternalServerError, "ChangeMilestoneAssign", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -694,6 +694,11 @@ func EditPullRequest(ctx *context.APIContext) {
|
|||||||
issue.MilestoneID != form.Milestone {
|
issue.MilestoneID != form.Milestone {
|
||||||
oldMilestoneID := issue.MilestoneID
|
oldMilestoneID := issue.MilestoneID
|
||||||
issue.MilestoneID = form.Milestone
|
issue.MilestoneID = form.Milestone
|
||||||
|
issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, form.Milestone)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
|
if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "ChangeMilestoneAssign", err)
|
ctx.Error(http.StatusInternalServerError, "ChangeMilestoneAssign", err)
|
||||||
return
|
return
|
||||||
@@ -1638,7 +1643,9 @@ func GetPullRequestFiles(ctx *context.APIContext) {
|
|||||||
|
|
||||||
apiFiles := make([]*api.ChangedFile, 0, limit)
|
apiFiles := make([]*api.ChangedFile, 0, limit)
|
||||||
for i := start; i < start+limit; i++ {
|
for i := start; i < start+limit; i++ {
|
||||||
apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.HeadRepo, endCommitID))
|
// refs/pull/1/head stores the HEAD commit ID, allowing all related commits to be found in the base repository.
|
||||||
|
// The head repository might have been deleted, so we should not rely on it here.
|
||||||
|
apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.BaseRepo, endCommitID))
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.SetLinkHeader(totalNumberOfFiles, listOptions.PageSize)
|
ctx.SetLinkHeader(totalNumberOfFiles, listOptions.PageSize)
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ func getWikiPage(ctx *context.APIContext, wikiName wiki_service.WebPath) *api.Wi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get commit count - wiki revisions
|
// get commit count - wiki revisions
|
||||||
commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
|
commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
|
||||||
|
|
||||||
// Get last change information.
|
// Get last change information.
|
||||||
lastCommit, err := wikiRepo.GetCommitByPath(pageFilename)
|
lastCommit, err := wikiRepo.GetCommitByPath(pageFilename)
|
||||||
@@ -432,7 +432,7 @@ func ListPageRevisions(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get commit count - wiki revisions
|
// get commit count - wiki revisions
|
||||||
commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
|
commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
|
||||||
|
|
||||||
page := ctx.FormInt("page")
|
page := ctx.FormInt("page")
|
||||||
if page <= 1 {
|
if page <= 1 {
|
||||||
@@ -442,7 +442,7 @@ func ListPageRevisions(ctx *context.APIContext) {
|
|||||||
// get Commit Count
|
// get Commit Count
|
||||||
commitsHistory, err := wikiRepo.CommitsByFileAndRange(
|
commitsHistory, err := wikiRepo.CommitsByFileAndRange(
|
||||||
git.CommitsByFileAndRangeOptions{
|
git.CommitsByFileAndRangeOptions{
|
||||||
Revision: "master",
|
Revision: ctx.Repo.Repository.DefaultWikiBranch,
|
||||||
File: pageFilename,
|
File: pageFilename,
|
||||||
Page: page,
|
Page: page,
|
||||||
})
|
})
|
||||||
@@ -486,7 +486,7 @@ func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit)
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
commit, err := wikiRepo.GetBranchCommit("master")
|
commit, err := wikiRepo.GetBranchCommit(ctx.Repo.Repository.DefaultWikiBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if git.IsErrNotExist(err) {
|
if git.IsErrNotExist(err) {
|
||||||
ctx.NotFound(err)
|
ctx.NotFound(err)
|
||||||
|
|||||||
92
routers/common/blockexpensive.go
Normal file
92
routers/common/blockexpensive.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BlockExpensive() func(next http.Handler) http.Handler {
|
||||||
|
if !setting.Service.BlockAnonymousAccessExpensive {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
ret := determineRequestPriority(req.Context())
|
||||||
|
if !ret.SignedIn {
|
||||||
|
if ret.Expensive || ret.LongPolling {
|
||||||
|
http.Redirect(w, req, setting.AppSubURL+"/user/login", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next.ServeHTTP(w, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRoutePathExpensive(routePattern string) bool {
|
||||||
|
if strings.HasPrefix(routePattern, "/user/") || strings.HasPrefix(routePattern, "/login/") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
expensivePaths := []string{
|
||||||
|
// code related
|
||||||
|
"/{username}/{reponame}/archive/",
|
||||||
|
"/{username}/{reponame}/blame/",
|
||||||
|
"/{username}/{reponame}/commit/",
|
||||||
|
"/{username}/{reponame}/commits/",
|
||||||
|
"/{username}/{reponame}/graph",
|
||||||
|
"/{username}/{reponame}/media/",
|
||||||
|
"/{username}/{reponame}/raw/",
|
||||||
|
"/{username}/{reponame}/src/",
|
||||||
|
|
||||||
|
// issue & PR related (no trailing slash)
|
||||||
|
"/{username}/{reponame}/issues",
|
||||||
|
"/{username}/{reponame}/{type:issues}",
|
||||||
|
"/{username}/{reponame}/pulls",
|
||||||
|
"/{username}/{reponame}/{type:pulls}",
|
||||||
|
"/{username}/{reponame}/{type:issues|pulls}", // for 1.23 only
|
||||||
|
|
||||||
|
// wiki
|
||||||
|
"/{username}/{reponame}/wiki/",
|
||||||
|
|
||||||
|
// activity
|
||||||
|
"/{username}/{reponame}/activity/",
|
||||||
|
}
|
||||||
|
for _, path := range expensivePaths {
|
||||||
|
if strings.HasPrefix(routePattern, path) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRoutePathForLongPolling(routePattern string) bool {
|
||||||
|
return routePattern == "/user/events"
|
||||||
|
}
|
||||||
|
|
||||||
|
func determineRequestPriority(ctx context.Context) (ret struct {
|
||||||
|
SignedIn bool
|
||||||
|
Expensive bool
|
||||||
|
LongPolling bool
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
dataStore := middleware.GetContextData(ctx)
|
||||||
|
chiRoutePath := chi.RouteContext(ctx).RoutePattern()
|
||||||
|
if _, ok := dataStore[middleware.ContextDataKeySignedUser].(*user_model.User); ok {
|
||||||
|
ret.SignedIn = true
|
||||||
|
} else {
|
||||||
|
ret.Expensive = isRoutePathExpensive(chiRoutePath)
|
||||||
|
ret.LongPolling = isRoutePathForLongPolling(chiRoutePath)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
30
routers/common/blockexpensive_test.go
Normal file
30
routers/common/blockexpensive_test.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBlockExpensive(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
expensive bool
|
||||||
|
routePath string
|
||||||
|
}{
|
||||||
|
{false, "/user/xxx"},
|
||||||
|
{false, "/login/xxx"},
|
||||||
|
{true, "/{username}/{reponame}/archive/xxx"},
|
||||||
|
{true, "/{username}/{reponame}/graph"},
|
||||||
|
{true, "/{username}/{reponame}/src/xxx"},
|
||||||
|
{true, "/{username}/{reponame}/wiki/xxx"},
|
||||||
|
{true, "/{username}/{reponame}/activity/xxx"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
assert.Equal(t, c.expensive, isRoutePathExpensive(c.routePath), "routePath: %s", c.routePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, isRoutePathForLongPolling("/user/events"))
|
||||||
|
}
|
||||||
@@ -156,7 +156,7 @@ func Install(ctx *context.Context) {
|
|||||||
form.DisableRegistration = setting.Service.DisableRegistration
|
form.DisableRegistration = setting.Service.DisableRegistration
|
||||||
form.AllowOnlyExternalRegistration = setting.Service.AllowOnlyExternalRegistration
|
form.AllowOnlyExternalRegistration = setting.Service.AllowOnlyExternalRegistration
|
||||||
form.EnableCaptcha = setting.Service.EnableCaptcha
|
form.EnableCaptcha = setting.Service.EnableCaptcha
|
||||||
form.RequireSignInView = setting.Service.RequireSignInView
|
form.RequireSignInView = setting.Service.RequireSignInViewStrict
|
||||||
form.DefaultKeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate
|
form.DefaultKeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate
|
||||||
form.DefaultAllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization
|
form.DefaultAllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization
|
||||||
form.DefaultEnableTimetracking = setting.Service.DefaultEnableTimetracking
|
form.DefaultEnableTimetracking = setting.Service.DefaultEnableTimetracking
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ func ServCommand(ctx *context.PrivateContext) {
|
|||||||
ownerName := ctx.PathParam(":owner")
|
ownerName := ctx.PathParam(":owner")
|
||||||
repoName := ctx.PathParam(":repo")
|
repoName := ctx.PathParam(":repo")
|
||||||
mode := perm.AccessMode(ctx.FormInt("mode"))
|
mode := perm.AccessMode(ctx.FormInt("mode"))
|
||||||
|
verb := ctx.FormString("verb")
|
||||||
|
|
||||||
// Set the basic parts of the results to return
|
// Set the basic parts of the results to return
|
||||||
results := private.ServCommandResults{
|
results := private.ServCommandResults{
|
||||||
@@ -286,7 +287,7 @@ func ServCommand(ctx *context.PrivateContext) {
|
|||||||
repo.IsPrivate ||
|
repo.IsPrivate ||
|
||||||
owner.Visibility.IsPrivate() ||
|
owner.Visibility.IsPrivate() ||
|
||||||
(user != nil && user.IsRestricted) || // user will be nil if the key is a deploykey
|
(user != nil && user.IsRestricted) || // user will be nil if the key is a deploykey
|
||||||
setting.Service.RequireSignInView) {
|
setting.Service.RequireSignInViewStrict) {
|
||||||
if key.Type == asymkey_model.KeyTypeDeploy {
|
if key.Type == asymkey_model.KeyTypeDeploy {
|
||||||
if deployKey.Mode < mode {
|
if deployKey.Mode < mode {
|
||||||
ctx.JSON(http.StatusUnauthorized, private.Response{
|
ctx.JSON(http.StatusUnauthorized, private.Response{
|
||||||
@@ -295,8 +296,11 @@ func ServCommand(ctx *context.PrivateContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Because of the special ref "refs/for" we will need to delay write permission check
|
// Because of the special ref "refs/for" (AGit) we will need to delay write permission check,
|
||||||
if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode {
|
// AGit flow needs to write its own ref when the doer has "reader" permission (allowing to create PR).
|
||||||
|
// The real permission check is done in HookPreReceive (routers/private/hook_pre_receive.go).
|
||||||
|
// Here it should relax the permission check for "git push (git-receive-pack)", but not for others like LFS operations.
|
||||||
|
if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode && verb == "git-receive-pack" {
|
||||||
mode = perm.AccessModeRead
|
mode = perm.AccessModeRead
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,26 +4,12 @@
|
|||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/routers/web/repo"
|
"code.gitea.io/gitea/routers/web/repo"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addOwnerRepoGitHTTPRouters(m *web.Router) {
|
func addOwnerRepoGitHTTPRouters(m *web.Router) {
|
||||||
reqGitSignIn := func(ctx *context.Context) {
|
|
||||||
if !setting.Service.RequireSignInView {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// rely on the results of Contexter
|
|
||||||
if !ctx.IsSigned {
|
|
||||||
// TODO: support digit auth - which would be Authorization header with digit
|
|
||||||
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`)
|
|
||||||
ctx.Error(http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.Group("/{username}/{reponame}", func() {
|
m.Group("/{username}/{reponame}", func() {
|
||||||
m.Methods("POST,OPTIONS", "/git-upload-pack", repo.ServiceUploadPack)
|
m.Methods("POST,OPTIONS", "/git-upload-pack", repo.ServiceUploadPack)
|
||||||
m.Methods("POST,OPTIONS", "/git-receive-pack", repo.ServiceReceivePack)
|
m.Methods("POST,OPTIONS", "/git-receive-pack", repo.ServiceReceivePack)
|
||||||
@@ -36,5 +22,5 @@ func addOwnerRepoGitHTTPRouters(m *web.Router) {
|
|||||||
m.Methods("GET,OPTIONS", "/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38,62}}", repo.GetLooseObject)
|
m.Methods("GET,OPTIONS", "/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38,62}}", repo.GetLooseObject)
|
||||||
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.pack", repo.GetPackFile)
|
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.pack", repo.GetPackFile)
|
||||||
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.idx", repo.GetIdxFile)
|
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.idx", repo.GetIdxFile)
|
||||||
}, optSignInIgnoreCsrf, reqGitSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context.UserAssignmentWeb())
|
}, optSignInIgnoreCsrf, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context.UserAssignmentWeb())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package actions
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
stdCtx "context"
|
stdCtx "context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
"slices"
|
||||||
@@ -77,7 +78,11 @@ func List(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
} else if !empty {
|
} else if !empty {
|
||||||
commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
|
commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
|
||||||
if err != nil {
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Data["NotFoundPrompt"] = ctx.Tr("repo.branch.default_branch_not_exist", ctx.Repo.Repository.DefaultBranch)
|
||||||
|
ctx.NotFound("GetBranchCommit", err)
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
ctx.ServerError("GetBranchCommit", err)
|
ctx.ServerError("GetBranchCommit", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
activities_model "code.gitea.io/gitea/models/activities"
|
activities_model "code.gitea.io/gitea/models/activities"
|
||||||
|
"code.gitea.io/gitea/models/git"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
@@ -52,12 +53,26 @@ func Activity(ctx *context.Context) {
|
|||||||
ctx.Data["DateUntil"] = timeUntil
|
ctx.Data["DateUntil"] = timeUntil
|
||||||
ctx.Data["PeriodText"] = ctx.Tr("repo.activity.period." + ctx.Data["Period"].(string))
|
ctx.Data["PeriodText"] = ctx.Tr("repo.activity.period." + ctx.Data["Period"].(string))
|
||||||
|
|
||||||
|
canReadCode := ctx.Repo.CanRead(unit.TypeCode)
|
||||||
|
if canReadCode {
|
||||||
|
// GetActivityStats needs to read the default branch to get some information
|
||||||
|
branchExist, _ := git.IsBranchExist(ctx, ctx.Repo.Repository.ID, ctx.Repo.Repository.DefaultBranch)
|
||||||
|
if !branchExist {
|
||||||
|
ctx.Data["NotFoundPrompt"] = ctx.Tr("repo.branch.default_branch_not_exist", ctx.Repo.Repository.DefaultBranch)
|
||||||
|
ctx.NotFound("", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if ctx.Data["Activity"], err = activities_model.GetActivityStats(ctx, ctx.Repo.Repository, timeFrom,
|
// TODO: refactor these arguments to a struct
|
||||||
|
ctx.Data["Activity"], err = activities_model.GetActivityStats(ctx, ctx.Repo.Repository, timeFrom,
|
||||||
ctx.Repo.CanRead(unit.TypeReleases),
|
ctx.Repo.CanRead(unit.TypeReleases),
|
||||||
ctx.Repo.CanRead(unit.TypeIssues),
|
ctx.Repo.CanRead(unit.TypeIssues),
|
||||||
ctx.Repo.CanRead(unit.TypePullRequests),
|
ctx.Repo.CanRead(unit.TypePullRequests),
|
||||||
ctx.Repo.CanRead(unit.TypeCode)); err != nil {
|
canReadCode,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
ctx.ServerError("GetActivityStats", err)
|
ctx.ServerError("GetActivityStats", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func CodeFrequencyData(ctx *context.Context) {
|
|||||||
ctx.Status(http.StatusAccepted)
|
ctx.Status(http.StatusAccepted)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.ServerError("GetCodeFrequencyData", err)
|
ctx.ServerError("GetContributorStats", err)
|
||||||
} else {
|
} else {
|
||||||
ctx.JSON(http.StatusOK, contributorStats["total"].Weeks)
|
ctx.JSON(http.StatusOK, contributorStats["total"].Weeks)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -405,7 +405,6 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo {
|
|||||||
ctx.ServerError("OpenRepository", err)
|
ctx.ServerError("OpenRepository", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
defer ci.HeadGitRepo.Close()
|
|
||||||
} else {
|
} else {
|
||||||
ctx.NotFound("ParseCompareInfo", nil)
|
ctx.NotFound("ParseCompareInfo", nil)
|
||||||
return nil
|
return nil
|
||||||
@@ -708,7 +707,7 @@ func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repositor
|
|||||||
func CompareDiff(ctx *context.Context) {
|
func CompareDiff(ctx *context.Context) {
|
||||||
ci := ParseCompareInfo(ctx)
|
ci := ParseCompareInfo(ctx)
|
||||||
defer func() {
|
defer func() {
|
||||||
if ci != nil && ci.HeadGitRepo != nil {
|
if !ctx.Repo.PullRequest.SameRepo && ci != nil && ci.HeadGitRepo != nil {
|
||||||
ci.HeadGitRepo.Close()
|
ci.HeadGitRepo.Close()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ func httpBase(ctx *context.Context) *serviceHandler {
|
|||||||
// Only public pull don't need auth.
|
// Only public pull don't need auth.
|
||||||
isPublicPull := repoExist && !repo.IsPrivate && isPull
|
isPublicPull := repoExist && !repo.IsPrivate && isPull
|
||||||
var (
|
var (
|
||||||
askAuth = !isPublicPull || setting.Service.RequireSignInView
|
askAuth = !isPublicPull || setting.Service.RequireSignInViewStrict
|
||||||
environ []string
|
environ []string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -418,6 +418,16 @@ func UpdateIssueMilestone(ctx *context.Context) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
issue.MilestoneID = milestoneID
|
issue.MilestoneID = milestoneID
|
||||||
|
if milestoneID > 0 {
|
||||||
|
var err error
|
||||||
|
issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetMilestoneByRepoID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
issue.Milestone = nil
|
||||||
|
}
|
||||||
if err := issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
|
if err := issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
|
||||||
ctx.ServerError("ChangeMilestoneAssign", err)
|
ctx.ServerError("ChangeMilestoneAssign", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1263,7 +1263,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
|
|||||||
|
|
||||||
ci := ParseCompareInfo(ctx)
|
ci := ParseCompareInfo(ctx)
|
||||||
defer func() {
|
defer func() {
|
||||||
if ci != nil && ci.HeadGitRepo != nil {
|
if !ctx.Repo.PullRequest.SameRepo && ci != nil && ci.HeadGitRepo != nil {
|
||||||
ci.HeadGitRepo.Close()
|
ci.HeadGitRepo.Close()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -4,12 +4,10 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
contributors_service "code.gitea.io/gitea/services/repository"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -26,16 +24,3 @@ func RecentCommits(ctx *context.Context) {
|
|||||||
|
|
||||||
ctx.HTML(http.StatusOK, tplRecentCommits)
|
ctx.HTML(http.StatusOK, tplRecentCommits)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecentCommitsData returns JSON of recent commits data
|
|
||||||
func RecentCommitsData(ctx *context.Context) {
|
|
||||||
if contributorStats, err := contributors_service.GetContributorStats(ctx, ctx.Cache, ctx.Repo.Repository, ctx.Repo.CommitID); err != nil {
|
|
||||||
if errors.Is(err, contributors_service.ErrAwaitGeneration) {
|
|
||||||
ctx.Status(http.StatusAccepted)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.ServerError("RecentCommitsData", err)
|
|
||||||
} else {
|
|
||||||
ctx.JSON(http.StatusOK, contributorStats["total"].Weeks)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
"path"
|
"path"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
@@ -79,7 +78,7 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) {
|
|||||||
if workFlowErr != nil {
|
if workFlowErr != nil {
|
||||||
ctx.Data["FileError"] = ctx.Locale.Tr("actions.runs.invalid_workflow_helper", workFlowErr.Error())
|
ctx.Data["FileError"] = ctx.Locale.Tr("actions.runs.invalid_workflow_helper", workFlowErr.Error())
|
||||||
}
|
}
|
||||||
} else if slices.Contains([]string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"}, ctx.Repo.TreePath) {
|
} else if issue_service.IsCodeOwnerFile(ctx.Repo.TreePath) {
|
||||||
if data, err := blob.GetBlobContent(setting.UI.MaxDisplayFileSize); err == nil {
|
if data, err := blob.GetBlobContent(setting.UI.MaxDisplayFileSize); err == nil {
|
||||||
_, warnings := issue_model.GetCodeOwnersFromContent(ctx, data)
|
_, warnings := issue_model.GetCodeOwnersFromContent(ctx, data)
|
||||||
if len(warnings) > 0 {
|
if len(warnings) > 0 {
|
||||||
|
|||||||
@@ -285,23 +285,23 @@ func Routes() *web.Router {
|
|||||||
mid = append(mid, repo.GetActiveStopwatch)
|
mid = append(mid, repo.GetActiveStopwatch)
|
||||||
mid = append(mid, goGet)
|
mid = append(mid, goGet)
|
||||||
|
|
||||||
others := web.NewRouter()
|
webRoutes := web.NewRouter()
|
||||||
others.Use(mid...)
|
webRoutes.Use(mid...)
|
||||||
registerRoutes(others)
|
webRoutes.Group("", func() { registerWebRoutes(webRoutes) }, common.BlockExpensive())
|
||||||
routes.Mount("", others)
|
routes.Mount("", webRoutes)
|
||||||
return routes
|
return routes
|
||||||
}
|
}
|
||||||
|
|
||||||
var optSignInIgnoreCsrf = verifyAuthWithOptions(&common.VerifyOptions{DisableCSRF: true})
|
var optSignInIgnoreCsrf = verifyAuthWithOptions(&common.VerifyOptions{DisableCSRF: true})
|
||||||
|
|
||||||
// registerRoutes register routes
|
// registerWebRoutes register routes
|
||||||
func registerRoutes(m *web.Router) {
|
func registerWebRoutes(m *web.Router) {
|
||||||
// required to be signed in or signed out
|
// required to be signed in or signed out
|
||||||
reqSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true})
|
reqSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true})
|
||||||
reqSignOut := verifyAuthWithOptions(&common.VerifyOptions{SignOutRequired: true})
|
reqSignOut := verifyAuthWithOptions(&common.VerifyOptions{SignOutRequired: true})
|
||||||
// optional sign in (if signed in, use the user as doer, if not, no doer)
|
// optional sign in (if signed in, use the user as doer, if not, no doer)
|
||||||
optSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView})
|
optSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInViewStrict})
|
||||||
optExploreSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView})
|
optExploreSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInViewStrict || setting.Service.Explore.RequireSigninView})
|
||||||
|
|
||||||
validation.AddBindingRules()
|
validation.AddBindingRules()
|
||||||
|
|
||||||
@@ -1454,6 +1454,8 @@ func registerRoutes(m *web.Router) {
|
|||||||
m.Group("/{username}/{reponame}/activity", func() {
|
m.Group("/{username}/{reponame}/activity", func() {
|
||||||
m.Get("", repo.Activity)
|
m.Get("", repo.Activity)
|
||||||
m.Get("/{period}", repo.Activity)
|
m.Get("/{period}", repo.Activity)
|
||||||
|
|
||||||
|
m.Group("", func() {
|
||||||
m.Group("/contributors", func() {
|
m.Group("/contributors", func() {
|
||||||
m.Get("", repo.Contributors)
|
m.Get("", repo.Contributors)
|
||||||
m.Get("/data", repo.ContributorsData)
|
m.Get("/data", repo.ContributorsData)
|
||||||
@@ -1464,10 +1466,11 @@ func registerRoutes(m *web.Router) {
|
|||||||
})
|
})
|
||||||
m.Group("/recent-commits", func() {
|
m.Group("/recent-commits", func() {
|
||||||
m.Get("", repo.RecentCommits)
|
m.Get("", repo.RecentCommits)
|
||||||
m.Get("/data", repo.RecentCommitsData)
|
m.Get("/data", repo.CodeFrequencyData) // "recent-commits" also uses the same data as "code-frequency"
|
||||||
})
|
})
|
||||||
|
}, reqRepoCodeReader)
|
||||||
},
|
},
|
||||||
optSignIn, context.RepoAssignment, context.RequireRepoReaderOr(unit.TypePullRequests, unit.TypeIssues, unit.TypeReleases),
|
optSignIn, context.RepoAssignment, context.RequireRepoReaderOr(unit.TypeCode, unit.TypePullRequests, unit.TypeIssues, unit.TypeReleases),
|
||||||
context.RepoRef(), repo.MustBeNotEmpty,
|
context.RepoRef(), repo.MustBeNotEmpty,
|
||||||
)
|
)
|
||||||
// end "/{username}/{reponame}/activity"
|
// end "/{username}/{reponame}/activity"
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ func (ctx *Context) notFoundInternal(logMsg string, logErr error) {
|
|||||||
|
|
||||||
ctx.Data["IsRepo"] = ctx.Repo.Repository != nil
|
ctx.Data["IsRepo"] = ctx.Repo.Repository != nil
|
||||||
ctx.Data["Title"] = "Page Not Found"
|
ctx.Data["Title"] = "Page Not Found"
|
||||||
ctx.HTML(http.StatusNotFound, base.TplName("status/404"))
|
ctx.HTML(http.StatusNotFound, "status/404")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerError displays a 500 (Internal Server Error) page and prints the given error, if any.
|
// ServerError displays a 500 (Internal Server Error) page and prints the given error, if any.
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, string, any))
|
|||||||
}
|
}
|
||||||
|
|
||||||
func determineAccessMode(ctx *Base, pkg *Package, doer *user_model.User) (perm.AccessMode, error) {
|
func determineAccessMode(ctx *Base, pkg *Package, doer *user_model.User) (perm.AccessMode, error) {
|
||||||
if setting.Service.RequireSignInView && (doer == nil || doer.IsGhost()) {
|
if setting.Service.RequireSignInViewStrict && (doer == nil || doer.IsGhost()) {
|
||||||
return perm.AccessModeNone, nil
|
return perm.AccessModeNone, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -356,7 +356,9 @@ func RedirectToRepo(ctx *Base, redirectRepoID int64) {
|
|||||||
if ctx.Req.URL.RawQuery != "" {
|
if ctx.Req.URL.RawQuery != "" {
|
||||||
redirectPath += "?" + ctx.Req.URL.RawQuery
|
redirectPath += "?" + ctx.Req.URL.RawQuery
|
||||||
}
|
}
|
||||||
ctx.Redirect(path.Join(setting.AppSubURL, redirectPath), http.StatusTemporaryRedirect)
|
// Git client needs a 301 redirect by default to follow the new location
|
||||||
|
// It's not documentated in git documentation, but it's the behavior of git client
|
||||||
|
ctx.Redirect(path.Join(setting.AppSubURL, redirectPath), http.StatusMovedPermanently)
|
||||||
}
|
}
|
||||||
|
|
||||||
func repoAssignment(ctx *Context, repo *repo_model.Repository) {
|
func repoAssignment(ctx *Context, repo *repo_model.Repository) {
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ func checkStorage(opts *checkStorageOptions) func(ctx context.Context, logger lo
|
|||||||
storer: storage.LFS,
|
storer: storage.LFS,
|
||||||
isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) {
|
isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) {
|
||||||
// The oid of an LFS stored object is the name but with all the path.Separators removed
|
// The oid of an LFS stored object is the name but with all the path.Separators removed
|
||||||
oid := strings.ReplaceAll(path, "/", "")
|
oid := strings.ReplaceAll(strings.ReplaceAll(path, "\\", ""), "/", "")
|
||||||
exists, err := git.ExistsLFSObject(ctx, oid)
|
exists, err := git.ExistsLFSObject(ctx, oid)
|
||||||
return !exists, err
|
return !exists, err
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -92,8 +92,12 @@ func ChangeTitle(ctx context.Context, issue *issues_model.Issue, doer *user_mode
|
|||||||
|
|
||||||
var reviewNotifiers []*ReviewRequestNotifier
|
var reviewNotifiers []*ReviewRequestNotifier
|
||||||
if issue.IsPull && issues_model.HasWorkInProgressPrefix(oldTitle) && !issues_model.HasWorkInProgressPrefix(title) {
|
if issue.IsPull && issues_model.HasWorkInProgressPrefix(oldTitle) && !issues_model.HasWorkInProgressPrefix(title) {
|
||||||
|
if err := issue.LoadPullRequest(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
reviewNotifiers, err = PullRequestCodeOwnersReview(ctx, issue, issue.PullRequest)
|
reviewNotifiers, err = PullRequestCodeOwnersReview(ctx, issue.PullRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("PullRequestCodeOwnersReview: %v", err)
|
log.Error("PullRequestCodeOwnersReview: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package issue
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
@@ -40,20 +41,27 @@ type ReviewRequestNotifier struct {
|
|||||||
ReviewTeam *org_model.Team
|
ReviewTeam *org_model.Team
|
||||||
}
|
}
|
||||||
|
|
||||||
func PullRequestCodeOwnersReview(ctx context.Context, issue *issues_model.Issue, pr *issues_model.PullRequest) ([]*ReviewRequestNotifier, error) {
|
var codeOwnerFiles = []string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"}
|
||||||
files := []string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"}
|
|
||||||
|
|
||||||
|
func IsCodeOwnerFile(f string) bool {
|
||||||
|
return slices.Contains(codeOwnerFiles, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PullRequestCodeOwnersReview(ctx context.Context, pr *issues_model.PullRequest) ([]*ReviewRequestNotifier, error) {
|
||||||
|
if err := pr.LoadIssue(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
issue := pr.Issue
|
||||||
if pr.IsWorkInProgress(ctx) {
|
if pr.IsWorkInProgress(ctx) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pr.LoadHeadRepo(ctx); err != nil {
|
if err := pr.LoadHeadRepo(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pr.LoadBaseRepo(ctx); err != nil {
|
if err := pr.LoadBaseRepo(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
pr.Issue.Repo = pr.BaseRepo
|
||||||
|
|
||||||
if pr.BaseRepo.IsFork {
|
if pr.BaseRepo.IsFork {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -71,7 +79,7 @@ func PullRequestCodeOwnersReview(ctx context.Context, issue *issues_model.Issue,
|
|||||||
}
|
}
|
||||||
|
|
||||||
var data string
|
var data string
|
||||||
for _, file := range files {
|
for _, file := range codeOwnerFiles {
|
||||||
if blob, err := commit.GetBlobByPath(file); err == nil {
|
if blob, err := commit.GetBlobByPath(file); err == nil {
|
||||||
data, err = blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
|
data, err = blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -79,8 +87,14 @@ func PullRequestCodeOwnersReview(ctx context.Context, issue *issues_model.Issue,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if data == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
rules, _ := issues_model.GetCodeOwnersFromContent(ctx, data)
|
rules, _ := issues_model.GetCodeOwnersFromContent(ctx, data)
|
||||||
|
if len(rules) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// get the mergebase
|
// get the mergebase
|
||||||
mergeBase, err := getMergeBase(repo, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName())
|
mergeBase, err := getMergeBase(repo, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName())
|
||||||
@@ -116,13 +130,31 @@ func PullRequestCodeOwnersReview(ctx context.Context, issue *issues_model.Issue,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// load all reviews from database
|
||||||
|
latestReivews, _, err := issues_model.GetReviewsByIssueID(ctx, pr.IssueID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contain := func(list issues_model.ReviewList, u *user_model.User) bool {
|
||||||
|
for _, review := range list {
|
||||||
|
if review.ReviewerTeamID == 0 && review.ReviewerID == u.ID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
for _, u := range uniqUsers {
|
for _, u := range uniqUsers {
|
||||||
if u.ID != issue.Poster.ID {
|
if u.ID != issue.Poster.ID && !contain(latestReivews, u) {
|
||||||
comment, err := issues_model.AddReviewRequest(ctx, issue, u, issue.Poster)
|
comment, err := issues_model.AddReviewRequest(ctx, issue, u, issue.Poster)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Failed add assignee user: %s to PR review: %s#%d, error: %s", u.Name, pr.BaseRepo.Name, pr.ID, err)
|
log.Warn("Failed add assignee user: %s to PR review: %s#%d, error: %s", u.Name, pr.BaseRepo.Name, pr.ID, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if comment == nil { // comment maybe nil if review type is ReviewTypeRequest
|
||||||
|
continue
|
||||||
|
}
|
||||||
notifiers = append(notifiers, &ReviewRequestNotifier{
|
notifiers = append(notifiers, &ReviewRequestNotifier{
|
||||||
Comment: comment,
|
Comment: comment,
|
||||||
IsAdd: true,
|
IsAdd: true,
|
||||||
@@ -130,12 +162,16 @@ func PullRequestCodeOwnersReview(ctx context.Context, issue *issues_model.Issue,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, t := range uniqTeams {
|
for _, t := range uniqTeams {
|
||||||
comment, err := issues_model.AddTeamReviewRequest(ctx, issue, t, issue.Poster)
|
comment, err := issues_model.AddTeamReviewRequest(ctx, issue, t, issue.Poster)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Failed add assignee team: %s to PR review: %s#%d, error: %s", t.Name, pr.BaseRepo.Name, pr.ID, err)
|
log.Warn("Failed add assignee team: %s to PR review: %s#%d, error: %s", t.Name, pr.BaseRepo.Name, pr.ID, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if comment == nil { // comment maybe nil if review type is ReviewTypeRequest
|
||||||
|
continue
|
||||||
|
}
|
||||||
notifiers = append(notifiers, &ReviewRequestNotifier{
|
notifiers = append(notifiers, &ReviewRequestNotifier{
|
||||||
Comment: comment,
|
Comment: comment,
|
||||||
IsAdd: true,
|
IsAdd: true,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ package migrations
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/google/go-github/v61/github"
|
"github.com/google/go-github/v71/github"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrRepoNotCreated returns the error that repository not created
|
// ErrRepoNotCreated returns the error that repository not created
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/proxy"
|
"code.gitea.io/gitea/modules/proxy"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
|
||||||
"github.com/google/go-github/v61/github"
|
"github.com/google/go-github/v71/github"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ func (g *GithubDownloaderV3) LogString() string {
|
|||||||
func (g *GithubDownloaderV3) addClient(client *http.Client, baseURL string) {
|
func (g *GithubDownloaderV3) addClient(client *http.Client, baseURL string) {
|
||||||
githubClient := github.NewClient(client)
|
githubClient := github.NewClient(client)
|
||||||
if baseURL != "https://github.com" {
|
if baseURL != "https://github.com" {
|
||||||
githubClient, _ = github.NewClient(client).WithEnterpriseURLs(baseURL, baseURL)
|
githubClient, _ = githubClient.WithEnterpriseURLs(baseURL, baseURL)
|
||||||
}
|
}
|
||||||
g.clients = append(g.clients, githubClient)
|
g.clients = append(g.clients, githubClient)
|
||||||
g.rates = append(g.rates, nil)
|
g.rates = append(g.rates, nil)
|
||||||
@@ -448,9 +448,11 @@ func (g *GithubDownloaderV3) GetIssues(page, perPage int) ([]*base.Issue, bool,
|
|||||||
if !g.SkipReactions {
|
if !g.SkipReactions {
|
||||||
for i := 1; ; i++ {
|
for i := 1; ; i++ {
|
||||||
g.waitAndPickClient()
|
g.waitAndPickClient()
|
||||||
res, resp, err := g.getClient().Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, issue.GetNumber(), &github.ListOptions{
|
res, resp, err := g.getClient().Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, issue.GetNumber(), &github.ListReactionOptions{
|
||||||
|
ListOptions: github.ListOptions{
|
||||||
Page: i,
|
Page: i,
|
||||||
PerPage: perPage,
|
PerPage: perPage,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
@@ -534,9 +536,11 @@ func (g *GithubDownloaderV3) getComments(commentable base.Commentable) ([]*base.
|
|||||||
if !g.SkipReactions {
|
if !g.SkipReactions {
|
||||||
for i := 1; ; i++ {
|
for i := 1; ; i++ {
|
||||||
g.waitAndPickClient()
|
g.waitAndPickClient()
|
||||||
res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListOptions{
|
res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListReactionOptions{
|
||||||
|
ListOptions: github.ListOptions{
|
||||||
Page: i,
|
Page: i,
|
||||||
PerPage: g.maxPerPage,
|
PerPage: g.maxPerPage,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -609,9 +613,11 @@ func (g *GithubDownloaderV3) GetAllComments(page, perPage int) ([]*base.Comment,
|
|||||||
if !g.SkipReactions {
|
if !g.SkipReactions {
|
||||||
for i := 1; ; i++ {
|
for i := 1; ; i++ {
|
||||||
g.waitAndPickClient()
|
g.waitAndPickClient()
|
||||||
res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListOptions{
|
res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListReactionOptions{
|
||||||
|
ListOptions: github.ListOptions{
|
||||||
Page: i,
|
Page: i,
|
||||||
PerPage: g.maxPerPage,
|
PerPage: g.maxPerPage,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
@@ -680,9 +686,11 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq
|
|||||||
if !g.SkipReactions {
|
if !g.SkipReactions {
|
||||||
for i := 1; ; i++ {
|
for i := 1; ; i++ {
|
||||||
g.waitAndPickClient()
|
g.waitAndPickClient()
|
||||||
res, resp, err := g.getClient().Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, pr.GetNumber(), &github.ListOptions{
|
res, resp, err := g.getClient().Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, pr.GetNumber(), &github.ListReactionOptions{
|
||||||
|
ListOptions: github.ListOptions{
|
||||||
Page: i,
|
Page: i,
|
||||||
PerPage: perPage,
|
PerPage: perPage,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
@@ -767,9 +775,11 @@ func (g *GithubDownloaderV3) convertGithubReviewComments(cs []*github.PullReques
|
|||||||
if !g.SkipReactions {
|
if !g.SkipReactions {
|
||||||
for i := 1; ; i++ {
|
for i := 1; ; i++ {
|
||||||
g.waitAndPickClient()
|
g.waitAndPickClient()
|
||||||
res, resp, err := g.getClient().Reactions.ListPullRequestCommentReactions(g.ctx, g.repoOwner, g.repoName, c.GetID(), &github.ListOptions{
|
res, resp, err := g.getClient().Reactions.ListPullRequestCommentReactions(g.ctx, g.repoOwner, g.repoName, c.GetID(), &github.ListReactionOptions{
|
||||||
|
ListOptions: github.ListOptions{
|
||||||
Page: i,
|
Page: i,
|
||||||
PerPage: g.maxPerPage,
|
PerPage: g.maxPerPage,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -879,3 +889,18 @@ func (g *GithubDownloaderV3) GetReviews(reviewable base.Reviewable) ([]*base.Rev
|
|||||||
}
|
}
|
||||||
return allReviews, nil
|
return allReviews, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FormatCloneURL add authentication into remote URLs
|
||||||
|
func (g *GithubDownloaderV3) FormatCloneURL(opts MigrateOptions, remoteAddr string) (string, error) {
|
||||||
|
u, err := url.Parse(remoteAddr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(opts.AuthToken) > 0 {
|
||||||
|
// "multiple tokens" are used to benefit more "API rate limit quota"
|
||||||
|
// git clone doesn't count for rate limits, so only use the first token.
|
||||||
|
// source: https://github.com/orgs/community/discussions/44515
|
||||||
|
u.User = url.UserPassword("oauth2", strings.Split(opts.AuthToken, ",")[0])
|
||||||
|
}
|
||||||
|
return u.String(), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
base "code.gitea.io/gitea/modules/migration"
|
base "code.gitea.io/gitea/modules/migration"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGitHubDownloadRepo(t *testing.T) {
|
func TestGitHubDownloadRepo(t *testing.T) {
|
||||||
@@ -429,3 +430,36 @@ func TestGitHubDownloadRepo(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}, reviews)
|
}, reviews)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGithubMultiToken(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
token string
|
||||||
|
expectedCloneURL string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Single Token",
|
||||||
|
token: "single_token",
|
||||||
|
expectedCloneURL: "https://oauth2:single_token@github.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Multi Token",
|
||||||
|
token: "token1,token2",
|
||||||
|
expectedCloneURL: "https://oauth2:token1@github.com",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
factory := GithubDownloaderV3Factory{}
|
||||||
|
|
||||||
|
for _, tC := range testCases {
|
||||||
|
t.Run(tC.desc, func(t *testing.T) {
|
||||||
|
opts := base.MigrateOptions{CloneAddr: "https://github.com/go-gitea/gitea", AuthToken: tC.token}
|
||||||
|
client, err := factory.New(context.Background(), opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cloneURL, err := client.FormatCloneURL(opts, "https://github.com")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tC.expectedCloneURL, cloneURL)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ func createOrUpdateConfigFile(ctx context.Context, repo *repo_model.Repository,
|
|||||||
"Initialize Cargo Config",
|
"Initialize Cargo Config",
|
||||||
func(t *files_service.TemporaryUploadRepository) error {
|
func(t *files_service.TemporaryUploadRepository) error {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
err := json.NewEncoder(&b).Encode(BuildConfig(owner, setting.Service.RequireSignInView || owner.Visibility != structs.VisibleTypePublic || repo.IsPrivate))
|
err := json.NewEncoder(&b).Encode(BuildConfig(owner, setting.Service.RequireSignInViewStrict || owner.Visibility != structs.VisibleTypePublic || repo.IsPrivate))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -408,7 +408,6 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []
|
|||||||
files = append(files, f)
|
files = append(files, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
packageVersion := fmt.Sprintf("%s-%s", pd.FileMetadata.Version, pd.FileMetadata.Release)
|
|
||||||
packages = append(packages, &Package{
|
packages = append(packages, &Package{
|
||||||
Type: "rpm",
|
Type: "rpm",
|
||||||
Name: pd.Package.Name,
|
Name: pd.Package.Name,
|
||||||
@@ -437,7 +436,7 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []
|
|||||||
Archive: pd.FileMetadata.ArchiveSize,
|
Archive: pd.FileMetadata.ArchiveSize,
|
||||||
},
|
},
|
||||||
Location: Location{
|
Location: Location{
|
||||||
Href: fmt.Sprintf("package/%s/%s/%s/%s-%s.%s.rpm", pd.Package.Name, packageVersion, pd.FileMetadata.Architecture, pd.Package.Name, packageVersion, pd.FileMetadata.Architecture),
|
Href: fmt.Sprintf("package/%s/%s/%s/%s-%s.%s.rpm", pd.Package.Name, pd.Version.Version, pd.FileMetadata.Architecture, pd.Package.Name, pd.Version.Version, pd.FileMetadata.Architecture),
|
||||||
},
|
},
|
||||||
Format: Format{
|
Format: Format{
|
||||||
License: pd.VersionMetadata.License,
|
License: pd.VersionMetadata.License,
|
||||||
|
|||||||
@@ -189,7 +189,15 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U
|
|||||||
}
|
}
|
||||||
defer releaser()
|
defer releaser()
|
||||||
defer func() {
|
defer func() {
|
||||||
go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false, "", "")
|
go AddTestPullRequestTask(TestPullRequestOptions{
|
||||||
|
RepoID: pr.BaseRepo.ID,
|
||||||
|
Doer: doer,
|
||||||
|
Branch: pr.BaseBranch,
|
||||||
|
IsSync: false,
|
||||||
|
IsForcePush: false,
|
||||||
|
OldCommitID: "",
|
||||||
|
NewCommitID: "",
|
||||||
|
})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
_, err = doMergeAndPush(ctx, pr, doer, mergeStyle, expectedHeadCommitID, message, repo_module.PushTriggerPRMergeToBase)
|
_, err = doMergeAndPush(ctx, pr, doer, mergeStyle, expectedHeadCommitID, message, repo_module.PushTriggerPRMergeToBase)
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !pr.IsWorkInProgress(ctx) {
|
if !pr.IsWorkInProgress(ctx) {
|
||||||
reviewNotifiers, err = issue_service.PullRequestCodeOwnersReview(ctx, issue, pr)
|
reviewNotifiers, err = issue_service.PullRequestCodeOwnersReview(ctx, pr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -349,19 +349,29 @@ func checkForInvalidation(ctx context.Context, requests issues_model.PullRequest
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TestPullRequestOptions struct {
|
||||||
|
RepoID int64
|
||||||
|
Doer *user_model.User
|
||||||
|
Branch string
|
||||||
|
IsSync bool // True means it's a pull request synchronization, false means it's triggered for pull request merging or updating
|
||||||
|
IsForcePush bool
|
||||||
|
OldCommitID string
|
||||||
|
NewCommitID string
|
||||||
|
}
|
||||||
|
|
||||||
// AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch,
|
// AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch,
|
||||||
// and generate new patch for testing as needed.
|
// and generate new patch for testing as needed.
|
||||||
func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string, isSync bool, oldCommitID, newCommitID string) {
|
func AddTestPullRequestTask(opts TestPullRequestOptions) {
|
||||||
log.Trace("AddTestPullRequestTask [head_repo_id: %d, head_branch: %s]: finding pull requests", repoID, branch)
|
log.Trace("AddTestPullRequestTask [head_repo_id: %d, head_branch: %s]: finding pull requests", opts.RepoID, opts.Branch)
|
||||||
graceful.GetManager().RunWithShutdownContext(func(ctx context.Context) {
|
graceful.GetManager().RunWithShutdownContext(func(ctx context.Context) {
|
||||||
// There is no sensible way to shut this down ":-("
|
// There is no sensible way to shut this down ":-("
|
||||||
// If you don't let it run all the way then you will lose data
|
// If you don't let it run all the way then you will lose data
|
||||||
// TODO: graceful: AddTestPullRequestTask needs to become a queue!
|
// TODO: graceful: AddTestPullRequestTask needs to become a queue!
|
||||||
|
|
||||||
// GetUnmergedPullRequestsByHeadInfo() only return open and unmerged PR.
|
// GetUnmergedPullRequestsByHeadInfo() only return open and unmerged PR.
|
||||||
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repoID, branch)
|
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, opts.RepoID, opts.Branch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Find pull requests [head_repo_id: %d, head_branch: %s]: %v", repoID, branch, err)
|
log.Error("Find pull requests [head_repo_id: %d, head_branch: %s]: %v", opts.RepoID, opts.Branch, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,25 +387,24 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
AddToTaskQueue(ctx, pr)
|
AddToTaskQueue(ctx, pr)
|
||||||
comment, err := CreatePushPullComment(ctx, doer, pr, oldCommitID, newCommitID)
|
comment, err := CreatePushPullComment(ctx, opts.Doer, pr, opts.OldCommitID, opts.NewCommitID)
|
||||||
if err == nil && comment != nil {
|
if err == nil && comment != nil {
|
||||||
notify_service.PullRequestPushCommits(ctx, doer, pr, comment)
|
notify_service.PullRequestPushCommits(ctx, opts.Doer, pr, comment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isSync {
|
if opts.IsSync {
|
||||||
requests := issues_model.PullRequestList(prs)
|
if err = issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil {
|
||||||
if err = requests.LoadAttributes(ctx); err != nil {
|
|
||||||
log.Error("PullRequestList.LoadAttributes: %v", err)
|
log.Error("PullRequestList.LoadAttributes: %v", err)
|
||||||
}
|
}
|
||||||
if invalidationErr := checkForInvalidation(ctx, requests, repoID, doer, branch); invalidationErr != nil {
|
if invalidationErr := checkForInvalidation(ctx, prs, opts.RepoID, opts.Doer, opts.Branch); invalidationErr != nil {
|
||||||
log.Error("checkForInvalidation: %v", invalidationErr)
|
log.Error("checkForInvalidation: %v", invalidationErr)
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, pr := range prs {
|
for _, pr := range prs {
|
||||||
objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName)
|
objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName)
|
||||||
if newCommitID != "" && newCommitID != objectFormat.EmptyObjectID().String() {
|
if opts.NewCommitID != "" && opts.NewCommitID != objectFormat.EmptyObjectID().String() {
|
||||||
changed, err := checkIfPRContentChanged(ctx, pr, oldCommitID, newCommitID)
|
changed, err := checkIfPRContentChanged(ctx, pr, opts.OldCommitID, opts.NewCommitID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("checkIfPRContentChanged: %v", err)
|
log.Error("checkIfPRContentChanged: %v", err)
|
||||||
}
|
}
|
||||||
@@ -411,12 +420,12 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string,
|
|||||||
log.Error("GetFirstMatchProtectedBranchRule: %v", err)
|
log.Error("GetFirstMatchProtectedBranchRule: %v", err)
|
||||||
}
|
}
|
||||||
if pb != nil && pb.DismissStaleApprovals {
|
if pb != nil && pb.DismissStaleApprovals {
|
||||||
if err := DismissApprovalReviews(ctx, doer, pr); err != nil {
|
if err := DismissApprovalReviews(ctx, opts.Doer, pr); err != nil {
|
||||||
log.Error("DismissApprovalReviews: %v", err)
|
log.Error("DismissApprovalReviews: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := issues_model.MarkReviewsAsNotStale(ctx, pr.IssueID, newCommitID); err != nil {
|
if err := issues_model.MarkReviewsAsNotStale(ctx, pr.IssueID, opts.NewCommitID); err != nil {
|
||||||
log.Error("MarkReviewsAsNotStale: %v", err)
|
log.Error("MarkReviewsAsNotStale: %v", err)
|
||||||
}
|
}
|
||||||
divergence, err := GetDiverging(ctx, pr)
|
divergence, err := GetDiverging(ctx, pr)
|
||||||
@@ -430,15 +439,25 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notify_service.PullRequestSynchronized(ctx, doer, pr)
|
if !pr.IsWorkInProgress(ctx) {
|
||||||
|
reviewNotifiers, err := issue_service.PullRequestCodeOwnersReview(ctx, pr)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("PullRequestCodeOwnersReview: %v", err)
|
||||||
|
}
|
||||||
|
if len(reviewNotifiers) > 0 {
|
||||||
|
issue_service.ReviewRequestNotify(ctx, pr.Issue, opts.Doer, reviewNotifiers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notify_service.PullRequestSynchronized(ctx, opts.Doer, pr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Trace("AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch)
|
log.Trace("AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests", opts.RepoID, opts.Branch)
|
||||||
prs, err = issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, repoID, branch)
|
prs, err = issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, opts.RepoID, opts.Branch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Find pull requests [base_repo_id: %d, base_branch: %s]: %v", repoID, branch, err)
|
log.Error("Find pull requests [base_repo_id: %d, base_branch: %s]: %v", opts.RepoID, opts.Branch, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, pr := range prs {
|
for _, pr := range prs {
|
||||||
|
|||||||
@@ -42,7 +42,15 @@ func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.
|
|||||||
|
|
||||||
if rebase {
|
if rebase {
|
||||||
defer func() {
|
defer func() {
|
||||||
go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false, "", "")
|
go AddTestPullRequestTask(TestPullRequestOptions{
|
||||||
|
RepoID: pr.BaseRepo.ID,
|
||||||
|
Doer: doer,
|
||||||
|
Branch: pr.BaseBranch,
|
||||||
|
IsSync: false,
|
||||||
|
IsForcePush: false,
|
||||||
|
OldCommitID: "",
|
||||||
|
NewCommitID: "",
|
||||||
|
})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return updateHeadByRebaseOnToBase(ctx, pr, doer)
|
return updateHeadByRebaseOnToBase(ctx, pr, doer)
|
||||||
@@ -83,7 +91,15 @@ func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.
|
|||||||
_, err = doMergeAndPush(ctx, reversePR, doer, repo_model.MergeStyleMerge, "", message, repository.PushTriggerPRUpdateWithBase)
|
_, err = doMergeAndPush(ctx, reversePR, doer, repo_model.MergeStyleMerge, "", message, repository.PushTriggerPRUpdateWithBase)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
go AddTestPullRequestTask(doer, reversePR.HeadRepo.ID, reversePR.HeadBranch, false, "", "")
|
go AddTestPullRequestTask(TestPullRequestOptions{
|
||||||
|
RepoID: reversePR.HeadRepo.ID,
|
||||||
|
Doer: doer,
|
||||||
|
Branch: reversePR.HeadBranch,
|
||||||
|
IsSync: false,
|
||||||
|
IsForcePush: false,
|
||||||
|
OldCommitID: "",
|
||||||
|
NewCommitID: "",
|
||||||
|
})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -170,7 +170,6 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
|
|||||||
branch := opts.RefFullName.BranchName()
|
branch := opts.RefFullName.BranchName()
|
||||||
if !opts.IsDelRef() {
|
if !opts.IsDelRef() {
|
||||||
log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
|
log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
|
||||||
go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true, opts.OldCommitID, opts.NewCommitID)
|
|
||||||
|
|
||||||
newCommit, err := gitRepo.GetCommit(opts.NewCommitID)
|
newCommit, err := gitRepo.GetCommit(opts.NewCommitID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -212,6 +211,17 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
|
|||||||
log.Error("IsForcePush %s:%s failed: %v", repo.FullName(), branch, err)
|
log.Error("IsForcePush %s:%s failed: %v", repo.FullName(), branch, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only update branch can trigger pull request task because the pull request hasn't been created yet when creaing a branch
|
||||||
|
go pull_service.AddTestPullRequestTask(pull_service.TestPullRequestOptions{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
Doer: pusher,
|
||||||
|
Branch: branch,
|
||||||
|
IsSync: true,
|
||||||
|
IsForcePush: isForcePush,
|
||||||
|
OldCommitID: opts.OldCommitID,
|
||||||
|
NewCommitID: opts.NewCommitID,
|
||||||
|
})
|
||||||
|
|
||||||
if isForcePush {
|
if isForcePush {
|
||||||
log.Trace("Push %s is a force push", opts.NewCommitID)
|
log.Trace("Push %s is a force push", opts.NewCommitID)
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
webhook_model "code.gitea.io/gitea/models/webhook"
|
webhook_model "code.gitea.io/gitea/models/webhook"
|
||||||
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
@@ -101,6 +102,13 @@ var (
|
|||||||
redColor = color("ff3232")
|
redColor = color("ff3232")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// https://discord.com/developers/docs/resources/message#embed-object-embed-limits
|
||||||
|
// Discord has some limits in place for the embeds.
|
||||||
|
// According to some tests, there is no consistent limit for different character sets.
|
||||||
|
// For example: 4096 ASCII letters are allowed, but only 2490 emoji characters are allowed.
|
||||||
|
// To keep it simple, we currently truncate at 2000.
|
||||||
|
const discordDescriptionCharactersLimit = 2000
|
||||||
|
|
||||||
type discordConvertor struct {
|
type discordConvertor struct {
|
||||||
Username string
|
Username string
|
||||||
AvatarURL string
|
AvatarURL string
|
||||||
@@ -307,7 +315,7 @@ func (d discordConvertor) createPayload(s *api.User, title, text, url string, co
|
|||||||
Embeds: []DiscordEmbed{
|
Embeds: []DiscordEmbed{
|
||||||
{
|
{
|
||||||
Title: title,
|
Title: title,
|
||||||
Description: text,
|
Description: base.TruncateString(text, discordDescriptionCharactersLimit),
|
||||||
URL: url,
|
URL: url,
|
||||||
Color: color,
|
Color: color,
|
||||||
Author: DiscordEmbedAuthor{
|
Author: DiscordEmbedAuthor{
|
||||||
|
|||||||
@@ -148,7 +148,7 @@
|
|||||||
<dt>{{ctx.Locale.Tr "admin.config.enable_openid_signin"}}</dt>
|
<dt>{{ctx.Locale.Tr "admin.config.enable_openid_signin"}}</dt>
|
||||||
<dd>{{svg (Iif .Service.EnableOpenIDSignIn "octicon-check" "octicon-x")}}</dd>
|
<dd>{{svg (Iif .Service.EnableOpenIDSignIn "octicon-check" "octicon-x")}}</dd>
|
||||||
<dt>{{ctx.Locale.Tr "admin.config.require_sign_in_view"}}</dt>
|
<dt>{{ctx.Locale.Tr "admin.config.require_sign_in_view"}}</dt>
|
||||||
<dd>{{svg (Iif .Service.RequireSignInView "octicon-check" "octicon-x")}}</dd>
|
<dd>{{svg (Iif .Service.RequireSignInViewStrict "octicon-check" "octicon-x")}}</dd>
|
||||||
<dt>{{ctx.Locale.Tr "admin.config.mail_notify"}}</dt>
|
<dt>{{ctx.Locale.Tr "admin.config.mail_notify"}}</dt>
|
||||||
<dd>{{svg (Iif .Service.EnableNotifyMail "octicon-check" "octicon-x")}}</dd>
|
<dd>{{svg (Iif .Service.EnableNotifyMail "octicon-check" "octicon-x")}}</dd>
|
||||||
<dt>{{ctx.Locale.Tr "admin.config.enable_captcha"}}</dt>
|
<dt>{{ctx.Locale.Tr "admin.config.enable_captcha"}}</dt>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{{$canWriteProject := and .CanWriteProjects (or (not .Repository) (not .Repository.IsArchived))}}
|
{{$canWriteProject := and .CanWriteProjects (or (not .Repository) (not .Repository.IsArchived))}}
|
||||||
|
|
||||||
<div class="ui container tw-max-w-full">
|
<div class="ui container tw-max-w-full">
|
||||||
<div class="tw-flex tw-justify-between tw-items-center tw-mb-4 tw-gap-3">
|
<div class="flex-text-block tw-flex-wrap tw-mb-4">
|
||||||
<h2 class="tw-mb-0 tw-flex-1 tw-break-anywhere">{{.Project.Title}}</h2>
|
<h2 class="tw-mb-0">{{.Project.Title}}</h2>
|
||||||
<div class="project-toolbar-right">
|
<div class="tw-flex-1"></div>
|
||||||
<div class="ui secondary filter menu labels">
|
<div class="ui secondary menu tw-m-0">
|
||||||
{{$queryLink := QueryBuild "?" "labels" .SelectLabels "assignee" $.AssigneeID "archived_labels" (Iif $.ShowArchivedLabels "true")}}
|
{{$queryLink := QueryBuild "?" "labels" .SelectLabels "assignee" $.AssigneeID "archived_labels" (Iif $.ShowArchivedLabels "true")}}
|
||||||
|
|
||||||
{{template "repo/issue/filter_item_label" dict "Labels" .Labels "QueryLink" $queryLink "SupportArchivedLabel" true}}
|
{{template "repo/issue/filter_item_label" dict "Labels" .Labels "QueryLink" $queryLink "SupportArchivedLabel" true}}
|
||||||
@@ -19,7 +19,6 @@
|
|||||||
"TextNegativeOne" (ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee")
|
"TextNegativeOne" (ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee")
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{{if $canWriteProject}}
|
{{if $canWriteProject}}
|
||||||
<div class="ui compact mini menu">
|
<div class="ui compact mini menu">
|
||||||
<a class="item" href="{{.Link}}/edit?redirect=project">
|
<a class="item" href="{{.Link}}/edit?redirect=project">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<button class="ui primary button js-btn-clone-panel">
|
<button class="ui primary button js-btn-clone-panel">
|
||||||
{{svg "octicon-code" 16}}
|
{{svg "octicon-code" 16}}
|
||||||
<span>Code</span>
|
<span>{{ctx.Locale.Tr "repo.code"}}</span>
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
</button>
|
</button>
|
||||||
<div class="clone-panel-popup tippy-target">
|
<div class="clone-panel-popup tippy-target">
|
||||||
|
|||||||
@@ -10,12 +10,7 @@
|
|||||||
<div class="ui attached segment">
|
<div class="ui attached segment">
|
||||||
{{template "base/alert" .}}
|
{{template "base/alert" .}}
|
||||||
{{template "repo/create_helper" .}}
|
{{template "repo/create_helper" .}}
|
||||||
|
<div id="create-repo-error-message" class="ui negative message tw-text-center tw-hidden"></div>
|
||||||
{{if not .CanCreateRepo}}
|
|
||||||
<div class="ui negative message">
|
|
||||||
<p>{{ctx.Locale.TrN .MaxCreationLimit "repo.form.reach_limit_of_creation_1" "repo.form.reach_limit_of_creation_n" .MaxCreationLimit}}</p>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
<div class="inline required field {{if .Err_Owner}}error{{end}}">
|
<div class="inline required field {{if .Err_Owner}}error{{end}}">
|
||||||
<label>{{ctx.Locale.Tr "repo.owner"}}</label>
|
<label>{{ctx.Locale.Tr "repo.owner"}}</label>
|
||||||
<div class="ui selection owner dropdown">
|
<div class="ui selection owner dropdown">
|
||||||
@@ -26,7 +21,11 @@
|
|||||||
</span>
|
</span>
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<div class="item truncated-item-container" data-value="{{.SignedUser.ID}}" title="{{.SignedUser.Name}}">
|
<div class="item truncated-item-container" data-value="{{.SignedUser.ID}}" title="{{.SignedUser.Name}}"
|
||||||
|
{{if not .CanCreateRepo}}
|
||||||
|
data-create-repo-disallowed-prompt="{{ctx.Locale.TrN .MaxCreationLimit "repo.form.reach_limit_of_creation_1" "repo.form.reach_limit_of_creation_n" .MaxCreationLimit}}"
|
||||||
|
{{end}}
|
||||||
|
>
|
||||||
{{ctx.AvatarUtils.Avatar .SignedUser 28 "mini"}}
|
{{ctx.AvatarUtils.Avatar .SignedUser 28 "mini"}}
|
||||||
<span class="truncated-item-name">{{.SignedUser.ShortName 40}}</span>
|
<span class="truncated-item-name">{{.SignedUser.ShortName 40}}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -209,7 +208,7 @@
|
|||||||
<br>
|
<br>
|
||||||
<div class="inline field">
|
<div class="inline field">
|
||||||
<label></label>
|
<label></label>
|
||||||
<button class="ui primary button{{if not .CanCreateRepo}} disabled{{end}}">
|
<button class="ui primary button">
|
||||||
{{ctx.Locale.Tr "repo.create_repo"}}
|
{{ctx.Locale.Tr "repo.create_repo"}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div class="flex-item">
|
<div class="flex-item">
|
||||||
<div class="flex-item-main">
|
<div class="flex-item-main">
|
||||||
<div class="flex-item-title">
|
<div class="flex-item-title">
|
||||||
<a class="item muted" href="{{.Link}}/releases">
|
<a class="item muted" href="{{.RepoLink}}/releases">
|
||||||
{{ctx.Locale.Tr "repo.releases"}}
|
{{ctx.Locale.Tr "repo.releases"}}
|
||||||
<span class="ui small label">{{.NumReleases}}</span>
|
<span class="ui small label">{{.NumReleases}}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
{{if $.Page.LinkedPRs}}
|
{{if $.Page.LinkedPRs}}
|
||||||
{{range index $.Page.LinkedPRs .ID}}
|
{{range index $.Page.LinkedPRs .ID}}
|
||||||
<div class="meta tw-my-1">
|
<div class="meta tw-my-1">
|
||||||
<a href="{{$.Issue.Repo.Link}}/pulls/{{.Index}}">
|
<a href="{{.Repo.Link}}/pulls/{{.Index}}">
|
||||||
<span class="tw-m-0 text {{if .PullRequest.HasMerged}}purple{{else if .IsClosed}}red{{else}}green{{end}}">{{svg "octicon-git-merge" 16 "tw-mr-1 tw-align-middle"}}</span>
|
<span class="tw-m-0 text {{if .PullRequest.HasMerged}}purple{{else if .IsClosed}}red{{else}}green{{end}}">{{svg "octicon-git-merge" 16 "tw-mr-1 tw-align-middle"}}</span>
|
||||||
<span class="tw-align-middle">{{.Title}} <span class="text light grey">#{{.Index}}</span></span>
|
<span class="tw-align-middle">{{.Title}} <span class="text light grey">#{{.Index}}</span></span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -14,9 +14,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<div class="list-header">
|
<div class="list-header flex-text-block">
|
||||||
{{template "repo/issue/navbar" .}}
|
|
||||||
{{template "repo/issue/search" .}}
|
{{template "repo/issue/search" .}}
|
||||||
|
<a class="ui small button" href="{{.RepoLink}}/labels">{{ctx.Locale.Tr "repo.labels"}}</a>
|
||||||
|
<a class="ui small button" href="{{.RepoLink}}/milestones">{{ctx.Locale.Tr "repo.milestones"}}</a>
|
||||||
{{if not .Repository.IsArchived}}
|
{{if not .Repository.IsArchived}}
|
||||||
{{if .PageIsIssueList}}
|
{{if .PageIsIssueList}}
|
||||||
<a class="ui small primary button issue-list-new" href="{{.RepoLink}}/issues/new{{if .NewIssueChooseTemplate}}/choose{{end}}">{{ctx.Locale.Tr "repo.issues.new"}}</a>
|
<a class="ui small primary button issue-list-new" href="{{.RepoLink}}/issues/new{{if .NewIssueChooseTemplate}}/choose{{end}}">{{ctx.Locale.Tr "repo.issues.new"}}</a>
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
{{$canReadCode := $.Permission.CanRead ctx.Consts.RepoUnitTypeCode}}
|
||||||
|
|
||||||
<div class="ui fluid vertical menu">
|
<div class="ui fluid vertical menu">
|
||||||
<a class="{{if .PageIsPulse}}active {{end}}item" href="{{.RepoLink}}/activity">
|
<a class="{{if .PageIsPulse}}active {{end}}item" href="{{.RepoLink}}/activity">
|
||||||
{{ctx.Locale.Tr "repo.activity.navbar.pulse"}}
|
{{ctx.Locale.Tr "repo.activity.navbar.pulse"}}
|
||||||
</a>
|
</a>
|
||||||
|
{{if $canReadCode}}
|
||||||
<a class="{{if .PageIsContributors}}active {{end}}item" href="{{.RepoLink}}/activity/contributors">
|
<a class="{{if .PageIsContributors}}active {{end}}item" href="{{.RepoLink}}/activity/contributors">
|
||||||
{{ctx.Locale.Tr "repo.activity.navbar.contributors"}}
|
{{ctx.Locale.Tr "repo.activity.navbar.contributors"}}
|
||||||
</a>
|
</a>
|
||||||
@@ -11,4 +14,5 @@
|
|||||||
<a class="{{if .PageIsRecentCommits}}active{{end}} item" href="{{.RepoLink}}/activity/recent-commits">
|
<a class="{{if .PageIsRecentCommits}}active{{end}} item" href="{{.RepoLink}}/activity/recent-commits">
|
||||||
{{ctx.Locale.Tr "repo.activity.navbar.recent_commits"}}
|
{{ctx.Locale.Tr "repo.activity.navbar.recent_commits"}}
|
||||||
</a>
|
</a>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
<div role="main" aria-label="{{.Title}}" class="page-content repository projects view-project">
|
<div role="main" aria-label="{{.Title}}" class="page-content repository projects view-project">
|
||||||
{{template "repo/header" .}}
|
{{template "repo/header" .}}
|
||||||
<div class="ui container padded">
|
<div class="ui container padded">
|
||||||
<div class="tw-flex tw-justify-between tw-items-center tw-mb-4">
|
<div class="flex-text-block tw-justify-end tw-mb-4">
|
||||||
{{template "repo/issue/navbar" .}}
|
<a class="ui small button" href="{{.RepoLink}}/labels">{{ctx.Locale.Tr "repo.labels"}}</a>
|
||||||
|
<a class="ui small button" href="{{.RepoLink}}/milestones">{{ctx.Locale.Tr "repo.milestones"}}</a>
|
||||||
<a class="ui small primary button" href="{{.RepoLink}}/issues/new/choose?project={{.Project.ID}}">{{ctx.Locale.Tr "repo.issues.new"}}</a>
|
<a class="ui small primary button" href="{{.RepoLink}}/issues/new/choose?project={{.Project.ID}}">{{ctx.Locale.Tr "repo.issues.new"}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<div class="ui stackable grid">
|
<div class="ui stackable grid">
|
||||||
<div class="ui eight wide column">
|
<div class="ui eight wide column">
|
||||||
<div class="ui header">
|
<div class="ui header">
|
||||||
<a class="file-revisions-btn ui basic button" title="{{ctx.Locale.Tr "repo.wiki.back_to_wiki"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}"><span>{{.revision}}</span> {{svg "octicon-home"}}</a>
|
<a class="file-revisions-btn ui basic button" title="{{ctx.Locale.Tr "repo.wiki.back_to_wiki"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}">{{if .revision}}<span>{{.revision}}</span> {{end}}{{svg "octicon-home"}}</a>
|
||||||
{{$title}}
|
{{$title}}
|
||||||
<div class="ui sub header tw-break-anywhere">
|
<div class="ui sub header tw-break-anywhere">
|
||||||
{{$timeSince := DateUtils.TimeSince .Author.When}}
|
{{$timeSince := DateUtils.TimeSince .Author.When}}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<div class="ui dividing header">
|
<div class="ui dividing header">
|
||||||
<div class="flex-text-block tw-flex-wrap tw-justify-end">
|
<div class="flex-text-block tw-flex-wrap tw-justify-end">
|
||||||
<div class="flex-text-block tw-flex-1 tw-min-w-[300px]">
|
<div class="flex-text-block tw-flex-1 tw-min-w-[300px]">
|
||||||
<a class="file-revisions-btn ui basic button" title="{{ctx.Locale.Tr "repo.wiki.file_revision"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_revision" ><span>{{.CommitCount}}</span> {{svg "octicon-history"}}</a>
|
<a class="file-revisions-btn ui basic button" title="{{ctx.Locale.Tr "repo.wiki.file_revision"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_revision" >{{if .CommitCount}}<span>{{.CommitCount}}</span> {{end}}{{svg "octicon-history"}}</a>
|
||||||
<div class="tw-flex-1 gt-ellipsis">
|
<div class="tw-flex-1 gt-ellipsis">
|
||||||
{{$title}}
|
{{$title}}
|
||||||
<div class="ui sub header gt-ellipsis">
|
<div class="ui sub header gt-ellipsis">
|
||||||
|
|||||||
13
templates/swagger/v1_json.tmpl
generated
13
templates/swagger/v1_json.tmpl
generated
@@ -19616,7 +19616,18 @@
|
|||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"x-go-name": "Scopes"
|
"x-go-name": "Scopes",
|
||||||
|
"example": [
|
||||||
|
"all",
|
||||||
|
"read:activitypub",
|
||||||
|
"read:issue",
|
||||||
|
"write:misc",
|
||||||
|
"read:notification",
|
||||||
|
"read:organization",
|
||||||
|
"read:package",
|
||||||
|
"read:repository",
|
||||||
|
"read:user"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -24,7 +26,9 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
pull_service "code.gitea.io/gitea/services/pull"
|
pull_service "code.gitea.io/gitea/services/pull"
|
||||||
release_service "code.gitea.io/gitea/services/release"
|
release_service "code.gitea.io/gitea/services/release"
|
||||||
repo_service "code.gitea.io/gitea/services/repository"
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
@@ -451,3 +455,109 @@ func TestCreateDeleteRefEvent(t *testing.T) {
|
|||||||
assert.NotNil(t, run)
|
assert.NotNil(t, run)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClosePullRequestWithPath(t *testing.T) {
|
||||||
|
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||||
|
// user2 is the owner of the base repo
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
user2Token := getTokenForLoggedInUser(t, loginUser(t, user2.Name), auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||||
|
// user4 is the owner of the fork repo
|
||||||
|
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||||
|
user4Token := getTokenForLoggedInUser(t, loginUser(t, user4.Name), auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||||
|
|
||||||
|
// create the base repo
|
||||||
|
req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{
|
||||||
|
Name: "close-pull-request-with-path",
|
||||||
|
Private: false,
|
||||||
|
Readme: "Default",
|
||||||
|
AutoInit: true,
|
||||||
|
DefaultBranch: "main",
|
||||||
|
}).AddTokenAuth(user2Token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusCreated)
|
||||||
|
var apiBaseRepo api.Repository
|
||||||
|
DecodeJSON(t, resp, &apiBaseRepo)
|
||||||
|
baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID})
|
||||||
|
user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
// init the workflow
|
||||||
|
wfTreePath := ".gitea/workflows/pull.yml"
|
||||||
|
wfFileContent := `name: Pull Request
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- closed
|
||||||
|
paths:
|
||||||
|
- 'app/**'
|
||||||
|
jobs:
|
||||||
|
echo:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo 'Hello World'
|
||||||
|
`
|
||||||
|
|
||||||
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", baseRepo.OwnerName, baseRepo.Name, wfTreePath), &api.CreateFileOptions{
|
||||||
|
FileOptions: api.FileOptions{
|
||||||
|
BranchName: baseRepo.DefaultBranch,
|
||||||
|
Message: "create " + wfTreePath,
|
||||||
|
Author: api.Identity{
|
||||||
|
Name: user2.Name,
|
||||||
|
Email: user2.Email,
|
||||||
|
},
|
||||||
|
Committer: api.Identity{
|
||||||
|
Name: user2.Name,
|
||||||
|
Email: user2.Email,
|
||||||
|
},
|
||||||
|
Dates: api.CommitDateOptions{
|
||||||
|
Author: time.Now(),
|
||||||
|
Committer: time.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ContentBase64: base64.StdEncoding.EncodeToString([]byte(wfFileContent)),
|
||||||
|
}).AddTokenAuth(user2Token)
|
||||||
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
|
// user4 forks the repo
|
||||||
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/forks", baseRepo.OwnerName, baseRepo.Name),
|
||||||
|
&api.CreateForkOption{
|
||||||
|
Name: util.ToPointer("close-pull-request-with-path-fork"),
|
||||||
|
}).AddTokenAuth(user4Token)
|
||||||
|
resp = MakeRequest(t, req, http.StatusAccepted)
|
||||||
|
var apiForkRepo api.Repository
|
||||||
|
DecodeJSON(t, resp, &apiForkRepo)
|
||||||
|
forkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiForkRepo.ID})
|
||||||
|
user4APICtx := NewAPITestContext(t, user4.Name, forkRepo.Name, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
// user4 creates a pull request to add file "app/main.go"
|
||||||
|
doAPICreateFile(user4APICtx, "app/main.go", &api.CreateFileOptions{
|
||||||
|
FileOptions: api.FileOptions{
|
||||||
|
NewBranchName: "user4/add-main",
|
||||||
|
Message: "create main.go",
|
||||||
|
Author: api.Identity{
|
||||||
|
Name: user4.Name,
|
||||||
|
Email: user4.Email,
|
||||||
|
},
|
||||||
|
Committer: api.Identity{
|
||||||
|
Name: user4.Name,
|
||||||
|
Email: user4.Email,
|
||||||
|
},
|
||||||
|
Dates: api.CommitDateOptions{
|
||||||
|
Author: time.Now(),
|
||||||
|
Committer: time.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ContentBase64: base64.StdEncoding.EncodeToString([]byte("// main.go")),
|
||||||
|
})(t)
|
||||||
|
apiPull, err := doAPICreatePullRequest(user4APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, user4.Name+":user4/add-main")(t)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
doAPIMergePullRequest(user2APICtx, baseRepo.OwnerName, baseRepo.Name, apiPull.Index)(t)
|
||||||
|
|
||||||
|
pullRequest := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID})
|
||||||
|
|
||||||
|
// load and compare ActionRun
|
||||||
|
assert.Equal(t, 1, unittest.GetCount(t, &actions_model.ActionRun{RepoID: baseRepo.ID}))
|
||||||
|
actionRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: baseRepo.ID})
|
||||||
|
assert.Equal(t, actions_module.GithubEventPullRequest, actionRun.TriggerEvent)
|
||||||
|
assert.Equal(t, pullRequest.MergedCommitID, actionRun.CommitSHA)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -148,9 +148,9 @@ func TestAPIOrgEditBadVisibility(t *testing.T) {
|
|||||||
|
|
||||||
func TestAPIOrgDeny(t *testing.T) {
|
func TestAPIOrgDeny(t *testing.T) {
|
||||||
onGiteaRun(t, func(*testing.T, *url.URL) {
|
onGiteaRun(t, func(*testing.T, *url.URL) {
|
||||||
setting.Service.RequireSignInView = true
|
setting.Service.RequireSignInViewStrict = true
|
||||||
defer func() {
|
defer func() {
|
||||||
setting.Service.RequireSignInView = false
|
setting.Service.RequireSignInViewStrict = false
|
||||||
}()
|
}()
|
||||||
|
|
||||||
orgName := "user1_org"
|
orgName := "user1_org"
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ func TestPackageContainer(t *testing.T) {
|
|||||||
AddTokenAuth(anonymousToken)
|
AddTokenAuth(anonymousToken)
|
||||||
MakeRequest(t, req, http.StatusOK)
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
defer test.MockVariableValue(&setting.Service.RequireSignInView, true)()
|
defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)()
|
||||||
|
|
||||||
req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL))
|
req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL))
|
||||||
MakeRequest(t, req, http.StatusUnauthorized)
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -131,11 +132,7 @@ func TestPackageGeneric(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("RequireSignInView", func(t *testing.T) {
|
t.Run("RequireSignInView", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)()
|
||||||
setting.Service.RequireSignInView = true
|
|
||||||
defer func() {
|
|
||||||
setting.Service.RequireSignInView = false
|
|
||||||
}()
|
|
||||||
|
|
||||||
req = NewRequest(t, "GET", url+"/dummy.bin")
|
req = NewRequest(t, "GET", url+"/dummy.bin")
|
||||||
MakeRequest(t, req, http.StatusUnauthorized)
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPackageSwift(t *testing.T) {
|
func TestPackageSwift(t *testing.T) {
|
||||||
@@ -34,6 +35,7 @@ func TestPackageSwift(t *testing.T) {
|
|||||||
packageName := "test_package"
|
packageName := "test_package"
|
||||||
packageID := packageScope + "." + packageName
|
packageID := packageScope + "." + packageName
|
||||||
packageVersion := "1.0.3"
|
packageVersion := "1.0.3"
|
||||||
|
packageVersion2 := "1.0.4"
|
||||||
packageAuthor := "KN4CK3R"
|
packageAuthor := "KN4CK3R"
|
||||||
packageDescription := "Gitea Test Package"
|
packageDescription := "Gitea Test Package"
|
||||||
packageRepositoryURL := "https://gitea.io/gitea/gitea"
|
packageRepositoryURL := "https://gitea.io/gitea/gitea"
|
||||||
@@ -183,6 +185,94 @@ func TestPackageSwift(t *testing.T) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("UploadMultipart", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
uploadPackage := func(t *testing.T, url string, expectedStatus int, sr io.Reader, metadata string) {
|
||||||
|
var body bytes.Buffer
|
||||||
|
mpw := multipart.NewWriter(&body)
|
||||||
|
|
||||||
|
// Read the source archive content
|
||||||
|
sourceContent, err := io.ReadAll(sr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
mpw.WriteField("source-archive", string(sourceContent))
|
||||||
|
|
||||||
|
if metadata != "" {
|
||||||
|
mpw.WriteField("metadata", metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
mpw.Close()
|
||||||
|
|
||||||
|
req := NewRequestWithBody(t, "PUT", url, &body).
|
||||||
|
SetHeader("Content-Type", mpw.FormDataContentType()).
|
||||||
|
SetHeader("Accept", swift_router.AcceptJSON).
|
||||||
|
AddBasicAuth(user.Name)
|
||||||
|
MakeRequest(t, req, expectedStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
createArchive := func(files map[string]string) *bytes.Buffer {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
zw := zip.NewWriter(&buf)
|
||||||
|
for filename, content := range files {
|
||||||
|
w, _ := zw.Create(filename)
|
||||||
|
w.Write([]byte(content))
|
||||||
|
}
|
||||||
|
zw.Close()
|
||||||
|
return &buf
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, packageVersion2)
|
||||||
|
|
||||||
|
req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{}))
|
||||||
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
|
||||||
|
// Test with metadata as form field
|
||||||
|
uploadPackage(
|
||||||
|
t,
|
||||||
|
uploadURL,
|
||||||
|
http.StatusCreated,
|
||||||
|
createArchive(map[string]string{
|
||||||
|
"Package.swift": contentManifest1,
|
||||||
|
"Package@swift-5.6.swift": contentManifest2,
|
||||||
|
}),
|
||||||
|
`{"name":"`+packageName+`","version":"`+packageVersion2+`","description":"`+packageDescription+`","codeRepository":"`+packageRepositoryURL+`","author":{"givenName":"`+packageAuthor+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeSwift)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
require.Len(t, pvs, 2) // ATTENTION: many subtests are unable to run separately, they depend on the results of previous tests
|
||||||
|
thisPackageVersion := pvs[0]
|
||||||
|
pd, err := packages.GetPackageDescriptor(db.DefaultContext, thisPackageVersion)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, pd.SemVer)
|
||||||
|
assert.Equal(t, packageID, pd.Package.Name)
|
||||||
|
assert.Equal(t, packageVersion2, pd.Version.Version)
|
||||||
|
assert.IsType(t, &swift_module.Metadata{}, pd.Metadata)
|
||||||
|
metadata := pd.Metadata.(*swift_module.Metadata)
|
||||||
|
assert.Equal(t, packageDescription, metadata.Description)
|
||||||
|
assert.Len(t, metadata.Manifests, 2)
|
||||||
|
assert.Equal(t, contentManifest1, metadata.Manifests[""].Content)
|
||||||
|
assert.Equal(t, contentManifest2, metadata.Manifests["5.6"].Content)
|
||||||
|
assert.Len(t, pd.VersionProperties, 1)
|
||||||
|
assert.Equal(t, packageRepositoryURL, pd.VersionProperties.GetByName(swift_module.PropertyRepositoryURL))
|
||||||
|
|
||||||
|
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, thisPackageVersion.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, pfs, 1)
|
||||||
|
assert.Equal(t, fmt.Sprintf("%s-%s.zip", packageName, packageVersion2), pfs[0].Name)
|
||||||
|
assert.True(t, pfs[0].IsLead)
|
||||||
|
|
||||||
|
uploadPackage(
|
||||||
|
t,
|
||||||
|
uploadURL,
|
||||||
|
http.StatusConflict,
|
||||||
|
createArchive(map[string]string{
|
||||||
|
"Package.swift": contentManifest1,
|
||||||
|
}),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("Download", func(t *testing.T) {
|
t.Run("Download", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
@@ -211,7 +301,7 @@ func TestPackageSwift(t *testing.T) {
|
|||||||
SetHeader("Accept", swift_router.AcceptJSON)
|
SetHeader("Accept", swift_router.AcceptJSON)
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
versionURL := setting.AppURL + url[1:] + fmt.Sprintf("/%s/%s/%s", packageScope, packageName, packageVersion)
|
versionURL := setting.AppURL + url[1:] + fmt.Sprintf("/%s/%s/%s", packageScope, packageName, packageVersion2)
|
||||||
|
|
||||||
assert.Equal(t, "1", resp.Header().Get("Content-Version"))
|
assert.Equal(t, "1", resp.Header().Get("Content-Version"))
|
||||||
assert.Equal(t, fmt.Sprintf(`<%s>; rel="latest-version"`, versionURL), resp.Header().Get("Link"))
|
assert.Equal(t, fmt.Sprintf(`<%s>; rel="latest-version"`, versionURL), resp.Header().Get("Link"))
|
||||||
@@ -221,9 +311,9 @@ func TestPackageSwift(t *testing.T) {
|
|||||||
var result *swift_router.EnumeratePackageVersionsResponse
|
var result *swift_router.EnumeratePackageVersionsResponse
|
||||||
DecodeJSON(t, resp, &result)
|
DecodeJSON(t, resp, &result)
|
||||||
|
|
||||||
assert.Len(t, result.Releases, 1)
|
assert.Len(t, result.Releases, 2)
|
||||||
assert.Contains(t, result.Releases, packageVersion)
|
assert.Contains(t, result.Releases, packageVersion2)
|
||||||
assert.Equal(t, versionURL, result.Releases[packageVersion].URL)
|
assert.Equal(t, versionURL, result.Releases[packageVersion2].URL)
|
||||||
|
|
||||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s.json", url, packageScope, packageName)).
|
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s.json", url, packageScope, packageName)).
|
||||||
AddBasicAuth(user.Name)
|
AddBasicAuth(user.Name)
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
@@ -18,11 +21,15 @@ import (
|
|||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/services/convert"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
"code.gitea.io/gitea/services/gitdiff"
|
"code.gitea.io/gitea/services/gitdiff"
|
||||||
issue_service "code.gitea.io/gitea/services/issue"
|
issue_service "code.gitea.io/gitea/services/issue"
|
||||||
|
pull_service "code.gitea.io/gitea/services/pull"
|
||||||
|
files_service "code.gitea.io/gitea/services/repository/files"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -425,3 +432,94 @@ func TestAPICommitPullRequest(t *testing.T) {
|
|||||||
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, invalidCommitSHA).AddTokenAuth(ctx.Token)
|
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, invalidCommitSHA).AddTokenAuth(ctx.Token)
|
||||||
ctx.Session.MakeRequest(t, req, http.StatusNotFound)
|
ctx.Session.MakeRequest(t, req, http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIViewPullFilesWithHeadRepoDeleted(t *testing.T) {
|
||||||
|
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||||
|
baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
|
||||||
|
ctx := NewAPITestContext(t, "user1", baseRepo.Name, auth_model.AccessTokenScopeAll)
|
||||||
|
|
||||||
|
doAPIForkRepository(ctx, "user2")(t)
|
||||||
|
|
||||||
|
forkedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ForkID: baseRepo.ID, OwnerName: "user1"})
|
||||||
|
|
||||||
|
// add a new file to the forked repo
|
||||||
|
addFileToForkedResp, err := files_service.ChangeRepoFiles(git.DefaultContext, forkedRepo, user1, &files_service.ChangeRepoFilesOptions{
|
||||||
|
Files: []*files_service.ChangeRepoFile{
|
||||||
|
{
|
||||||
|
Operation: "create",
|
||||||
|
TreePath: "file_1.txt",
|
||||||
|
ContentReader: strings.NewReader("file1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Message: "add file1",
|
||||||
|
OldBranch: "master",
|
||||||
|
NewBranch: "fork-branch-1",
|
||||||
|
Author: &files_service.IdentityOptions{
|
||||||
|
Name: user1.Name,
|
||||||
|
Email: user1.Email,
|
||||||
|
},
|
||||||
|
Committer: &files_service.IdentityOptions{
|
||||||
|
Name: user1.Name,
|
||||||
|
Email: user1.Email,
|
||||||
|
},
|
||||||
|
Dates: &files_service.CommitDateOptions{
|
||||||
|
Author: time.Now(),
|
||||||
|
Committer: time.Now(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, addFileToForkedResp)
|
||||||
|
|
||||||
|
// create Pull
|
||||||
|
pullIssue := &issues_model.Issue{
|
||||||
|
RepoID: baseRepo.ID,
|
||||||
|
Title: "Test pull-request-target-event",
|
||||||
|
PosterID: user1.ID,
|
||||||
|
Poster: user1,
|
||||||
|
IsPull: true,
|
||||||
|
}
|
||||||
|
pullRequest := &issues_model.PullRequest{
|
||||||
|
HeadRepoID: forkedRepo.ID,
|
||||||
|
BaseRepoID: baseRepo.ID,
|
||||||
|
HeadBranch: "fork-branch-1",
|
||||||
|
BaseBranch: "master",
|
||||||
|
HeadRepo: forkedRepo,
|
||||||
|
BaseRepo: baseRepo,
|
||||||
|
Type: issues_model.PullRequestGitea,
|
||||||
|
}
|
||||||
|
|
||||||
|
prOpts := &pull_service.NewPullRequestOptions{Repo: baseRepo, Issue: pullIssue, PullRequest: pullRequest}
|
||||||
|
err = pull_service.NewPullRequest(git.DefaultContext, prOpts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
pr := convert.ToAPIPullRequest(context.Background(), pullRequest, user1)
|
||||||
|
|
||||||
|
ctx = NewAPITestContext(t, "user2", baseRepo.Name, auth_model.AccessTokenScopeAll)
|
||||||
|
doAPIGetPullFiles(ctx, pr, func(t *testing.T, files []*api.ChangedFile) {
|
||||||
|
if assert.Len(t, files, 1) {
|
||||||
|
assert.Equal(t, "file_1.txt", files[0].Filename)
|
||||||
|
assert.Empty(t, files[0].PreviousFilename)
|
||||||
|
assert.Equal(t, 1, files[0].Additions)
|
||||||
|
assert.Equal(t, 1, files[0].Changes)
|
||||||
|
assert.Equal(t, 0, files[0].Deletions)
|
||||||
|
assert.Equal(t, "added", files[0].Status)
|
||||||
|
}
|
||||||
|
})(t)
|
||||||
|
|
||||||
|
// delete the head repository of the pull request
|
||||||
|
forkCtx := NewAPITestContext(t, "user1", forkedRepo.Name, auth_model.AccessTokenScopeAll)
|
||||||
|
doAPIDeleteRepository(forkCtx)(t)
|
||||||
|
|
||||||
|
doAPIGetPullFiles(ctx, pr, func(t *testing.T, files []*api.ChangedFile) {
|
||||||
|
if assert.Len(t, files, 1) {
|
||||||
|
assert.Equal(t, "file_1.txt", files[0].Filename)
|
||||||
|
assert.Empty(t, files[0].PreviousFilename)
|
||||||
|
assert.Equal(t, 1, files[0].Additions)
|
||||||
|
assert.Equal(t, 1, files[0].Changes)
|
||||||
|
assert.Equal(t, 0, files[0].Deletions)
|
||||||
|
assert.Equal(t, "added", files[0].Status)
|
||||||
|
}
|
||||||
|
})(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user