merge main

This commit is contained in:
Kerwin Bryant 2025-03-03 03:14:43 +00:00
commit c14f7a75d6
140 changed files with 1460 additions and 1131 deletions

View File

@ -17,6 +17,7 @@ insert_final_newline = false
[templates/swagger/v1_json.tmpl] [templates/swagger/v1_json.tmpl]
indent_style = space indent_style = space
insert_final_newline = false
[templates/user/auth/oidc_wellknown.tmpl] [templates/user/auth/oidc_wellknown.tmpl]
indent_style = space indent_style = space

View File

@ -336,7 +336,7 @@ module.exports = {
'@typescript-eslint/no-unsafe-unary-minus': [2], '@typescript-eslint/no-unsafe-unary-minus': [2],
'@typescript-eslint/no-unused-expressions': [0], '@typescript-eslint/no-unused-expressions': [0],
'@typescript-eslint/no-unused-vars': [2, {vars: 'all', args: 'all', caughtErrors: 'all', ignoreRestSiblings: false, argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_'}], '@typescript-eslint/no-unused-vars': [2, {vars: 'all', args: 'all', caughtErrors: 'all', ignoreRestSiblings: false, argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_'}],
'@typescript-eslint/no-use-before-define': [0], '@typescript-eslint/no-use-before-define': [2, {functions: false, classes: true, variables: true, allowNamedExports: true, typedefs: false, enums: false, ignoreTypeReferences: true}],
'@typescript-eslint/no-useless-constructor': [0], '@typescript-eslint/no-useless-constructor': [0],
'@typescript-eslint/no-useless-empty-export': [0], '@typescript-eslint/no-useless-empty-export': [0],
'@typescript-eslint/no-wrapper-object-types': [2], '@typescript-eslint/no-wrapper-object-types': [2],
@ -693,7 +693,7 @@ module.exports = {
'no-unused-labels': [2], 'no-unused-labels': [2],
'no-unused-private-class-members': [2], 'no-unused-private-class-members': [2],
'no-unused-vars': [0], // handled by @typescript-eslint/no-unused-vars 'no-unused-vars': [0], // handled by @typescript-eslint/no-unused-vars
'no-use-before-define': [2, {functions: false, classes: true, variables: true, allowNamedExports: true}], 'no-use-before-define': [0], // handled by @typescript-eslint/no-use-before-define
'no-use-extend-native/no-use-extend-native': [2], 'no-use-extend-native/no-use-extend-native': [2],
'no-useless-backreference': [2], 'no-useless-backreference': [2],
'no-useless-call': [2], 'no-useless-call': [2],

10
.github/labeler.yml vendored
View File

@ -41,7 +41,7 @@ modifies/internal:
- ".dockerignore" - ".dockerignore"
- "docker/**" - "docker/**"
- ".editorconfig" - ".editorconfig"
- ".eslintrc.yaml" - ".eslintrc.cjs"
- ".golangci.yml" - ".golangci.yml"
- ".gitpod.yml" - ".gitpod.yml"
- ".markdownlint.yaml" - ".markdownlint.yaml"
@ -49,7 +49,7 @@ modifies/internal:
- "stylelint.config.js" - "stylelint.config.js"
- ".yamllint.yaml" - ".yamllint.yaml"
- ".github/**" - ".github/**"
- ".gitea/" - ".gitea/**"
- ".devcontainer/**" - ".devcontainer/**"
- "build.go" - "build.go"
- "build/**" - "build/**"
@ -73,9 +73,9 @@ modifies/go:
modifies/frontend: modifies/frontend:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- "**/*.js" - "*.js"
- "**/*.ts" - "*.ts"
- "**/*.vue" - "web_src/**"
docs-update-needed: docs-update-needed:
- changed-files: - changed-files:

View File

@ -51,14 +51,16 @@ jobs:
- "options/locale/locale_en-US.ini" - "options/locale/locale_en-US.ini"
frontend: frontend:
- "**/*.js" - "*.js"
- "*.ts"
- "web_src/**" - "web_src/**"
- "tools/*.js"
- "tools/*.ts"
- "assets/emoji.json" - "assets/emoji.json"
- "package.json" - "package.json"
- "package-lock.json" - "package-lock.json"
- "Makefile" - "Makefile"
- ".eslintrc.yaml" - ".eslintrc.cjs"
- "stylelint.config.js"
- ".npmrc" - ".npmrc"
docs: docs:
@ -85,6 +87,7 @@ jobs:
swagger: swagger:
- "templates/swagger/v1_json.tmpl" - "templates/swagger/v1_json.tmpl"
- "templates/swagger/v1_input.json"
- "Makefile" - "Makefile"
- "package.json" - "package.json"
- "package-lock.json" - "package-lock.json"

View File

@ -88,9 +88,9 @@ jobs:
# 1.2 # 1.2
# 1.2.3 # 1.2.3
tags: | tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}} type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{version}}
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
@ -126,9 +126,9 @@ jobs:
# 1.2 # 1.2
# 1.2.3 # 1.2.3
tags: | tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}} type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{version}}
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:

View File

@ -165,10 +165,8 @@ ifdef DEPS_PLAYWRIGHT
endif endif
SWAGGER_SPEC := templates/swagger/v1_json.tmpl SWAGGER_SPEC := templates/swagger/v1_json.tmpl
SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape}}/api/v1"|g SWAGGER_SPEC_INPUT := templates/swagger/v1_input.json
SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape}}/api/v1"|"basePath": "/api/v1"|g
SWAGGER_EXCLUDE := code.gitea.io/sdk SWAGGER_EXCLUDE := code.gitea.io/sdk
SWAGGER_NEWLINE_COMMAND := -e '$$a\'
TEST_MYSQL_HOST ?= mysql:3306 TEST_MYSQL_HOST ?= mysql:3306
TEST_MYSQL_DBNAME ?= testgitea TEST_MYSQL_DBNAME ?= testgitea
@ -271,10 +269,8 @@ endif
.PHONY: generate-swagger .PHONY: generate-swagger
generate-swagger: $(SWAGGER_SPEC) ## generate the swagger spec from code comments generate-swagger: $(SWAGGER_SPEC) ## generate the swagger spec from code comments
$(SWAGGER_SPEC): $(GO_SOURCES_NO_BINDATA) $(SWAGGER_SPEC): $(GO_SOURCES_NO_BINDATA) $(SWAGGER_SPEC_INPUT)
$(GO) run $(SWAGGER_PACKAGE) generate spec -x "$(SWAGGER_EXCLUDE)" -o './$(SWAGGER_SPEC)' $(GO) run $(SWAGGER_PACKAGE) generate spec --exclude "$(SWAGGER_EXCLUDE)" --input "$(SWAGGER_SPEC_INPUT)" --output './$(SWAGGER_SPEC)'
$(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)'
$(SED_INPLACE) $(SWAGGER_NEWLINE_COMMAND) './$(SWAGGER_SPEC)'
.PHONY: swagger-check .PHONY: swagger-check
swagger-check: generate-swagger swagger-check: generate-swagger
@ -287,9 +283,11 @@ swagger-check: generate-swagger
.PHONY: swagger-validate .PHONY: swagger-validate
swagger-validate: ## check if the swagger spec is valid swagger-validate: ## check if the swagger spec is valid
$(SED_INPLACE) '$(SWAGGER_SPEC_S_JSON)' './$(SWAGGER_SPEC)' @# swagger "validate" requires that the "basePath" must start with a slash, but we are using Golang template "{{...}}"
@$(SED_INPLACE) -E -e 's|"basePath":( *)"(.*)"|"basePath":\1"/\2"|g' './$(SWAGGER_SPEC)' # add a prefix slash to basePath
@# FIXME: there are some warnings
$(GO) run $(SWAGGER_PACKAGE) validate './$(SWAGGER_SPEC)' $(GO) run $(SWAGGER_PACKAGE) validate './$(SWAGGER_SPEC)'
$(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)' @$(SED_INPLACE) -E -e 's|"basePath":( *)"/(.*)"|"basePath":\1"\2"|g' './$(SWAGGER_SPEC)' # remove the prefix slash from basePath
.PHONY: checks .PHONY: checks
checks: checks-frontend checks-backend ## run various consistency checks checks: checks-frontend checks-backend ## run various consistency checks
@ -380,6 +378,7 @@ lint-go-gopls: ## lint go files with gopls
.PHONY: lint-editorconfig .PHONY: lint-editorconfig
lint-editorconfig: lint-editorconfig:
@echo "Running editorconfig check..."
@$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) $(EDITORCONFIG_FILES) @$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) $(EDITORCONFIG_FILES)
.PHONY: lint-actions .PHONY: lint-actions
@ -471,7 +470,9 @@ tidy-check: tidy
go-licenses: $(GO_LICENSE_FILE) ## regenerate go licenses go-licenses: $(GO_LICENSE_FILE) ## regenerate go licenses
$(GO_LICENSE_FILE): go.mod go.sum $(GO_LICENSE_FILE): go.mod go.sum
-$(GO) run $(GO_LICENSES_PACKAGE) save . --force --save_path=$(GO_LICENSE_TMP_DIR) 2>/dev/null @rm -rf $(GO_LICENSE_FILE)
$(GO) install $(GO_LICENSES_PACKAGE)
-GOOS=linux CGO_ENABLED=1 go-licenses save . --force --save_path=$(GO_LICENSE_TMP_DIR) 2>/dev/null
$(GO) run build/generate-go-licenses.go $(GO_LICENSE_TMP_DIR) $(GO_LICENSE_FILE) $(GO) run build/generate-go-licenses.go $(GO_LICENSE_TMP_DIR) $(GO_LICENSE_FILE)
@rm -rf $(GO_LICENSE_TMP_DIR) @rm -rf $(GO_LICENSE_TMP_DIR)

View File

@ -54,10 +54,6 @@ func runACME(listenAddr string, m http.Handler) error {
altTLSALPNPort = p altTLSALPNPort = p
} }
// FIXME: this path is not right, it uses "AppWorkPath" incorrectly, and writes the data into "AppWorkPath/https"
// Ideally it should migrate to AppDataPath write to "AppDataPath/https"
certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
magic := certmagic.NewDefault()
// Try to use private CA root if provided, otherwise defaults to system's trust // Try to use private CA root if provided, otherwise defaults to system's trust
var certPool *x509.CertPool var certPool *x509.CertPool
if setting.AcmeCARoot != "" { if setting.AcmeCARoot != "" {
@ -67,7 +63,13 @@ func runACME(listenAddr string, m http.Handler) error {
log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err) log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err)
} }
} }
myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{ // FIXME: this path is not right, it uses "AppWorkPath" incorrectly, and writes the data into "AppWorkPath/https"
// Ideally it should migrate to AppDataPath write to "AppDataPath/https"
// And one more thing, no idea why we should set the global default variables here
// But it seems that the current ACME code needs these global variables to make renew work.
// Otherwise, "renew" will use incorrect storage path
certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
certmagic.DefaultACME = certmagic.ACMEIssuer{
CA: setting.AcmeURL, CA: setting.AcmeURL,
TrustedRoots: certPool, TrustedRoots: certPool,
Email: setting.AcmeEmail, Email: setting.AcmeEmail,
@ -77,8 +79,10 @@ func runACME(listenAddr string, m http.Handler) error {
ListenHost: setting.HTTPAddr, ListenHost: setting.HTTPAddr,
AltTLSALPNPort: altTLSALPNPort, AltTLSALPNPort: altTLSALPNPort,
AltHTTPPort: altHTTPPort, AltHTTPPort: altHTTPPort,
}) }
magic := certmagic.NewDefault()
myACME := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME)
magic.Issuers = []certmagic.Issuer{myACME} magic.Issuers = []certmagic.Issuer{myACME}
// this obtains certificates or renews them if necessary // this obtains certificates or renews them if necessary

10
go.mod
View File

@ -8,7 +8,7 @@ go 1.24
godebug x509negativeserial=1 godebug x509negativeserial=1
require ( require (
code.gitea.io/actions-proto-go v0.4.0 code.gitea.io/actions-proto-go v0.4.1
code.gitea.io/gitea-vet v0.2.3 code.gitea.io/gitea-vet v0.2.3
code.gitea.io/sdk/gitea v0.20.0 code.gitea.io/sdk/gitea v0.20.0
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
@ -24,7 +24,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
github.com/ProtonMail/go-crypto v1.1.5 github.com/ProtonMail/go-crypto v1.1.6
github.com/PuerkitoBio/goquery v1.10.2 github.com/PuerkitoBio/goquery v1.10.2
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3 github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3
github.com/alecthomas/chroma/v2 v2.15.0 github.com/alecthomas/chroma/v2 v2.15.0
@ -117,10 +117,10 @@ require (
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0 github.com/yuin/goldmark-meta v1.1.0
gitlab.com/gitlab-org/api/client-go v0.123.0 gitlab.com/gitlab-org/api/client-go v0.123.0
golang.org/x/crypto v0.33.0 golang.org/x/crypto v0.35.0
golang.org/x/image v0.24.0 golang.org/x/image v0.24.0
golang.org/x/net v0.35.0 golang.org/x/net v0.35.0
golang.org/x/oauth2 v0.26.0 golang.org/x/oauth2 v0.27.0
golang.org/x/sync v0.11.0 golang.org/x/sync v0.11.0
golang.org/x/sys v0.30.0 golang.org/x/sys v0.30.0
golang.org/x/text v0.22.0 golang.org/x/text v0.22.0
@ -318,7 +318,7 @@ replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
replace github.com/nektos/act => gitea.com/gitea/act v0.261.3 replace github.com/nektos/act => gitea.com/gitea/act v0.261.4
// TODO: the only difference is in `PutObject`: the fork doesn't use `NewVerifyingReader(r, sha256.New(), oid, expectedSize)`, need to figure out why // TODO: the only difference is in `PutObject`: the fork doesn't use `NewVerifyingReader(r, sha256.New(), oid, expectedSize)`, need to figure out why
replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-transfer v0.2.0 replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-transfer v0.2.0

19
go.sum
View File

@ -1,7 +1,7 @@
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
code.gitea.io/actions-proto-go v0.4.0 h1:OsPBPhodXuQnsspG1sQ4eRE1PeoZyofd7+i73zCwnsU= code.gitea.io/actions-proto-go v0.4.1 h1:l0EYhjsgpUe/1VABo2eK7zcoNX2W44WOnb0MSLrKfls=
code.gitea.io/actions-proto-go v0.4.0/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas= code.gitea.io/actions-proto-go v0.4.1/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas=
code.gitea.io/gitea-vet v0.2.3 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI= code.gitea.io/gitea-vet v0.2.3 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI=
code.gitea.io/gitea-vet v0.2.3/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= code.gitea.io/gitea-vet v0.2.3/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
code.gitea.io/sdk/gitea v0.20.0 h1:Zm/QDwwZK1awoM4AxdjeAQbxolzx2rIP8dDfmKu+KoU= code.gitea.io/sdk/gitea v0.20.0 h1:Zm/QDwwZK1awoM4AxdjeAQbxolzx2rIP8dDfmKu+KoU=
@ -16,8 +16,8 @@ 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 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg=
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs= 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.4 h1:Tf9eLlvsYFtKcpuxlMvf9yT3g4Hshb2Beqw6C1STuH8=
gitea.com/gitea/act v0.261.3/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok= gitea.com/gitea/act v0.261.4/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/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso= gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso=
@ -71,8 +71,8 @@ github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSC
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8= github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8=
github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU= github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU=
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
@ -831,8 +831,9 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4= golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4=
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
@ -868,8 +869,8 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

View File

@ -454,6 +454,24 @@ func ActivityReadable(user, doer *user_model.User) bool {
doer != nil && (doer.IsAdmin || user.ID == doer.ID) doer != nil && (doer.IsAdmin || user.ID == doer.ID)
} }
func FeedDateCond(opts GetFeedsOptions) builder.Cond {
cond := builder.NewCond()
if opts.Date == "" {
return cond
}
dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation)
if err != nil {
log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err)
} else {
dateHigh := dateLow.Add(86399000000000) // 23h59m59s
cond = cond.And(builder.Gte{"`action`.created_unix": dateLow.Unix()})
cond = cond.And(builder.Lte{"`action`.created_unix": dateHigh.Unix()})
}
return cond
}
func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.Cond, error) { func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.Cond, error) {
cond := builder.NewCond() cond := builder.NewCond()
@ -534,17 +552,7 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.
cond = cond.And(builder.Eq{"is_deleted": false}) cond = cond.And(builder.Eq{"is_deleted": false})
} }
if opts.Date != "" { cond = cond.And(FeedDateCond(opts))
dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation)
if err != nil {
log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err)
} else {
dateHigh := dateLow.Add(86399000000000) // 23h59m59s
cond = cond.And(builder.Gte{"`action`.created_unix": dateLow.Unix()})
cond = cond.And(builder.Lte{"`action`.created_unix": dateHigh.Unix()})
}
}
return cond, nil return cond, nil
} }

View File

@ -208,10 +208,32 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
return nil, 0, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo") return nil, 0, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo")
} }
cond, err := ActivityQueryCondition(ctx, opts) var err error
var cond builder.Cond
// if the actor is the requested user or is an administrator, we can skip the ActivityQueryCondition
if opts.Actor != nil && opts.RequestedUser != nil && (opts.Actor.IsAdmin || opts.Actor.ID == opts.RequestedUser.ID) {
cond = builder.Eq{
"user_id": opts.RequestedUser.ID,
}.And(
FeedDateCond(opts),
)
if !opts.IncludeDeleted {
cond = cond.And(builder.Eq{"is_deleted": false})
}
if !opts.IncludePrivate {
cond = cond.And(builder.Eq{"is_private": false})
}
if opts.OnlyPerformedBy {
cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID})
}
} else {
cond, err = ActivityQueryCondition(ctx, opts)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
}
actions := make([]*Action, 0, opts.PageSize) actions := make([]*Action, 0, opts.PageSize)
var count int64 var count int64

View File

@ -44,7 +44,7 @@ func init() {
// TranslatableMessage represents JSON struct that can be translated with a Locale // TranslatableMessage represents JSON struct that can be translated with a Locale
type TranslatableMessage struct { type TranslatableMessage struct {
Format string Format string
Args []any `json:"omitempty"` Args []any `json:",omitempty"`
} }
// LoadRepo loads repository of the task // LoadRepo loads repository of the task

View File

@ -5,6 +5,7 @@ package auth
import ( import (
"fmt" "fmt"
"slices"
"strings" "strings"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
@ -14,7 +15,7 @@ import (
type AccessTokenScopeCategory int type AccessTokenScopeCategory int
const ( const (
AccessTokenScopeCategoryActivityPub = iota AccessTokenScopeCategoryActivityPub AccessTokenScopeCategory = iota
AccessTokenScopeCategoryAdmin AccessTokenScopeCategoryAdmin
AccessTokenScopeCategoryMisc // WARN: this is now just a placeholder, don't remove it which will change the following values AccessTokenScopeCategoryMisc // WARN: this is now just a placeholder, don't remove it which will change the following values
AccessTokenScopeCategoryNotification AccessTokenScopeCategoryNotification
@ -193,6 +194,14 @@ var accessTokenScopes = map[AccessTokenScopeLevel]map[AccessTokenScopeCategory]A
}, },
} }
func GetAccessTokenCategories() (res []string) {
for _, cat := range accessTokenScopes[Read] {
res = append(res, strings.TrimPrefix(string(cat), "read:"))
}
slices.Sort(res)
return res
}
// GetRequiredScopes gets the specific scopes for a given level and categories // GetRequiredScopes gets the specific scopes for a given level and categories
func GetRequiredScopes(level AccessTokenScopeLevel, scopeCategories ...AccessTokenScopeCategory) []AccessTokenScope { func GetRequiredScopes(level AccessTokenScopeLevel, scopeCategories ...AccessTokenScopeCategory) []AccessTokenScope {
scopes := make([]AccessTokenScope, 0, len(scopeCategories)) scopes := make([]AccessTokenScope, 0, len(scopeCategories))
@ -270,6 +279,9 @@ func (s AccessTokenScope) parse() (accessTokenScopeBitmap, error) {
// StringSlice returns the AccessTokenScope as a []string // StringSlice returns the AccessTokenScope as a []string
func (s AccessTokenScope) StringSlice() []string { func (s AccessTokenScope) StringSlice() []string {
if s == "" {
return nil
}
return strings.Split(string(s), ",") return strings.Split(string(s), ",")
} }

View File

@ -17,6 +17,7 @@ type scopeTestNormalize struct {
} }
func TestAccessTokenScope_Normalize(t *testing.T) { func TestAccessTokenScope_Normalize(t *testing.T) {
assert.Equal(t, []string{"activitypub", "admin", "issue", "misc", "notification", "organization", "package", "repository", "user"}, GetAccessTokenCategories())
tests := []scopeTestNormalize{ tests := []scopeTestNormalize{
{"", "", nil}, {"", "", nil},
{"write:misc,write:notification,read:package,write:notification,public-only", "public-only,write:misc,write:notification,read:package", nil}, {"write:misc,write:notification,read:package,write:notification,public-only", "public-only,write:misc,write:notification,read:package", nil},
@ -25,7 +26,7 @@ func TestAccessTokenScope_Normalize(t *testing.T) {
{"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user,public-only", "public-only,all", nil}, {"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user,public-only", "public-only,all", nil},
} }
for _, scope := range []string{"activitypub", "admin", "misc", "notification", "organization", "package", "issue", "repository", "user"} { for _, scope := range GetAccessTokenCategories() {
tests = append(tests, tests = append(tests,
scopeTestNormalize{AccessTokenScope(fmt.Sprintf("read:%s", scope)), AccessTokenScope(fmt.Sprintf("read:%s", scope)), nil}, scopeTestNormalize{AccessTokenScope(fmt.Sprintf("read:%s", scope)), AccessTokenScope(fmt.Sprintf("read:%s", scope)), nil},
scopeTestNormalize{AccessTokenScope(fmt.Sprintf("write:%s", scope)), AccessTokenScope(fmt.Sprintf("write:%s", scope)), nil}, scopeTestNormalize{AccessTokenScope(fmt.Sprintf("write:%s", scope)), AccessTokenScope(fmt.Sprintf("write:%s", scope)), nil},
@ -59,7 +60,7 @@ func TestAccessTokenScope_HasScope(t *testing.T) {
{"public-only", "read:issue", false, nil}, {"public-only", "read:issue", false, nil},
} }
for _, scope := range []string{"activitypub", "admin", "misc", "notification", "organization", "package", "issue", "repository", "user"} { for _, scope := range GetAccessTokenCategories() {
tests = append(tests, tests = append(tests,
scopeTestHasScope{ scopeTestHasScope{
AccessTokenScope(fmt.Sprintf("read:%s", scope)), AccessTokenScope(fmt.Sprintf("read:%s", scope)),

View File

@ -28,11 +28,16 @@ type PullRequestsOptions struct {
Labels []int64 Labels []int64
MilestoneID int64 MilestoneID int64
PosterID int64 PosterID int64
BaseBranch string
} }
func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullRequestsOptions) *xorm.Session { func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullRequestsOptions) *xorm.Session {
sess := db.GetEngine(ctx).Where("pull_request.base_repo_id=?", baseRepoID) sess := db.GetEngine(ctx).Where("pull_request.base_repo_id=?", baseRepoID)
if opts.BaseBranch != "" {
sess.And("pull_request.base_branch=?", opts.BaseBranch)
}
sess.Join("INNER", "issue", "pull_request.issue_id = issue.id") sess.Join("INNER", "issue", "pull_request.issue_id = issue.id")
switch opts.State { switch opts.State {
case "closed", "open": case "closed", "open":
@ -56,7 +61,7 @@ func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullR
} }
// GetUnmergedPullRequestsByHeadInfo returns all pull requests that are open and has not been merged // GetUnmergedPullRequestsByHeadInfo returns all pull requests that are open and has not been merged
func GetUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch string) ([]*PullRequest, error) { func GetUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch string) (PullRequestList, error) {
prs := make([]*PullRequest, 0, 2) prs := make([]*PullRequest, 0, 2)
sess := db.GetEngine(ctx). sess := db.GetEngine(ctx).
Join("INNER", "issue", "issue.id = pull_request.issue_id"). Join("INNER", "issue", "issue.id = pull_request.issue_id").
@ -111,7 +116,7 @@ func HasUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch
// GetUnmergedPullRequestsByBaseInfo returns all pull requests that are open and has not been merged // GetUnmergedPullRequestsByBaseInfo returns all pull requests that are open and has not been merged
// by given base information (repo and branch). // by given base information (repo and branch).
func GetUnmergedPullRequestsByBaseInfo(ctx context.Context, repoID int64, branch string) ([]*PullRequest, error) { func GetUnmergedPullRequestsByBaseInfo(ctx context.Context, repoID int64, branch string) (PullRequestList, error) {
prs := make([]*PullRequest, 0, 2) prs := make([]*PullRequest, 0, 2)
return prs, db.GetEngine(ctx). return prs, db.GetEngine(ctx).
Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?", Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",

View File

@ -16,11 +16,11 @@ import (
func TestPullRequestList_LoadAttributes(t *testing.T) { func TestPullRequestList_LoadAttributes(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
prs := []*issues_model.PullRequest{ prs := issues_model.PullRequestList{
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}), unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}),
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}), unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}),
} }
assert.NoError(t, issues_model.PullRequestList(prs).LoadAttributes(db.DefaultContext)) assert.NoError(t, prs.LoadAttributes(db.DefaultContext))
for _, pr := range prs { for _, pr := range prs {
assert.NotNil(t, pr.Issue) assert.NotNil(t, pr.Issue)
assert.Equal(t, pr.IssueID, pr.Issue.ID) assert.Equal(t, pr.IssueID, pr.Issue.ID)
@ -32,11 +32,11 @@ func TestPullRequestList_LoadAttributes(t *testing.T) {
func TestPullRequestList_LoadReviewCommentsCounts(t *testing.T) { func TestPullRequestList_LoadReviewCommentsCounts(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
prs := []*issues_model.PullRequest{ prs := issues_model.PullRequestList{
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}), unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}),
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}), unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}),
} }
reviewComments, err := issues_model.PullRequestList(prs).LoadReviewCommentsCounts(db.DefaultContext) reviewComments, err := prs.LoadReviewCommentsCounts(db.DefaultContext)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, reviewComments, 2) assert.Len(t, reviewComments, 2)
for _, pr := range prs { for _, pr := range prs {
@ -47,11 +47,11 @@ func TestPullRequestList_LoadReviewCommentsCounts(t *testing.T) {
func TestPullRequestList_LoadReviews(t *testing.T) { func TestPullRequestList_LoadReviews(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
prs := []*issues_model.PullRequest{ prs := issues_model.PullRequestList{
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}), unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}),
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}), unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}),
} }
reviewList, err := issues_model.PullRequestList(prs).LoadReviews(db.DefaultContext) reviewList, err := prs.LoadReviews(db.DefaultContext)
assert.NoError(t, err) assert.NoError(t, err)
// 1, 7, 8, 9, 10, 22 // 1, 7, 8, 9, 10, 22
assert.Len(t, reviewList, 6) assert.Len(t, reviewList, 6)

View File

@ -124,6 +124,7 @@ func GetUserOrgsList(ctx context.Context, user *user_model.User) ([]*MinimalOrg,
if err := db.GetEngine(ctx).Select(columnsStr). if err := db.GetEngine(ctx).Select(columnsStr).
Table("user"). Table("user").
Where(builder.In("`user`.`id`", queryUserOrgIDs(user.ID, true))). Where(builder.In("`user`.`id`", queryUserOrgIDs(user.ID, true))).
OrderBy("`user`.lower_name ASC").
Find(&orgs); err != nil { Find(&orgs); err != nil {
return nil, err return nil, err
} }

View File

@ -110,10 +110,13 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
if err != nil { if err != nil {
return nil, err return nil, err
} }
repository, err := repo_model.GetRepositoryByID(ctx, p.RepoID) var repository *repo_model.Repository
if p.RepoID > 0 {
repository, err = repo_model.GetRepositoryByID(ctx, p.RepoID)
if err != nil && !repo_model.IsErrRepoNotExist(err) { if err != nil && !repo_model.IsErrRepoNotExist(err) {
return nil, err return nil, err
} }
}
creator, err := user_model.GetUserByID(ctx, pv.CreatorID) creator, err := user_model.GetUserByID(ctx, pv.CreatorID)
if err != nil { if err != nil {
if errors.Is(err, util.ErrNotExist) { if errors.Is(err, util.ErrNotExist) {

View File

@ -646,13 +646,15 @@ func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
type CloneLink struct { type CloneLink struct {
SSH string SSH string
HTTPS string HTTPS string
Tea string
} }
// ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name. // ComposeHTTPSCloneURL returns HTTPS clone URL based on the given owner and repository name.
func ComposeHTTPSCloneURL(ctx context.Context, owner, repo string) string { func ComposeHTTPSCloneURL(ctx context.Context, owner, repo string) string {
return fmt.Sprintf("%s%s/%s.git", httplib.GuessCurrentAppURL(ctx), url.PathEscape(owner), url.PathEscape(repo)) return fmt.Sprintf("%s%s/%s.git", httplib.GuessCurrentAppURL(ctx), url.PathEscape(owner), url.PathEscape(repo))
} }
// ComposeSSHCloneURL returns SSH clone URL based on the given owner and repository name.
func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) string { func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) string {
sshUser := setting.SSH.User sshUser := setting.SSH.User
sshDomain := setting.SSH.Domain sshDomain := setting.SSH.Domain
@ -686,11 +688,17 @@ func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) strin
return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName)) return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
} }
// ComposeTeaCloneCommand returns Tea CLI clone command based on the given owner and repository name.
func ComposeTeaCloneCommand(ctx context.Context, owner, repo string) string {
return fmt.Sprintf("tea clone %s/%s", url.PathEscape(owner), url.PathEscape(repo))
}
func (repo *Repository) cloneLink(ctx context.Context, doer *user_model.User, repoPathName string) *CloneLink { func (repo *Repository) cloneLink(ctx context.Context, doer *user_model.User, repoPathName string) *CloneLink {
cl := new(CloneLink) return &CloneLink{
cl.SSH = ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName) SSH: ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName),
cl.HTTPS = ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName) HTTPS: ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName),
return cl Tea: ComposeTeaCloneCommand(ctx, repo.OwnerName, repoPathName),
}
} }
// CloneLink returns clone URLs of repository. // CloneLink returns clone URLs of repository.

View File

@ -5,6 +5,7 @@ package repo
import ( import (
"context" "context"
"strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
@ -149,9 +150,9 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
// If isShowFullName is set to true, also include full name prefix search // If isShowFullName is set to true, also include full name prefix search
func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string, isShowFullName bool) ([]*user_model.User, error) { func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string, isShowFullName bool) ([]*user_model.User, error) {
users := make([]*user_model.User, 0, 30) users := make([]*user_model.User, 0, 30)
var prefixCond builder.Cond = builder.Like{"name", search + "%"} var prefixCond builder.Cond = builder.Like{"lower_name", strings.ToLower(search) + "%"}
if isShowFullName { if isShowFullName {
prefixCond = prefixCond.Or(builder.Like{"full_name", "%" + search + "%"}) prefixCond = prefixCond.Or(db.BuildCaseInsensitiveLike("full_name", "%"+search+"%"))
} }
cond := builder.In("`user`.id", cond := builder.In("`user`.id",

View File

@ -12,6 +12,7 @@ import (
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 TestRepoAssignees(t *testing.T) { func TestRepoAssignees(t *testing.T) {
@ -38,3 +39,19 @@ func TestRepoAssignees(t *testing.T) {
assert.NotContains(t, []int64{users[0].ID, users[1].ID, users[2].ID}, 15) assert.NotContains(t, []int64{users[0].ID, users[1].ID, users[2].ID}, 15)
} }
} }
func TestGetIssuePostersWithSearch(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
users, err := repo_model.GetIssuePostersWithSearch(db.DefaultContext, repo2, false, "USER", false /* full name */)
require.NoError(t, err)
require.Len(t, users, 1)
assert.Equal(t, "user2", users[0].Name)
users, err = repo_model.GetIssuePostersWithSearch(db.DefaultContext, repo2, false, "TW%O", true /* full name */)
require.NoError(t, err)
require.Len(t, users, 1)
assert.Equal(t, "user2", users[0].Name)
}

View File

@ -62,6 +62,10 @@ func TestSanitizer(t *testing.T) {
`<a href="javascript:alert('xss')">bad</a>`, `bad`, `<a href="javascript:alert('xss')">bad</a>`, `bad`,
`<a href="vbscript:no">bad</a>`, `bad`, `<a href="vbscript:no">bad</a>`, `bad`,
`<a href="data:1234">bad</a>`, `bad`, `<a href="data:1234">bad</a>`, `bad`,
// Some classes and attributes are used by the frontend framework and will execute JS code, so make sure they are removed
`<div class="link-action" data-attr-class="foo" data-url="xxx">txt</div>`, `<div data-attr-class="foo">txt</div>`,
`<div class="form-fetch-action" data-markdown-generated-content="bar" data-global-init="a" data-global-click="b">txt</div>`, `<div data-markdown-generated-content="bar">txt</div>`,
} }
for i := 0; i < len(testCases); i += 2 { for i := 0; i < len(testCases); i += 2 {

View File

@ -169,12 +169,6 @@ func loadServerFrom(rootCfg ConfigProvider) {
HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0") HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0")
HTTPPort = sec.Key("HTTP_PORT").MustString("3000") HTTPPort = sec.Key("HTTP_PORT").MustString("3000")
Protocol = HTTP
protocolCfg := sec.Key("PROTOCOL").String()
switch protocolCfg {
case "https":
Protocol = HTTPS
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version // DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
// if these are removed, the warning will not be shown // if these are removed, the warning will not be shown
if sec.HasKey("ENABLE_ACME") { if sec.HasKey("ENABLE_ACME") {
@ -183,6 +177,16 @@ func loadServerFrom(rootCfg ConfigProvider) {
deprecatedSetting(rootCfg, "server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME", "v1.19.0") deprecatedSetting(rootCfg, "server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME", "v1.19.0")
EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false) EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false)
} }
Protocol = HTTP
protocolCfg := sec.Key("PROTOCOL").String()
if protocolCfg != "https" && EnableAcme {
log.Fatal("ACME could only be used with HTTPS protocol")
}
switch protocolCfg {
case "https":
Protocol = HTTPS
if EnableAcme { if EnableAcme {
AcmeURL = sec.Key("ACME_URL").MustString("") AcmeURL = sec.Key("ACME_URL").MustString("")
AcmeCARoot = sec.Key("ACME_CA_ROOT").MustString("") AcmeCARoot = sec.Key("ACME_CA_ROOT").MustString("")
@ -210,6 +214,9 @@ func loadServerFrom(rootCfg ConfigProvider) {
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_EMAIL", "server", "ACME_EMAIL", "v1.19.0") deprecatedSetting(rootCfg, "server", "LETSENCRYPT_EMAIL", "server", "ACME_EMAIL", "v1.19.0")
AcmeEmail = sec.Key("LETSENCRYPT_EMAIL").MustString("") AcmeEmail = sec.Key("LETSENCRYPT_EMAIL").MustString("")
} }
if AcmeEmail == "" {
log.Fatal("ACME Email is not set (ACME_EMAIL).")
}
} else { } else {
CertFile = sec.Key("CERT_FILE").String() CertFile = sec.Key("CERT_FILE").String()
KeyFile = sec.Key("KEY_FILE").String() KeyFile = sec.Key("KEY_FILE").String()

View File

@ -911,7 +911,6 @@ delete_token_success=Token byl odstraněn. Aplikace, které jej používají ji
repo_and_org_access=Repozitář a přístup organizace repo_and_org_access=Repozitář a přístup organizace
permissions_public_only=Pouze veřejnost permissions_public_only=Pouze veřejnost
permissions_access_all=Vše (veřejné, soukromé a omezené) permissions_access_all=Vše (veřejné, soukromé a omezené)
select_permissions=Vyberte oprávnění
permission_not_set=Není nastaveno permission_not_set=Není nastaveno
permission_no_access=Bez přístupu permission_no_access=Bez přístupu
permission_read=Přečtené permission_read=Přečtené
@ -2580,7 +2579,6 @@ diff.commit=revize
diff.git-notes=Poznámky diff.git-notes=Poznámky
diff.data_not_available=Rozdílový obsah není dostupný diff.data_not_available=Rozdílový obsah není dostupný
diff.options_button=Možnosti rozdílového porovnání diff.options_button=Možnosti rozdílového porovnání
diff.show_diff_stats=Zobrazit statistiky
diff.download_patch=Stáhněte soubor záplaty diff.download_patch=Stáhněte soubor záplaty
diff.download_diff=Stáhněte rozdílový soubor diff.download_diff=Stáhněte rozdílový soubor
diff.show_split_view=Rozdělené zobrazení diff.show_split_view=Rozdělené zobrazení

View File

@ -910,7 +910,6 @@ delete_token_success=Der Zugriffstoken wurde gelöscht. Anwendungen die diesen T
repo_and_org_access=Repository- und Organisationszugriff repo_and_org_access=Repository- und Organisationszugriff
permissions_public_only=Nur öffentlich permissions_public_only=Nur öffentlich
permissions_access_all=Alle (öffentlich, privat und begrenzt) permissions_access_all=Alle (öffentlich, privat und begrenzt)
select_permissions=Berechtigungen auswählen
permission_not_set=Nicht festgelegt permission_not_set=Nicht festgelegt
permission_no_access=Kein Zugriff permission_no_access=Kein Zugriff
permission_read=Lesen permission_read=Lesen
@ -2569,7 +2568,6 @@ diff.commit=Commit
diff.git-notes=Hinweise diff.git-notes=Hinweise
diff.data_not_available=Keine Diff-Daten verfügbar diff.data_not_available=Keine Diff-Daten verfügbar
diff.options_button=Diff-Optionen diff.options_button=Diff-Optionen
diff.show_diff_stats=Statistiken anzeigen
diff.download_patch=Patch-Datei herunterladen diff.download_patch=Patch-Datei herunterladen
diff.download_diff=Vergleichs-Datei herunterladen diff.download_diff=Vergleichs-Datei herunterladen
diff.show_split_view=Geteilte Ansicht diff.show_split_view=Geteilte Ansicht

View File

@ -810,7 +810,6 @@ delete_token_success=Το διακριτικό έχει διαγραφεί. Οι
repo_and_org_access=Πρόσβαση στο Αποθετήριο και Οργανισμό repo_and_org_access=Πρόσβαση στο Αποθετήριο και Οργανισμό
permissions_public_only=Δημόσια μόνο permissions_public_only=Δημόσια μόνο
permissions_access_all=Όλα (δημόσια, ιδιωτικά, και περιορισμένα) permissions_access_all=Όλα (δημόσια, ιδιωτικά, και περιορισμένα)
select_permissions=Επιλέξτε δικαιώματα
permission_no_access=Καμία Πρόσβαση permission_no_access=Καμία Πρόσβαση
permission_read=Αναγνωσμένες permission_read=Αναγνωσμένες
permission_write=Ανάγνωση και Εγγραφή permission_write=Ανάγνωση και Εγγραφή
@ -2317,7 +2316,6 @@ diff.commit=υποβολή
diff.git-notes=Σημειώσεις diff.git-notes=Σημειώσεις
diff.data_not_available=Δεν Υπάρχει Διαθέσιμο Περιεχόμενο Diff diff.data_not_available=Δεν Υπάρχει Διαθέσιμο Περιεχόμενο Diff
diff.options_button=Επιλογές Diff diff.options_button=Επιλογές Diff
diff.show_diff_stats=Εμφάνιση Στατιστικών
diff.download_patch=Λήψη Αρχείου Patch diff.download_patch=Λήψη Αρχείου Patch
diff.download_diff=Λήψη Αρχείου Diff diff.download_diff=Λήψη Αρχείου Diff
diff.show_split_view=Διαιρεμένη Προβολή diff.show_split_view=Διαιρεμένη Προβολή

View File

@ -917,7 +917,6 @@ delete_token_success = The token has been deleted. Applications using it no long
repo_and_org_access = Repository and Organization Access repo_and_org_access = Repository and Organization Access
permissions_public_only = Public only permissions_public_only = Public only
permissions_access_all = All (public, private, and limited) permissions_access_all = All (public, private, and limited)
select_permissions = Select permissions
permission_not_set = Not set permission_not_set = Not set
permission_no_access = No Access permission_no_access = No Access
permission_read = Read permission_read = Read
@ -1465,6 +1464,8 @@ issues.filter_milestones = Filter Milestone
issues.filter_projects = Filter Project issues.filter_projects = Filter Project
issues.filter_labels = Filter Label issues.filter_labels = Filter Label
issues.filter_reviewers = Filter Reviewer issues.filter_reviewers = Filter Reviewer
issues.filter_no_results = No results
issues.filter_no_results_placeholder = Try adjusting your search filters.
issues.new = New Issue issues.new = New Issue
issues.new.title_empty = Title cannot be empty issues.new.title_empty = Title cannot be empty
issues.new.labels = Labels issues.new.labels = Labels
@ -2593,7 +2594,6 @@ diff.commit = commit
diff.git-notes = Notes diff.git-notes = Notes
diff.data_not_available = Diff Content Not Available diff.data_not_available = Diff Content Not Available
diff.options_button = Diff Options diff.options_button = Diff Options
diff.show_diff_stats = Show Stats
diff.download_patch = Download Patch File diff.download_patch = Download Patch File
diff.download_diff = Download Diff File diff.download_diff = Download Diff File
diff.show_split_view = Split View diff.show_split_view = Split View

View File

@ -806,7 +806,6 @@ delete_token_success=El token ha sido eliminado. Las aplicaciones que lo usen ya
repo_and_org_access=Acceso al Repositorio y a la Organización repo_and_org_access=Acceso al Repositorio y a la Organización
permissions_public_only=Sólo público permissions_public_only=Sólo público
permissions_access_all=Todo (público, privado y limitado) permissions_access_all=Todo (público, privado y limitado)
select_permissions=Seleccionar permisos
permission_no_access=Sin acceso permission_no_access=Sin acceso
permission_read=Leídas permission_read=Leídas
permission_write=Lectura y Escritura permission_write=Lectura y Escritura
@ -2298,7 +2297,6 @@ diff.commit=commit
diff.git-notes=Notas diff.git-notes=Notas
diff.data_not_available=El contenido del Diff no está disponible diff.data_not_available=El contenido del Diff no está disponible
diff.options_button=Opciones de diferencias diff.options_button=Opciones de diferencias
diff.show_diff_stats=Mostrar estadísticas
diff.download_patch=Descargar archivo de parche diff.download_patch=Descargar archivo de parche
diff.download_diff=Descargar archivo de diferencias diff.download_diff=Descargar archivo de diferencias
diff.show_split_view=Dividir vista diff.show_split_view=Dividir vista

View File

@ -1777,7 +1777,6 @@ diff.commit=کامیت
diff.git-notes=یادداشت‌ها diff.git-notes=یادداشت‌ها
diff.data_not_available=محتوای تفاوت ها در دسترس نیست diff.data_not_available=محتوای تفاوت ها در دسترس نیست
diff.options_button=تنظیمات (diff) تغییرات diff.options_button=تنظیمات (diff) تغییرات
diff.show_diff_stats=نمایش وضعیت
diff.download_patch=دانلود پرونده وصله diff.download_patch=دانلود پرونده وصله
diff.download_diff=دانلود فایل تغییرات diff diff.download_diff=دانلود فایل تغییرات diff
diff.show_split_view=مشاهده تقسیم شده diff.show_split_view=مشاهده تقسیم شده

View File

@ -917,7 +917,6 @@ delete_token_success=Ce jeton a été supprimé. Les applications l'utilisant n'
repo_and_org_access=Accès aux Organisations et Dépôts repo_and_org_access=Accès aux Organisations et Dépôts
permissions_public_only=Publique uniquement permissions_public_only=Publique uniquement
permissions_access_all=Tout (public, privé et limité) permissions_access_all=Tout (public, privé et limité)
select_permissions=Sélectionner les autorisations
permission_not_set=Non défini permission_not_set=Non défini
permission_no_access=Aucun accès permission_no_access=Aucun accès
permission_read=Lecture permission_read=Lecture
@ -1701,7 +1700,9 @@ issues.time_estimate_invalid=Le format du temps estimé est invalide
issues.start_tracking_history=`a commencé son travail %s.` issues.start_tracking_history=`a commencé son travail %s.`
issues.tracker_auto_close=Le minuteur sera automatiquement arrêté quand le ticket sera fermé. issues.tracker_auto_close=Le minuteur sera automatiquement arrêté quand le ticket sera fermé.
issues.tracking_already_started=`Vous avez déjà un minuteur en cours sur <a href="%s">un autre ticket</a> !` issues.tracking_already_started=`Vous avez déjà un minuteur en cours sur <a href="%s">un autre ticket</a> !`
issues.stop_tracking=Arrêter le minuteur
issues.stop_tracking_history=a travaillé sur <b>%[1]s</b> %[2]s issues.stop_tracking_history=a travaillé sur <b>%[1]s</b> %[2]s
issues.cancel_tracking=Abandonner
issues.cancel_tracking_history=`a abandonné son minuteur %s.` issues.cancel_tracking_history=`a abandonné son minuteur %s.`
issues.del_time=Supprimer ce minuteur du journal issues.del_time=Supprimer ce minuteur du journal
issues.add_time_history=a pointé du temps de travail sur <b>%[1]s</b>, %[2]s issues.add_time_history=a pointé du temps de travail sur <b>%[1]s</b>, %[2]s
@ -2590,7 +2591,6 @@ diff.commit=révision
diff.git-notes=Notes diff.git-notes=Notes
diff.data_not_available=Contenu de la comparaison indisponible diff.data_not_available=Contenu de la comparaison indisponible
diff.options_button=Option de Diff diff.options_button=Option de Diff
diff.show_diff_stats=Voir les Statistiques
diff.download_patch=Télécharger le Fichier Patch diff.download_patch=Télécharger le Fichier Patch
diff.download_diff=Télécharger le Fichier des Différences diff.download_diff=Télécharger le Fichier des Différences
diff.show_split_view=Vue séparée diff.show_split_view=Vue séparée

View File

@ -917,7 +917,6 @@ delete_token_success=Tá an comhartha scriosta. Níl rochtain ag iarratais a ús
repo_and_org_access=Rochtain Stórála agus Eagraíochta repo_and_org_access=Rochtain Stórála agus Eagraíochta
permissions_public_only=Poiblí amháin permissions_public_only=Poiblí amháin
permissions_access_all=Gach (poiblí, príobháideach agus teoranta) permissions_access_all=Gach (poiblí, príobháideach agus teoranta)
select_permissions=Roghnaigh ceadanna
permission_not_set=Níl leagtha permission_not_set=Níl leagtha
permission_no_access=Gan rochtain permission_no_access=Gan rochtain
permission_read=Léigh permission_read=Léigh
@ -1464,6 +1463,8 @@ issues.filter_milestones=Cloch Mhíle Scagaire
issues.filter_projects=Tionscadal Scagaire issues.filter_projects=Tionscadal Scagaire
issues.filter_labels=Lipéad Scagaire issues.filter_labels=Lipéad Scagaire
issues.filter_reviewers=Athbhreithneoir Scagaire issues.filter_reviewers=Athbhreithneoir Scagaire
issues.filter_no_results=Gan torthaí
issues.filter_no_results_placeholder=Bain triail as do scagairí cuardaigh a choigeartú.
issues.new=Eagrán Nua issues.new=Eagrán Nua
issues.new.title_empty=Ní féidir leis an teideal a bheith folamh issues.new.title_empty=Ní féidir leis an teideal a bheith folamh
issues.new.labels=Lipéid issues.new.labels=Lipéid
@ -1701,7 +1702,9 @@ issues.time_estimate_invalid=Tá formáid meastachán ama neamhbhailí
issues.start_tracking_history=thosaigh ag obair %s issues.start_tracking_history=thosaigh ag obair %s
issues.tracker_auto_close=Stopfar ama go huathoibríoch nuair a dhúnfar an tsaincheist seo issues.tracker_auto_close=Stopfar ama go huathoibríoch nuair a dhúnfar an tsaincheist seo
issues.tracking_already_started=`Tá tús curtha agat cheana féin ag rianú ama ar <a href="%s">eagrán eile</a>!` issues.tracking_already_started=`Tá tús curtha agat cheana féin ag rianú ama ar <a href="%s">eagrán eile</a>!`
issues.stop_tracking=Stad uaineadóir
issues.stop_tracking_history=d'oibrigh do <b>%[1]s</b> %[2]s issues.stop_tracking_history=d'oibrigh do <b>%[1]s</b> %[2]s
issues.cancel_tracking=Caith amach
issues.cancel_tracking_history=`rianú ama curtha ar ceal %s` issues.cancel_tracking_history=`rianú ama curtha ar ceal %s`
issues.del_time=Scrios an log ama seo issues.del_time=Scrios an log ama seo
issues.add_time_history=cuireadh am caite <b>%[1]s</b> %[2]s leis issues.add_time_history=cuireadh am caite <b>%[1]s</b> %[2]s leis
@ -2590,7 +2593,6 @@ diff.commit=tiomantas
diff.git-notes=Nótaí diff.git-notes=Nótaí
diff.data_not_available=Níl Ábhar Difríochtaí Ar Fáil diff.data_not_available=Níl Ábhar Difríochtaí Ar Fáil
diff.options_button=Roghanna Diff diff.options_button=Roghanna Diff
diff.show_diff_stats=Taispeáin Staitisticí
diff.download_patch=Íoslódáil an comhad paiste diff.download_patch=Íoslódáil an comhad paiste
diff.download_diff=Íoslódáil Comhad Diff diff.download_diff=Íoslódáil Comhad Diff
diff.show_split_view=Amharc Scoilt diff.show_split_view=Amharc Scoilt

View File

@ -1098,7 +1098,6 @@ diff.parent=szülő
diff.commit=commit diff.commit=commit
diff.git-notes=Megjegyzések diff.git-notes=Megjegyzések
diff.data_not_available=A különbségek nem megjeleníthetőek diff.data_not_available=A különbségek nem megjeleníthetőek
diff.show_diff_stats=Statisztikák mutatása
diff.show_split_view=Osztott nézet diff.show_split_view=Osztott nézet
diff.show_unified_view=Egyesített nézet diff.show_unified_view=Egyesített nézet
diff.stats_desc=<strong>%d fájl</strong> változott, egészen pontosan <strong>%d új sor hozzáadva</strong> és <strong>%d régi sor törölve</strong> diff.stats_desc=<strong>%d fájl</strong> változott, egészen pontosan <strong>%d új sor hozzáadva</strong> és <strong>%d régi sor törölve</strong>

View File

@ -1929,7 +1929,6 @@ diff.commit=commit
diff.git-notes=Note diff.git-notes=Note
diff.data_not_available=Dati Diff non disponibili diff.data_not_available=Dati Diff non disponibili
diff.options_button=Opzioni Diff diff.options_button=Opzioni Diff
diff.show_diff_stats=Mostra statistiche
diff.download_patch=Scarica il file Patch diff.download_patch=Scarica il file Patch
diff.download_diff=Scarica il file Diff diff.download_diff=Scarica il file Diff
diff.show_split_view=Visualizzazione separata diff.show_split_view=Visualizzazione separata

View File

@ -385,6 +385,12 @@ show_only_public=公開のみ表示
issues.in_your_repos=あなたのリポジトリ issues.in_your_repos=あなたのリポジトリ
guide_title=アクティビティはありません
guide_desc=現在フォロー中のリポジトリやユーザーがないため、表示するコンテンツがありません。 以下のリンクから、興味のあるリポジトリやユーザーを探すことができます。
explore_repos=リポジトリを探す
explore_users=ユーザーを探す
empty_org=組織はまだありません。
empty_repo=リポジトリはまだありません。
[explore] [explore]
repos=リポジトリ repos=リポジトリ
@ -911,7 +917,6 @@ delete_token_success=トークンを削除しました。 削除したトーク
repo_and_org_access=リポジトリと組織へのアクセス repo_and_org_access=リポジトリと組織へのアクセス
permissions_public_only=公開のみ permissions_public_only=公開のみ
permissions_access_all=すべて (公開、プライベート、限定) permissions_access_all=すべて (公開、プライベート、限定)
select_permissions=許可の選択
permission_not_set=設定なし permission_not_set=設定なし
permission_no_access=アクセス不可 permission_no_access=アクセス不可
permission_read=読み取り permission_read=読み取り
@ -1348,6 +1353,8 @@ editor.new_branch_name_desc=新しいブランチ名…
editor.cancel=キャンセル editor.cancel=キャンセル
editor.filename_cannot_be_empty=ファイル名は空にできません。 editor.filename_cannot_be_empty=ファイル名は空にできません。
editor.filename_is_invalid=`ファイル名が不正です: "%s"` editor.filename_is_invalid=`ファイル名が不正です: "%s"`
editor.commit_email=コミット メールアドレス
editor.invalid_commit_email=コミットに使うメールアドレスが正しくありません。
editor.branch_does_not_exist=このリポジトリにブランチ "%s" は存在しません。 editor.branch_does_not_exist=このリポジトリにブランチ "%s" は存在しません。
editor.branch_already_exists=ブランチ "%s" は、このリポジトリに既に存在します。 editor.branch_already_exists=ブランチ "%s" は、このリポジトリに既に存在します。
editor.directory_is_a_file=ディレクトリ名 "%s" はすでにリポジトリ内のファイルで使用されています。 editor.directory_is_a_file=ディレクトリ名 "%s" はすでにリポジトリ内のファイルで使用されています。
@ -1693,7 +1700,9 @@ issues.time_estimate_invalid=見積時間のフォーマットが不正です
issues.start_tracking_history=が作業を開始 %s issues.start_tracking_history=が作業を開始 %s
issues.tracker_auto_close=タイマーは、このイシューがクローズされると自動的に終了します issues.tracker_auto_close=タイマーは、このイシューがクローズされると自動的に終了します
issues.tracking_already_started=`<a href="%s">別のイシュー</a>で既にタイムトラッキングを開始しています!` issues.tracking_already_started=`<a href="%s">別のイシュー</a>で既にタイムトラッキングを開始しています!`
issues.stop_tracking=タイマー終了
issues.stop_tracking_history=が <b>%[1]s</b> の作業を終了 %[2]s issues.stop_tracking_history=が <b>%[1]s</b> の作業を終了 %[2]s
issues.cancel_tracking=破棄
issues.cancel_tracking_history=`がタイムトラッキングを中止 %s` issues.cancel_tracking_history=`がタイムトラッキングを中止 %s`
issues.del_time=このタイムログを削除 issues.del_time=このタイムログを削除
issues.add_time_history=が作業時間 <b>%[1]s</b> を追加 %[2]s issues.add_time_history=が作業時間 <b>%[1]s</b> を追加 %[2]s
@ -2329,6 +2338,8 @@ settings.event_fork=フォーク
settings.event_fork_desc=リポジトリがフォークされたとき。 settings.event_fork_desc=リポジトリがフォークされたとき。
settings.event_wiki=Wiki settings.event_wiki=Wiki
settings.event_wiki_desc=Wikiページが作成・名前変更・編集・削除されたとき。 settings.event_wiki_desc=Wikiページが作成・名前変更・編集・削除されたとき。
settings.event_statuses=ステータス
settings.event_statuses_desc=APIによってコミットのステータスが更新されたとき。
settings.event_release=リリース settings.event_release=リリース
settings.event_release_desc=リポジトリでリリースが作成・更新・削除されたとき。 settings.event_release_desc=リポジトリでリリースが作成・更新・削除されたとき。
settings.event_push=プッシュ settings.event_push=プッシュ
@ -2580,7 +2591,6 @@ diff.commit=コミット
diff.git-notes=Notes diff.git-notes=Notes
diff.data_not_available=差分はありません diff.data_not_available=差分はありません
diff.options_button=差分オプション diff.options_button=差分オプション
diff.show_diff_stats=統計情報を表示
diff.download_patch=Patchファイルをダウンロード diff.download_patch=Patchファイルをダウンロード
diff.download_diff=Diffファイルをダウンロード diff.download_diff=Diffファイルをダウンロード
diff.show_split_view=分割表示 diff.show_split_view=分割表示
@ -2876,6 +2886,14 @@ view_as_role=表示: %s
view_as_public_hint=READMEを公開ユーザーとして見ています。 view_as_public_hint=READMEを公開ユーザーとして見ています。
view_as_member_hint=READMEをこの組織のメンバーとして見ています。 view_as_member_hint=READMEをこの組織のメンバーとして見ています。
worktime=作業時間
worktime.date_range_start=期間 (自)
worktime.date_range_end=期間 (至)
worktime.query=集計
worktime.time=時間
worktime.by_repositories=リポジトリ別
worktime.by_milestones=マイルストーン別
worktime.by_members=メンバー別
[admin] [admin]
maintenance=メンテナンス maintenance=メンテナンス

View File

@ -814,7 +814,6 @@ delete_token_success=Pilnvara tika izdzēsta. Lietojumprogrammām, kas izmantoja
repo_and_org_access=Repozitorija un organizācijas piekļuve repo_and_org_access=Repozitorija un organizācijas piekļuve
permissions_public_only=Tikai publiskie permissions_public_only=Tikai publiskie
permissions_access_all=Visi (publiskie, privātie un ierobežotie) permissions_access_all=Visi (publiskie, privātie un ierobežotie)
select_permissions=Norādiet tiesības
permission_no_access=Nav piekļuves permission_no_access=Nav piekļuves
permission_read=Skatīšanās permission_read=Skatīšanās
permission_write=Skatīšanās un raksīšanas permission_write=Skatīšanās un raksīšanas
@ -2317,7 +2316,6 @@ diff.commit=revīzija
diff.git-notes=Piezīmes diff.git-notes=Piezīmes
diff.data_not_available=Satura salīdzināšana nav pieejama diff.data_not_available=Satura salīdzināšana nav pieejama
diff.options_button=Salīdzināšanas iespējas diff.options_button=Salīdzināšanas iespējas
diff.show_diff_stats=Rādīt statistiku
diff.download_patch=Lejupielādēt ielāpa failu diff.download_patch=Lejupielādēt ielāpa failu
diff.download_diff=Lejupielādēt izmaiņu failu diff.download_diff=Lejupielādēt izmaiņu failu
diff.show_split_view=Dalītais skats diff.show_split_view=Dalītais skats

View File

@ -1864,7 +1864,6 @@ diff.commit=commit
diff.git-notes=Notities diff.git-notes=Notities
diff.data_not_available=Diff gegevens niet beschikbaar diff.data_not_available=Diff gegevens niet beschikbaar
diff.options_button=Diff opties diff.options_button=Diff opties
diff.show_diff_stats=Statistieken weergeven
diff.download_patch=Download Patch-bestand diff.download_patch=Download Patch-bestand
diff.download_diff=Download Diff-bestand diff.download_diff=Download Diff-bestand
diff.show_split_view=Zij-aan-zij weergave diff.show_split_view=Zij-aan-zij weergave

View File

@ -1728,7 +1728,6 @@ diff.commit=commit
diff.git-notes=Notatki diff.git-notes=Notatki
diff.data_not_available=Informacje nt. zmian nie są dostępne diff.data_not_available=Informacje nt. zmian nie są dostępne
diff.options_button=Opcje porównania diff.options_button=Opcje porównania
diff.show_diff_stats=Pokaż statystyki
diff.download_patch=Ściągnij plik aktualizacji diff.download_patch=Ściągnij plik aktualizacji
diff.download_diff=Ściągnij plik porównania diff.download_diff=Ściągnij plik porównania
diff.show_split_view=Widok podzielony diff.show_split_view=Widok podzielony

View File

@ -812,7 +812,6 @@ delete_token_success=O token foi excluído. Os aplicativos que o utilizam já n
repo_and_org_access=Acesso ao Repositório e Organização repo_and_org_access=Acesso ao Repositório e Organização
permissions_public_only=Apenas público permissions_public_only=Apenas público
permissions_access_all=Todos (público, privado e limitado) permissions_access_all=Todos (público, privado e limitado)
select_permissions=Selecionar permissões
permission_no_access=Sem acesso permission_no_access=Sem acesso
permission_read=Ler permission_read=Ler
permission_write=Ler e escrever permission_write=Ler e escrever
@ -2282,7 +2281,6 @@ diff.commit=commit
diff.git-notes=Notas diff.git-notes=Notas
diff.data_not_available=Conteúdo de diff não disponível diff.data_not_available=Conteúdo de diff não disponível
diff.options_button=Opções de diferenças diff.options_button=Opções de diferenças
diff.show_diff_stats=Mostrar estatísticas
diff.download_patch=Baixar arquivo de patch diff.download_patch=Baixar arquivo de patch
diff.download_diff=Baixar arquivo de diferenças diff.download_diff=Baixar arquivo de diferenças
diff.show_split_view=Visão dividida diff.show_split_view=Visão dividida

View File

@ -917,7 +917,6 @@ delete_token_success=O código foi eliminado. Aplicações que o usavam deixaram
repo_and_org_access=Acesso aos repositórios e às organizações repo_and_org_access=Acesso aos repositórios e às organizações
permissions_public_only=Apenas público permissions_public_only=Apenas público
permissions_access_all=Tudo (público, privado e limitado) permissions_access_all=Tudo (público, privado e limitado)
select_permissions=Escolher permissões
permission_not_set=Não definido permission_not_set=Não definido
permission_no_access=Sem acesso permission_no_access=Sem acesso
permission_read=Lidas permission_read=Lidas
@ -1464,6 +1463,8 @@ issues.filter_milestones=Filtrar etapa
issues.filter_projects=Filtrar planeamento issues.filter_projects=Filtrar planeamento
issues.filter_labels=Filtrar rótulo issues.filter_labels=Filtrar rótulo
issues.filter_reviewers=Filtrar revisor issues.filter_reviewers=Filtrar revisor
issues.filter_no_results=Sem resultados
issues.filter_no_results_placeholder=Tente ajustar os seus filtros de pesquisa.
issues.new=Questão nova issues.new=Questão nova
issues.new.title_empty=O título não pode estar vazio issues.new.title_empty=O título não pode estar vazio
issues.new.labels=Rótulos issues.new.labels=Rótulos
@ -2592,7 +2593,6 @@ diff.commit=cometimento
diff.git-notes=Notas diff.git-notes=Notas
diff.data_not_available=O conteúdo das diferenças não está disponível diff.data_not_available=O conteúdo das diferenças não está disponível
diff.options_button=Opções das diferenças diff.options_button=Opções das diferenças
diff.show_diff_stats=Mostrar estatísticas
diff.download_patch=Descarregar ficheiro patch diff.download_patch=Descarregar ficheiro patch
diff.download_diff=Descarregar ficheiro diff diff.download_diff=Descarregar ficheiro diff
diff.show_split_view=Visualização em 2 colunas diff.show_split_view=Visualização em 2 colunas

View File

@ -807,7 +807,6 @@ delete_token_success=Токен удалён. Приложения, исполь
repo_and_org_access=Доступ к репозиторию и организации repo_and_org_access=Доступ к репозиторию и организации
permissions_public_only=Только публичные permissions_public_only=Только публичные
permissions_access_all=Все (публичные, приватные и ограниченные) permissions_access_all=Все (публичные, приватные и ограниченные)
select_permissions=Выбрать разрешения
permission_no_access=Нет доступа permission_no_access=Нет доступа
permission_read=Прочитанные permission_read=Прочитанные
permission_write=Чтение и запись permission_write=Чтение и запись
@ -2268,7 +2267,6 @@ diff.commit=Коммит
diff.git-notes=Заметки diff.git-notes=Заметки
diff.data_not_available=Разница недоступна diff.data_not_available=Разница недоступна
diff.options_button=Опции Diff diff.options_button=Опции Diff
diff.show_diff_stats=Показать статистику
diff.download_patch=Скачать Patch файл diff.download_patch=Скачать Patch файл
diff.download_diff=Скачать Diff файл diff.download_diff=Скачать Diff файл
diff.show_split_view=Разделённый вид diff.show_split_view=Разделённый вид

View File

@ -1739,7 +1739,6 @@ diff.commit=කැප
diff.git-notes=සටහන් diff.git-notes=සටහන්
diff.data_not_available=Diff අන්තර්ගත ලබාගත නොහැක diff.data_not_available=Diff අන්තර්ගත ලබාගත නොහැක
diff.options_button=විවිධ විකල්ප diff.options_button=විවිධ විකල්ප
diff.show_diff_stats=සංඛ්යාන පෙන්වන්න
diff.download_patch=පැච් ගොනුව බාගත diff.download_patch=පැච් ගොනුව බාගත
diff.download_diff=බාගත Dff ගොනුව diff.download_diff=බාගත Dff ගොනුව
diff.show_split_view=භේදය දැක්ම diff.show_split_view=භේදය දැක්ම

View File

@ -895,7 +895,6 @@ delete_token_success=Jeton silindi. Onu kullanan uygulamalar artık hesabınıza
repo_and_org_access=Depo ve Organizasyon Erişimi repo_and_org_access=Depo ve Organizasyon Erişimi
permissions_public_only=Yalnızca herkese açık permissions_public_only=Yalnızca herkese açık
permissions_access_all=Tümü (herkese açık, özel ve sınırlı) permissions_access_all=Tümü (herkese açık, özel ve sınırlı)
select_permissions=İzinleri seçin
permission_not_set=Ayarlanmadı permission_not_set=Ayarlanmadı
permission_no_access=Erişim Yok permission_no_access=Erişim Yok
permission_read=Okunmuş permission_read=Okunmuş
@ -2470,7 +2469,6 @@ diff.commit=işleme
diff.git-notes=Notlar diff.git-notes=Notlar
diff.data_not_available=Farklı İçerik Mevut Değil diff.data_not_available=Farklı İçerik Mevut Değil
diff.options_button=Diff Seçenekleri diff.options_button=Diff Seçenekleri
diff.show_diff_stats=İstatistikleri Göster
diff.download_patch=Yama Dosyasını İndir diff.download_patch=Yama Dosyasını İndir
diff.download_diff=Diff Dosyasını İndir diff.download_diff=Diff Dosyasını İndir
diff.show_split_view=Görünümü Böl diff.show_split_view=Görünümü Böl

View File

@ -1789,7 +1789,6 @@ diff.commit=коміт
diff.git-notes=Примітки diff.git-notes=Примітки
diff.data_not_available=Різниця недоступна diff.data_not_available=Різниця недоступна
diff.options_button=Параметри порівняння diff.options_button=Параметри порівняння
diff.show_diff_stats=Показати статистику
diff.download_patch=Завантажити патч diff.download_patch=Завантажити патч
diff.download_diff=Завантажити файл різниці diff.download_diff=Завантажити файл різниці
diff.show_split_view=Розділений перегляд diff.show_split_view=Розділений перегляд

View File

@ -910,7 +910,6 @@ delete_token_success=令牌已经被删除。使用该令牌的应用将不再
repo_and_org_access=仓库和组织访问权限 repo_and_org_access=仓库和组织访问权限
permissions_public_only=仅公开 permissions_public_only=仅公开
permissions_access_all=全部(公开、私有和受限) permissions_access_all=全部(公开、私有和受限)
select_permissions=选择权限
permission_not_set=未设置 permission_not_set=未设置
permission_no_access=无访问权限 permission_no_access=无访问权限
permission_read=可读 permission_read=可读
@ -2569,7 +2568,6 @@ diff.commit=当前提交
diff.git-notes=Notes diff.git-notes=Notes
diff.data_not_available=比较内容不可用 diff.data_not_available=比较内容不可用
diff.options_button=Diff 选项 diff.options_button=Diff 选项
diff.show_diff_stats=显示统计
diff.download_patch=下载 Patch 文件 diff.download_patch=下载 Patch 文件
diff.download_diff=下载 Diff 文件 diff.download_diff=下载 Diff 文件
diff.show_split_view=分列视图 diff.show_split_view=分列视图

View File

@ -907,7 +907,6 @@ delete_token_success=已刪除 Token。使用此 Token 的應用程式無法再
repo_and_org_access=儲存庫和組織存取 repo_and_org_access=儲存庫和組織存取
permissions_public_only=僅公開 permissions_public_only=僅公開
permissions_access_all=全部 (公開、私有與受限) permissions_access_all=全部 (公開、私有與受限)
select_permissions=選擇權限
permission_not_set=尚未設定 permission_not_set=尚未設定
permission_no_access=沒有權限 permission_no_access=沒有權限
permission_read=讀取 permission_read=讀取
@ -2560,7 +2559,6 @@ diff.commit=當前提交
diff.git-notes=備註 diff.git-notes=備註
diff.data_not_available=沒有內容比較可以使用 diff.data_not_available=沒有內容比較可以使用
diff.options_button=差異選項 diff.options_button=差異選項
diff.show_diff_stats=顯示統計資料
diff.download_patch=下載 Patch 檔 diff.download_patch=下載 Patch 檔
diff.download_diff=下載差異檔 diff.download_diff=下載差異檔
diff.show_split_view=分割檢視 diff.show_split_view=分割檢視

View File

@ -66,6 +66,7 @@ type PackageMetadataResponse struct {
} }
// PackageVersionMetadata contains package metadata // PackageVersionMetadata contains package metadata
// https://getcomposer.org/doc/05-repositories.md#package
type PackageVersionMetadata struct { type PackageVersionMetadata struct {
*composer_module.Metadata *composer_module.Metadata
Name string `json:"name"` Name string `json:"name"`
@ -73,6 +74,7 @@ type PackageVersionMetadata struct {
Type string `json:"type"` Type string `json:"type"`
Created time.Time `json:"time"` Created time.Time `json:"time"`
Dist Dist `json:"dist"` Dist Dist `json:"dist"`
Source Source `json:"source"`
} }
// Dist contains package download information // Dist contains package download information
@ -82,6 +84,13 @@ type Dist struct {
Checksum string `json:"shasum"` Checksum string `json:"shasum"`
} }
// Source contains package source information
type Source struct {
URL string `json:"url"`
Type string `json:"type"`
Reference string `json:"reference"`
}
func createPackageMetadataResponse(registryURL string, pds []*packages_model.PackageDescriptor) *PackageMetadataResponse { func createPackageMetadataResponse(registryURL string, pds []*packages_model.PackageDescriptor) *PackageMetadataResponse {
versions := make([]*PackageVersionMetadata, 0, len(pds)) versions := make([]*PackageVersionMetadata, 0, len(pds))
@ -94,7 +103,7 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac
} }
} }
versions = append(versions, &PackageVersionMetadata{ pkg := PackageVersionMetadata{
Name: pd.Package.Name, Name: pd.Package.Name,
Version: pd.Version.Version, Version: pd.Version.Version,
Type: packageType, Type: packageType,
@ -105,7 +114,16 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac
URL: fmt.Sprintf("%s/files/%s/%s/%s", registryURL, url.PathEscape(pd.Package.LowerName), url.PathEscape(pd.Version.LowerVersion), url.PathEscape(pd.Files[0].File.LowerName)), URL: fmt.Sprintf("%s/files/%s/%s/%s", registryURL, url.PathEscape(pd.Package.LowerName), url.PathEscape(pd.Version.LowerVersion), url.PathEscape(pd.Files[0].File.LowerName)),
Checksum: pd.Files[0].Blob.HashSHA1, Checksum: pd.Files[0].Blob.HashSHA1,
}, },
}) }
if pd.Repository != nil {
pkg.Source = Source{
URL: pd.Repository.HTMLURL(),
Type: "git",
Reference: pd.Version.Version,
}
}
versions = append(versions, &pkg)
} }
return &PackageMetadataResponse{ return &PackageMetadataResponse{

View File

@ -8,7 +8,6 @@ import (
"strings" "strings"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
maven_module "code.gitea.io/gitea/modules/packages/maven"
) )
// MetadataResponse https://maven.apache.org/ref/3.2.5/maven-repository-metadata/repository-metadata.html // MetadataResponse https://maven.apache.org/ref/3.2.5/maven-repository-metadata/repository-metadata.html
@ -22,7 +21,7 @@ type MetadataResponse struct {
} }
// pds is expected to be sorted ascending by CreatedUnix // pds is expected to be sorted ascending by CreatedUnix
func createMetadataResponse(pds []*packages_model.PackageDescriptor) *MetadataResponse { func createMetadataResponse(pds []*packages_model.PackageDescriptor, groupID, artifactID string) *MetadataResponse {
var release *packages_model.PackageDescriptor var release *packages_model.PackageDescriptor
versions := make([]string, 0, len(pds)) versions := make([]string, 0, len(pds))
@ -35,11 +34,9 @@ func createMetadataResponse(pds []*packages_model.PackageDescriptor) *MetadataRe
latest := pds[len(pds)-1] latest := pds[len(pds)-1]
metadata := latest.Metadata.(*maven_module.Metadata)
resp := &MetadataResponse{ resp := &MetadataResponse{
GroupID: metadata.GroupID, GroupID: groupID,
ArtifactID: metadata.ArtifactID, ArtifactID: artifactID,
Latest: latest.Version.Version, Latest: latest.Version.Version,
Version: versions, Version: versions,
} }

View File

@ -84,20 +84,19 @@ func handlePackageFile(ctx *context.Context, serveContent bool) {
} }
func serveMavenMetadata(ctx *context.Context, params parameters) { func serveMavenMetadata(ctx *context.Context, params parameters) {
// /com/foo/project/maven-metadata.xml[.md5/.sha1/.sha256/.sha512] // path pattern: /com/foo/project/maven-metadata.xml[.md5/.sha1/.sha256/.sha512]
// in case there are legacy package names ("GroupID-ArtifactID") we need to check both, new packages always use ":" as separator("GroupID:ArtifactID")
pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageName()) pvsLegacy, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageNameLegacy())
if errors.Is(err, util.ErrNotExist) {
pvs, err = packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageNameLegacy())
}
if err != nil { if err != nil {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
return return
} }
if len(pvs) == 0 { pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageName())
apiError(ctx, http.StatusNotFound, packages_model.ErrPackageNotExist) if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return return
} }
pvs = append(pvsLegacy, pvs...)
pds, err := packages_model.GetPackageDescriptors(ctx, pvs) pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
if err != nil { if err != nil {
@ -110,7 +109,7 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
return pds[i].Version.CreatedUnix < pds[j].Version.CreatedUnix return pds[i].Version.CreatedUnix < pds[j].Version.CreatedUnix
}) })
xmlMetadata, err := xml.Marshal(createMetadataResponse(pds)) xmlMetadata, err := xml.Marshal(createMetadataResponse(pds, params.GroupID, params.ArtifactID))
if err != nil { if err != nil {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
return return

View File

@ -7,8 +7,6 @@
// This documentation describes the Gitea API. // This documentation describes the Gitea API.
// //
// Schemes: https, http // Schemes: https, http
// BasePath: /api/v1
// Version: {{AppVer | JSEscape}}
// License: MIT http://opensource.org/licenses/MIT // License: MIT http://opensource.org/licenses/MIT
// //
// Consumes: // Consumes:

View File

@ -7,6 +7,7 @@ package repo
import ( import (
"errors" "errors"
"net/http" "net/http"
"strings"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access" access_model "code.gitea.io/gitea/models/perm/access"
@ -274,12 +275,13 @@ func GetRepoPermissions(ctx *context.APIContext) {
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
if !ctx.Doer.IsAdmin && ctx.Doer.LoginName != ctx.PathParam("collaborator") && !ctx.IsUserRepoAdmin() { collaboratorUsername := ctx.PathParam("collaborator")
if !ctx.Doer.IsAdmin && ctx.Doer.LowerName != strings.ToLower(collaboratorUsername) && !ctx.IsUserRepoAdmin() {
ctx.APIError(http.StatusForbidden, "Only admins can query all permissions, repo admins can query all repo permissions, collaborators can query only their own") ctx.APIError(http.StatusForbidden, "Only admins can query all permissions, repo admins can query all repo permissions, collaborators can query only their own")
return return
} }
collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator")) collaborator, err := user_model.GetUserByName(ctx, collaboratorUsername)
if err != nil { if err != nil {
if user_model.IsErrUserNotExist(err) { if user_model.IsErrUserNotExist(err) {
ctx.APIError(http.StatusNotFound, err) ctx.APIError(http.StatusNotFound, err)

View File

@ -59,6 +59,10 @@ func ListPullRequests(ctx *context.APIContext) {
// description: Name of the repo // description: Name of the repo
// type: string // type: string
// required: true // required: true
// - name: base_branch
// in: query
// description: Filter by target base branch of the pull request
// type: string
// - name: state // - name: state
// in: query // in: query
// description: State of pull request // description: State of pull request
@ -132,6 +136,7 @@ func ListPullRequests(ctx *context.APIContext) {
Labels: labelIDs, Labels: labelIDs,
MilestoneID: ctx.FormInt64("milestone"), MilestoneID: ctx.FormInt64("milestone"),
PosterID: posterID, PosterID: posterID,
BaseBranch: ctx.FormTrim("base_branch"),
}) })
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)

View File

@ -52,13 +52,22 @@ func generateMockStepsLog(logCur actions.LogCursor) (stepsLog []*actions.ViewSte
return stepsLog return stepsLog
} }
func MockActionsRunsJobs(ctx *context.Context) { func MockActionsView(ctx *context.Context) {
req := web.GetForm(ctx).(*actions.ViewRequest) ctx.Data["RunID"] = ctx.PathParam("run")
ctx.Data["JobID"] = ctx.PathParam("job")
ctx.HTML(http.StatusOK, "devtest/repo-action-view")
}
func MockActionsRunsJobs(ctx *context.Context) {
runID := ctx.PathParamInt64("run")
req := web.GetForm(ctx).(*actions.ViewRequest)
resp := &actions.ViewResponse{} resp := &actions.ViewResponse{}
resp.State.Run.TitleHTML = `mock run title <a href="/">link</a>` resp.State.Run.TitleHTML = `mock run title <a href="/">link</a>`
resp.State.Run.Status = actions_model.StatusRunning.String() resp.State.Run.Status = actions_model.StatusRunning.String()
resp.State.Run.CanCancel = true resp.State.Run.CanCancel = runID == 10
resp.State.Run.CanApprove = runID == 20
resp.State.Run.CanRerun = runID == 30
resp.State.Run.CanDeleteArtifact = true resp.State.Run.CanDeleteArtifact = true
resp.State.Run.WorkflowID = "workflow-id" resp.State.Run.WorkflowID = "workflow-id"
resp.State.Run.WorkflowLink = "./workflow-link" resp.State.Run.WorkflowLink = "./workflow-link"
@ -85,6 +94,29 @@ func MockActionsRunsJobs(ctx *context.Context) {
Size: 1024 * 1024, Size: 1024 * 1024,
Status: "completed", Status: "completed",
}) })
resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{
ID: runID * 10,
Name: "job 100",
Status: actions_model.StatusRunning.String(),
CanRerun: true,
Duration: "1h",
})
resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{
ID: runID*10 + 1,
Name: "job 101",
Status: actions_model.StatusWaiting.String(),
CanRerun: false,
Duration: "2h",
})
resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{
ID: runID*10 + 2,
Name: "job 102",
Status: actions_model.StatusFailure.String(),
CanRerun: false,
Duration: "3h",
})
resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &actions.ViewJobStep{ resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &actions.ViewJobStep{
Summary: "step 0 (mock slow)", Summary: "step 0 (mock slow)",
Duration: time.Hour.String(), Duration: time.Hour.String(),

View File

@ -344,18 +344,30 @@ func Diff(ctx *context.Context) {
ctx.Data["Reponame"] = repoName ctx.Data["Reponame"] = repoName
var parentCommit *git.Commit var parentCommit *git.Commit
var parentCommitID string
if commit.ParentCount() > 0 { if commit.ParentCount() > 0 {
parentCommit, err = gitRepo.GetCommit(parents[0]) parentCommit, err = gitRepo.GetCommit(parents[0])
if err != nil { if err != nil {
ctx.NotFound(err) ctx.NotFound(err)
return return
} }
parentCommitID = parentCommit.ID.String()
} }
setCompareContext(ctx, parentCommit, commit, userName, repoName) setCompareContext(ctx, parentCommit, commit, userName, repoName)
ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitID) ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitID)
ctx.Data["Commit"] = commit ctx.Data["Commit"] = commit
ctx.Data["Diff"] = diff ctx.Data["Diff"] = diff
if !fileOnly {
diffTree, err := gitdiff.GetDiffTree(ctx, gitRepo, false, parentCommitID, commitID)
if err != nil {
ctx.ServerError("GetDiffTree", err)
return
}
ctx.PageData["DiffFiles"] = transformDiffTreeForUI(diffTree, nil)
}
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll) statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll)
if err != nil { if err != nil {
log.Error("GetLatestCommitStatus: %v", err) log.Error("GetLatestCommitStatus: %v", err)

View File

@ -633,6 +633,16 @@ func PrepareCompareDiff(
ctx.Data["Diff"] = diff ctx.Data["Diff"] = diff
ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0 ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0
if !fileOnly {
diffTree, err := gitdiff.GetDiffTree(ctx, ci.HeadGitRepo, false, beforeCommitID, headCommitID)
if err != nil {
ctx.ServerError("GetDiffTree", err)
return false
}
ctx.PageData["DiffFiles"] = transformDiffTreeForUI(diffTree, nil)
}
headCommit, err := ci.HeadGitRepo.GetCommit(headCommitID) headCommit, err := ci.HeadGitRepo.GetCommit(headCommitID)
if err != nil { if err != nil {
ctx.ServerError("GetCommit", err) ctx.ServerError("GetCommit", err)

View File

@ -78,7 +78,7 @@ func httpBase(ctx *context.Context) *serviceHandler {
strings.HasSuffix(ctx.Req.URL.Path, "git-upload-archive") { strings.HasSuffix(ctx.Req.URL.Path, "git-upload-archive") {
isPull = true isPull = true
} else { } else {
isPull = ctx.Req.Method == "GET" isPull = ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET"
} }
var accessMode perm.AccessMode var accessMode perm.AccessMode

View File

@ -761,6 +761,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
var methodWithError string var methodWithError string
var diff *gitdiff.Diff var diff *gitdiff.Diff
shouldGetUserSpecificDiff := false
// if we're not logged in or only a single commit (or commit range) is shown we // if we're not logged in or only a single commit (or commit range) is shown we
// have to load only the diff and not get the viewed information // have to load only the diff and not get the viewed information
@ -772,6 +773,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
} else { } else {
diff, err = gitdiff.SyncAndGetUserSpecificDiff(ctx, ctx.Doer.ID, pull, gitRepo, diffOptions, files...) diff, err = gitdiff.SyncAndGetUserSpecificDiff(ctx, ctx.Doer.ID, pull, gitRepo, diffOptions, files...)
methodWithError = "SyncAndGetUserSpecificDiff" methodWithError = "SyncAndGetUserSpecificDiff"
shouldGetUserSpecificDiff = true
} }
if err != nil { if err != nil {
ctx.ServerError(methodWithError, err) ctx.ServerError(methodWithError, err)
@ -816,6 +818,27 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
} }
} }
if !fileOnly {
// note: use mergeBase is set to false because we already have the merge base from the pull request info
diffTree, err := gitdiff.GetDiffTree(ctx, gitRepo, false, pull.MergeBase, headCommitID)
if err != nil {
ctx.ServerError("GetDiffTree", err)
return
}
filesViewedState := make(map[string]pull_model.ViewedState)
if shouldGetUserSpecificDiff {
// This sort of sucks because we already fetch this when getting the diff
review, err := pull_model.GetNewestReviewState(ctx, ctx.Doer.ID, issue.ID)
if err == nil && review != nil && review.UpdatedFiles != nil {
// If there wasn't an error and we have a review with updated files, use that
filesViewedState = review.UpdatedFiles
}
}
ctx.PageData["DiffFiles"] = transformDiffTreeForUI(diffTree, filesViewedState)
}
ctx.Data["Diff"] = diff ctx.Data["Diff"] = diff
ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0 ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0

View File

@ -7,9 +7,11 @@ import (
"errors" "errors"
"net/http" "net/http"
pull_model "code.gitea.io/gitea/models/pull"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/gitdiff"
files_service "code.gitea.io/gitea/services/repository/files" files_service "code.gitea.io/gitea/services/repository/files"
"github.com/go-enry/go-enry/v2" "github.com/go-enry/go-enry/v2"
@ -55,6 +57,36 @@ func isExcludedEntry(entry *git.TreeEntry) bool {
return false return false
} }
type FileDiffFile struct {
Name string
NameHash string
IsSubmodule bool
IsViewed bool
Status string
}
// transformDiffTreeForUI transforms a DiffTree into a slice of FileDiffFile for UI rendering
// it also takes a map of file names to their viewed state, which is used to mark files as viewed
func transformDiffTreeForUI(diffTree *gitdiff.DiffTree, filesViewedState map[string]pull_model.ViewedState) []FileDiffFile {
files := make([]FileDiffFile, 0, len(diffTree.Files))
for _, file := range diffTree.Files {
nameHash := git.HashFilePathForWebUI(file.HeadPath)
isSubmodule := file.HeadMode == git.EntryModeCommit
isViewed := filesViewedState[file.HeadPath] == pull_model.Viewed
files = append(files, FileDiffFile{
Name: file.HeadPath,
NameHash: nameHash,
IsSubmodule: isSubmodule,
IsViewed: isViewed,
Status: file.Status,
})
}
return files
}
func Tree(ctx *context.Context) { func Tree(ctx *context.Context) {
recursive := ctx.FormBool("recursive") recursive := ctx.FormBool("recursive")

View File

@ -6,12 +6,14 @@ package setting
import ( import (
"net/http" "net/http"
"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"
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/templates" "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
@ -39,18 +41,29 @@ func ApplicationsPost(ctx *context.Context) {
ctx.Data["PageIsSettingsApplications"] = true ctx.Data["PageIsSettingsApplications"] = true
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
if ctx.HasError() { _ = ctx.Req.ParseForm()
loadApplicationsData(ctx) var scopeNames []string
for k, v := range ctx.Req.Form {
ctx.HTML(http.StatusOK, tplSettingsApplications) if strings.HasPrefix(k, "scope-") {
return scopeNames = append(scopeNames, v...)
}
} }
scope, err := form.GetScope() scope, err := auth_model.AccessTokenScope(strings.Join(scopeNames, ",")).Normalize()
if err != nil { if err != nil {
ctx.ServerError("GetScope", err) ctx.ServerError("GetScope", err)
return return
} }
if scope == "" || scope == auth_model.AccessTokenScopePublicOnly {
ctx.Flash.Error(ctx.Tr("settings.at_least_one_permission"), true)
}
if ctx.HasError() {
loadApplicationsData(ctx)
ctx.HTML(http.StatusOK, tplSettingsApplications)
return
}
t := &auth_model.AccessToken{ t := &auth_model.AccessToken{
UID: ctx.Doer.ID, UID: ctx.Doer.ID,
Name: form.Name, Name: form.Name,
@ -99,7 +112,14 @@ func loadApplicationsData(ctx *context.Context) {
} }
ctx.Data["Tokens"] = tokens ctx.Data["Tokens"] = tokens
ctx.Data["EnableOAuth2"] = setting.OAuth2.Enabled ctx.Data["EnableOAuth2"] = setting.OAuth2.Enabled
ctx.Data["IsAdmin"] = ctx.Doer.IsAdmin
// Handle specific ordered token categories for admin or non-admin users
tokenCategoryNames := auth_model.GetAccessTokenCategories()
if !ctx.Doer.IsAdmin {
tokenCategoryNames = util.SliceRemoveAll(tokenCategoryNames, "admin")
}
ctx.Data["TokenCategories"] = tokenCategoryNames
if setting.OAuth2.Enabled { if setting.OAuth2.Enabled {
ctx.Data["Applications"], err = db.Find[auth_model.OAuth2Application](ctx, auth_model.FindOAuth2ApplicationsOptions{ ctx.Data["Applications"], err = db.Find[auth_model.OAuth2Application](ctx, auth_model.FindOAuth2ApplicationsOptions{
OwnerID: ctx.Doer.ID, OwnerID: ctx.Doer.ID,

View File

@ -1640,6 +1640,7 @@ func registerRoutes(m *web.Router) {
m.Any("", devtest.List) m.Any("", devtest.List)
m.Any("/fetch-action-test", devtest.FetchActionTest) m.Any("/fetch-action-test", devtest.FetchActionTest)
m.Any("/{sub}", devtest.Tmpl) m.Any("/{sub}", devtest.Tmpl)
m.Get("/repo-action-view/{run}/{job}", devtest.MockActionsView)
m.Post("/actions-mock/runs/{run}/jobs/{job}", web.Bind(actions.ViewRequest{}), devtest.MockActionsRunsJobs) m.Post("/actions-mock/runs/{run}/jobs/{job}", web.Bind(actions.ViewRequest{}), devtest.MockActionsRunsJobs)
}) })
} }

View File

@ -291,6 +291,11 @@ func RepoRefForAPI(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := GetAPIContext(req) ctx := GetAPIContext(req)
if ctx.Repo.Repository.IsEmpty {
ctx.APIErrorNotFound("repository is empty")
return
}
if ctx.Repo.GitRepo == nil { if ctx.Repo.GitRepo == nil {
ctx.APIErrorInternal(fmt.Errorf("no open git repo")) ctx.APIErrorInternal(fmt.Errorf("no open git repo"))
return return

View File

@ -213,13 +213,16 @@ func Contexter() func(next http.Handler) http.Handler {
// Attention: this function changes ctx.Data and ctx.Flash // Attention: this function changes ctx.Data and ctx.Flash
// If HasError is called, then before Redirect, the error message should be stored by ctx.Flash.Error(ctx.GetErrMsg()) again. // If HasError is called, then before Redirect, the error message should be stored by ctx.Flash.Error(ctx.GetErrMsg()) again.
func (ctx *Context) HasError() bool { func (ctx *Context) HasError() bool {
hasErr, ok := ctx.Data["HasError"] hasErr, _ := ctx.Data["HasError"].(bool)
if !ok { hasErr = hasErr || ctx.Flash.ErrorMsg != ""
if !hasErr {
return false return false
} }
if ctx.Flash.ErrorMsg == "" {
ctx.Flash.ErrorMsg = ctx.GetErrMsg() ctx.Flash.ErrorMsg = ctx.GetErrMsg()
}
ctx.Data["Flash"] = ctx.Flash ctx.Data["Flash"] = ctx.Flash
return hasErr.(bool) return hasErr
} }
// GetErrMsg returns error message in form validation. // GetErrMsg returns error message in form validation.

View File

@ -7,9 +7,7 @@ package forms
import ( import (
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"strings"
auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/modules/web/middleware"
@ -348,7 +346,6 @@ func (f *EditVariableForm) Validate(req *http.Request, errs binding.Errors) bind
// NewAccessTokenForm form for creating access token // NewAccessTokenForm form for creating access token
type NewAccessTokenForm struct { type NewAccessTokenForm struct {
Name string `binding:"Required;MaxSize(255)" locale:"settings.token_name"` Name string `binding:"Required;MaxSize(255)" locale:"settings.token_name"`
Scope []string
} }
// Validate validates the fields // Validate validates the fields
@ -357,12 +354,6 @@ func (f *NewAccessTokenForm) Validate(req *http.Request, errs binding.Errors) bi
return middleware.Validate(errs, ctx.Data, f, ctx.Locale) return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
} }
func (f *NewAccessTokenForm) GetScope() (auth_model.AccessTokenScope, error) {
scope := strings.Join(f.Scope, ",")
s, err := auth_model.AccessTokenScope(scope).Normalize()
return s, err
}
// EditOAuth2ApplicationForm form for editing oauth2 applications // EditOAuth2ApplicationForm form for editing oauth2 applications
type EditOAuth2ApplicationForm struct { type EditOAuth2ApplicationForm struct {
Name string `binding:"Required;MaxSize(255)" form:"application_name"` Name string `binding:"Required;MaxSize(255)" form:"application_name"`

View File

@ -4,10 +4,8 @@
package forms package forms
import ( import (
"strconv"
"testing" "testing"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/gobwas/glob" "github.com/gobwas/glob"
@ -104,28 +102,3 @@ func TestRegisterForm_IsDomainAllowed_BlockedEmail(t *testing.T) {
assert.Equal(t, v.valid, form.IsEmailDomainAllowed()) assert.Equal(t, v.valid, form.IsEmailDomainAllowed())
} }
} }
func TestNewAccessTokenForm_GetScope(t *testing.T) {
tests := []struct {
form NewAccessTokenForm
scope auth_model.AccessTokenScope
expectedErr error
}{
{
form: NewAccessTokenForm{Name: "test", Scope: []string{"read:repository"}},
scope: "read:repository",
},
{
form: NewAccessTokenForm{Name: "test", Scope: []string{"read:repository", "write:user"}},
scope: "read:repository,write:user",
},
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
scope, err := test.form.GetScope()
assert.Equal(t, test.expectedErr, err)
assert.Equal(t, test.scope, scope)
})
}
}

View File

@ -44,10 +44,10 @@ func UnlinkFromRepository(ctx context.Context, pkg *packages_model.Package, doer
} }
repo, err := repo_model.GetRepositoryByID(ctx, pkg.RepoID) repo, err := repo_model.GetRepositoryByID(ctx, pkg.RepoID)
if err != nil { if err != nil && !repo_model.IsErrRepoNotExist(err) {
return fmt.Errorf("error getting repository %d: %w", pkg.RepoID, err) return fmt.Errorf("error getting repository %d: %w", pkg.RepoID, err)
} }
if err == nil {
perms, err := access_model.GetUserRepoPermission(ctx, repo, doer) perms, err := access_model.GetUserRepoPermission(ctx, repo, doer)
if err != nil { if err != nil {
return fmt.Errorf("error getting permissions for user %d on repository %d: %w", doer.ID, repo.ID, err) return fmt.Errorf("error getting permissions for user %d on repository %d: %w", doer.ID, repo.ID, err)
@ -55,6 +55,7 @@ func UnlinkFromRepository(ctx context.Context, pkg *packages_model.Package, doer
if !perms.CanWrite(unit.TypePackages) { if !perms.CanWrite(unit.TypePackages) {
return util.ErrPermissionDenied return util.ErrPermissionDenied
} }
}
user, err := user_model.GetUserByID(ctx, pkg.OwnerID) user, err := user_model.GetUserByID(ctx, pkg.OwnerID)
if err != nil { if err != nil {

View File

@ -407,11 +407,10 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string,
} }
if isSync { if isSync {
requests := issues_model.PullRequestList(prs) if err = 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, repoID, doer, branch); invalidationErr != nil {
log.Error("checkForInvalidation: %v", invalidationErr) log.Error("checkForInvalidation: %v", invalidationErr)
} }
if err == nil { if err == nil {
@ -645,7 +644,7 @@ func retargetBranchPulls(ctx context.Context, doer *user_model.User, repoID int6
return err return err
} }
if err := issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil { if err := prs.LoadAttributes(ctx); err != nil {
return err return err
} }
@ -672,11 +671,11 @@ func AdjustPullsCausedByBranchDeleted(ctx context.Context, doer *user_model.User
return err return err
} }
if err := issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil { if err := prs.LoadAttributes(ctx); err != nil {
return err return err
} }
issues_model.PullRequestList(prs).SetHeadRepo(repo) prs.SetHeadRepo(repo)
if err := issues_model.PullRequestList(prs).LoadRepositories(ctx); err != nil { if err := prs.LoadRepositories(ctx); err != nil {
return err return err
} }
@ -707,11 +706,11 @@ func AdjustPullsCausedByBranchDeleted(ctx context.Context, doer *user_model.User
return err return err
} }
if err := issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil { if err := prs.LoadAttributes(ctx); err != nil {
return err return err
} }
issues_model.PullRequestList(prs).SetBaseRepo(repo) prs.SetBaseRepo(repo)
if err := issues_model.PullRequestList(prs).LoadRepositories(ctx); err != nil { if err := prs.LoadRepositories(ctx); err != nil {
return err return err
} }
@ -744,7 +743,7 @@ func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *re
return err return err
} }
if err = issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil { if err = prs.LoadAttributes(ctx); err != nil {
return err return err
} }

View File

@ -14,6 +14,7 @@ import (
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
packages_model "code.gitea.io/gitea/models/packages"
access_model "code.gitea.io/gitea/models/perm/access" access_model "code.gitea.io/gitea/models/perm/access"
project_model "code.gitea.io/gitea/models/project" project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
@ -267,6 +268,11 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID
return err return err
} }
// unlink packages linked to this repository
if err = packages_model.UnlinkRepositoryFromAllPackages(ctx, repoID); err != nil {
return err
}
if err = committer.Commit(); err != nil { if err = committer.Commit(); err != nil {
return err return err
} }

View File

@ -11,7 +11,6 @@ import (
"code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
packages_model "code.gitea.io/gitea/models/packages"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
system_model "code.gitea.io/gitea/models/system" system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
@ -63,11 +62,7 @@ func DeleteRepository(ctx context.Context, doer *user_model.User, repo *repo_mod
notify_service.DeleteRepository(ctx, doer, repo) notify_service.DeleteRepository(ctx, doer, repo)
} }
if err := DeleteRepositoryDirectly(ctx, doer, repo.ID); err != nil { return DeleteRepositoryDirectly(ctx, doer, repo.ID)
return err
}
return packages_model.UnlinkRepositoryFromAllPackages(ctx, repo.ID)
} }
// PushCreateRepo creates a repository when a new repository is pushed to an appropriate namespace // PushCreateRepo creates a repository when a new repository is pushed to an appropriate namespace

View File

@ -18,6 +18,7 @@ import (
"sync" "sync"
"time" "time"
user_model "code.gitea.io/gitea/models/user"
webhook_model "code.gitea.io/gitea/models/webhook" webhook_model "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/hostmatcher" "code.gitea.io/gitea/modules/hostmatcher"
@ -92,10 +93,10 @@ func newDefaultRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook
} }
body = []byte(t.PayloadContent) body = []byte(t.PayloadContent)
return req, body, addDefaultHeaders(req, []byte(w.Secret), t, body) return req, body, addDefaultHeaders(req, []byte(w.Secret), w, t, body)
} }
func addDefaultHeaders(req *http.Request, secret []byte, t *webhook_model.HookTask, payloadContent []byte) error { func addDefaultHeaders(req *http.Request, secret []byte, w *webhook_model.Webhook, t *webhook_model.HookTask, payloadContent []byte) error {
var signatureSHA1 string var signatureSHA1 string
var signatureSHA256 string var signatureSHA256 string
if len(secret) > 0 { if len(secret) > 0 {
@ -112,10 +113,27 @@ func addDefaultHeaders(req *http.Request, secret []byte, t *webhook_model.HookTa
event := t.EventType.Event() event := t.EventType.Event()
eventType := string(t.EventType) eventType := string(t.EventType)
targetType := "default"
if w.IsSystemWebhook {
targetType = "system"
} else if w.RepoID != 0 {
targetType = "repository"
} else if w.OwnerID != 0 {
owner, err := user_model.GetUserByID(req.Context(), w.OwnerID)
if owner != nil && err == nil {
if owner.IsOrganization() {
targetType = "organization"
} else {
targetType = "user"
}
}
}
req.Header.Add("X-Gitea-Delivery", t.UUID) req.Header.Add("X-Gitea-Delivery", t.UUID)
req.Header.Add("X-Gitea-Event", event) req.Header.Add("X-Gitea-Event", event)
req.Header.Add("X-Gitea-Event-Type", eventType) req.Header.Add("X-Gitea-Event-Type", eventType)
req.Header.Add("X-Gitea-Signature", signatureSHA256) req.Header.Add("X-Gitea-Signature", signatureSHA256)
req.Header.Add("X-Gitea-Hook-Installation-Target-Type", targetType)
req.Header.Add("X-Gogs-Delivery", t.UUID) req.Header.Add("X-Gogs-Delivery", t.UUID)
req.Header.Add("X-Gogs-Event", event) req.Header.Add("X-Gogs-Event", event)
req.Header.Add("X-Gogs-Event-Type", eventType) req.Header.Add("X-Gogs-Event-Type", eventType)
@ -125,6 +143,7 @@ func addDefaultHeaders(req *http.Request, secret []byte, t *webhook_model.HookTa
req.Header["X-GitHub-Delivery"] = []string{t.UUID} req.Header["X-GitHub-Delivery"] = []string{t.UUID}
req.Header["X-GitHub-Event"] = []string{event} req.Header["X-GitHub-Event"] = []string{event}
req.Header["X-GitHub-Event-Type"] = []string{eventType} req.Header["X-GitHub-Event-Type"] = []string{eventType}
req.Header["X-GitHub-Hook-Installation-Target-Type"] = []string{targetType}
return nil return nil
} }

View File

@ -56,7 +56,7 @@ func newMatrixRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_mo
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
return req, body, addDefaultHeaders(req, []byte(w.Secret), t, body) // likely useless, but has always been sent historially return req, body, addDefaultHeaders(req, []byte(w.Secret), w, t, body) // likely useless, but has always been sent historially
} }
const matrixPayloadSizeLimit = 1024 * 64 const matrixPayloadSizeLimit = 1024 * 64

View File

@ -107,7 +107,7 @@ func newJSONRequest[T any](pc payloadConvertor[T], w *webhook_model.Webhook, t *
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
if withDefaultHeaders { if withDefaultHeaders {
return req, body, addDefaultHeaders(req, []byte(w.Secret), t, body) return req, body, addDefaultHeaders(req, []byte(w.Secret), w, t, body)
} }
return req, body, nil return req, body, nil
} }

View File

@ -9,30 +9,25 @@
{{if .User.IsAdmin}} {{if .User.IsAdmin}}
<span class="ui basic label">{{ctx.Locale.Tr "admin.users.admin"}}</span> <span class="ui basic label">{{ctx.Locale.Tr "admin.users.admin"}}</span>
{{end}} {{end}}
{{if .User.IsTypeBot}}
<span class="ui basic label">{{ctx.Locale.Tr "admin.users.bot"}}</span>
{{end}}
</div> </div>
<div class="flex-item-body"> <div class="flex-item-body">
<b>{{ctx.Locale.Tr "admin.users.auth_source"}}:</b> <b>{{ctx.Locale.Tr "admin.users.auth_source"}}:</b>
{{if eq .LoginSource.ID 0}} {{Iif (eq .LoginSource.ID 0) (ctx.Locale.Tr "admin.users.local") .LoginSource.Name}}
{{ctx.Locale.Tr "admin.users.local"}}
{{else}}
{{.LoginSource.Name}}
{{end}}
</div> </div>
<div class="flex-item-body"> <div class="flex-item-body">
<b>{{ctx.Locale.Tr "admin.users.activated"}}:</b> <b>{{ctx.Locale.Tr "admin.users.activated"}}:</b>
{{if .User.IsActive}} {{svg (Iif .User.IsActive "octicon-check" "octicon-x")}}
{{svg "octicon-check"}} </div>
{{else}} <div class="flex-item-body">
{{svg "octicon-x"}} <b>{{ctx.Locale.Tr "admin.users.prohibit_login"}}:</b>
{{end}} {{svg (Iif .User.ProhibitLogin "octicon-check" "octicon-x")}}
</div> </div>
<div class="flex-item-body"> <div class="flex-item-body">
<b>{{ctx.Locale.Tr "admin.users.restricted"}}:</b> <b>{{ctx.Locale.Tr "admin.users.restricted"}}:</b>
{{if .User.IsRestricted}} {{svg (Iif .User.IsRestricted "octicon-check" "octicon-x")}}
{{svg "octicon-check"}}
{{else}}
{{svg "octicon-x"}}
{{end}}
</div> </div>
<div class="flex-item-body"> <div class="flex-item-body">
<b>{{ctx.Locale.Tr "settings.visibility"}}:</b> <b>{{ctx.Locale.Tr "settings.visibility"}}:</b>
@ -42,11 +37,7 @@
</div> </div>
<div class="flex-item-body"> <div class="flex-item-body">
<b>{{ctx.Locale.Tr "admin.users.2fa"}}:</b> <b>{{ctx.Locale.Tr "admin.users.2fa"}}:</b>
{{if .TwoFactorEnabled}} {{svg (Iif .TwoFactorEnabled "octicon-check" "octicon-x")}}
<span class="text green">{{svg "octicon-check"}}</span>
{{else}}
{{svg "octicon-x"}}
{{end}}
</div> </div>
{{if .User.Language}} {{if .User.Language}}
<div class="flex-item-body"> <div class="flex-item-body">

View File

@ -3,7 +3,7 @@
<div class="flex-item"> <div class="flex-item">
<div class="flex-item-main"> <div class="flex-item-main">
<div class="flex-text-block"> <div class="flex-text-block">
{{.Email}} <a href="mailto:{{.Email}}">{{.Email}}</a>
{{if .IsPrimary}} {{if .IsPrimary}}
<div class="ui primary label">{{ctx.Locale.Tr "settings.primary"}}</div> <div class="ui primary label">{{ctx.Locale.Tr "settings.primary"}}</div>
{{end}} {{end}}

View File

@ -1,20 +1,20 @@
{{if .Flash.ErrorMsg}} {{- if .Flash.ErrorMsg -}}
<div class="ui negative message flash-message flash-error"> <div class="ui negative message flash-message flash-error">
<p>{{.Flash.ErrorMsg | SanitizeHTML}}</p> <p>{{.Flash.ErrorMsg | SanitizeHTML}}</p>
</div> </div>
{{end}} {{- end -}}
{{if .Flash.SuccessMsg}} {{- if .Flash.SuccessMsg -}}
<div class="ui positive message flash-message flash-success"> <div class="ui positive message flash-message flash-success">
<p>{{.Flash.SuccessMsg | SanitizeHTML}}</p> <p>{{.Flash.SuccessMsg | SanitizeHTML}}</p>
</div> </div>
{{end}} {{- end -}}
{{if .Flash.InfoMsg}} {{- if .Flash.InfoMsg -}}
<div class="ui info message flash-message flash-info"> <div class="ui info message flash-message flash-info">
<p>{{.Flash.InfoMsg | SanitizeHTML}}</p> <p>{{.Flash.InfoMsg | SanitizeHTML}}</p>
</div> </div>
{{end}} {{- end -}}
{{if .Flash.WarningMsg}} {{- if .Flash.WarningMsg -}}
<div class="ui warning message flash-message flash-warning"> <div class="ui warning message flash-message flash-warning">
<p>{{.Flash.WarningMsg | SanitizeHTML}}</p> <p>{{.Flash.WarningMsg | SanitizeHTML}}</p>
</div> </div>
{{end}} {{- end -}}

View File

@ -17,7 +17,7 @@
{{if eq .Num -1}} {{if eq .Num -1}}
<a class="disabled item">...</a> <a class="disabled item">...</a>
{{else}} {{else}}
<a class="{{if .IsCurrent}}active {{end}}item tw-content-center" {{if not .IsCurrent}}href="{{$paginationLink}}?page={{.Num}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>{{.Num}}</a> <a class="{{if .IsCurrent}}active {{end}}item" {{if not .IsCurrent}}href="{{$paginationLink}}?page={{.Num}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>{{.Num}}</a>
{{end}} {{end}}
{{end}} {{end}}
<a class="{{if not .HasNext}}disabled{{end}} item navigation" {{if .HasNext}}href="{{$paginationLink}}?page={{.Next}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}> <a class="{{if not .HasNext}}disabled{{end}} item navigation" {{if .HasNext}}href="{{$paginationLink}}?page={{.Next}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>

View File

@ -1,8 +1,13 @@
{{template "base/head" .}} {{template "base/head" .}}
<div class="page-content"> <div class="page-content">
<div class="tw-flex tw-justify-center tw-items-center tw-gap-5">
<a href="/devtest/repo-action-view/10/100">Run:CanCancel</a>
<a href="/devtest/repo-action-view/20/200">Run:CanApprove</a>
<a href="/devtest/repo-action-view/30/300">Run:CanRerun</a>
</div>
{{template "repo/actions/view_component" (dict {{template "repo/actions/view_component" (dict
"RunIndex" 1 "RunIndex" (or .RunID 10)
"JobIndex" 2 "JobIndex" (or .JobID 100)
"ActionsURL" (print AppSubUrl "/devtest/actions-mock") "ActionsURL" (print AppSubUrl "/devtest/actions-mock")
)}} )}}
</div> </div>

View File

@ -14,6 +14,7 @@
{{if $.CloneButtonShowSSH}} {{if $.CloneButtonShowSSH}}
<button class="item repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}">SSH</button> <button class="item repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}">SSH</button>
{{end}} {{end}}
<button class="item repo-clone-tea" data-link="{{$.CloneButtonOriginLink.Tea}}">Tea CLI</button>
</div> </div>
<div class="divider"></div> <div class="divider"></div>

View File

@ -1,6 +1,6 @@
{{$showFileTree := (and (not .DiffNotAvailable) (gt .Diff.NumFiles 1))}} {{$showFileTree := (and (not .DiffNotAvailable) (gt .Diff.NumFiles 1))}}
<div> <div>
<div class="diff-detail-box diff-box"> <div class="diff-detail-box">
<div class="tw-flex tw-items-center tw-flex-wrap tw-gap-2 tw-ml-0.5"> <div class="tw-flex tw-items-center tw-flex-wrap tw-gap-2 tw-ml-0.5">
{{if $showFileTree}} {{if $showFileTree}}
<button class="diff-toggle-file-tree-button not-mobile btn interact-fg" data-show-text="{{ctx.Locale.Tr "repo.diff.show_file_tree"}}" data-hide-text="{{ctx.Locale.Tr "repo.diff.hide_file_tree"}}"> <button class="diff-toggle-file-tree-button not-mobile btn interact-fg" data-show-text="{{ctx.Locale.Tr "repo.diff.show_file_tree"}}" data-hide-text="{{ctx.Locale.Tr "repo.diff.hide_file_tree"}}">
@ -57,32 +57,6 @@
<div>{{ctx.Locale.Tr "repo.pulls.showing_specified_commit_range" (ShortSha .BeforeCommitID) (ShortSha .AfterCommitID)}} - <a href="{{$.Issue.Link}}/files?style={{if $.IsSplitStyle}}split{{else}}unified{{end}}&whitespace={{$.WhitespaceBehavior}}&show-outdated={{$.ShowOutdatedComments}}">{{ctx.Locale.Tr "repo.pulls.show_all_commits"}}</a></div> <div>{{ctx.Locale.Tr "repo.pulls.showing_specified_commit_range" (ShortSha .BeforeCommitID) (ShortSha .AfterCommitID)}} - <a href="{{$.Issue.Link}}/files?style={{if $.IsSplitStyle}}split{{else}}unified{{end}}&whitespace={{$.WhitespaceBehavior}}&show-outdated={{$.ShowOutdatedComments}}">{{ctx.Locale.Tr "repo.pulls.show_all_commits"}}</a></div>
</div> </div>
{{end}} {{end}}
<script id="diff-data-script" type="module">
const diffDataFiles = [{{range $i, $file := .Diff.Files}}{Name:"{{$file.Name}}",NameHash:"{{$file.NameHash}}",Type:{{$file.Type}},IsBin:{{$file.IsBin}},IsSubmodule:{{$file.IsSubmodule}},Addition:{{$file.Addition}},Deletion:{{$file.Deletion}},IsViewed:{{$file.IsViewed}}},{{end}}];
const diffData = {
isIncomplete: {{.Diff.IsIncomplete}},
tooManyFilesMessage: "{{ctx.Locale.Tr "repo.diff.too_many_files"}}",
binaryFileMessage: "{{ctx.Locale.Tr "repo.diff.bin"}}",
showMoreMessage: "{{ctx.Locale.Tr "repo.diff.show_more"}}",
statisticsMessage: "{{ctx.Locale.Tr "repo.diff.stats_desc_file"}}",
linkLoadMore: "?skip-to={{.Diff.End}}&file-only=true",
};
// for first time loading, the diffFileInfo is a plain object
// after the Vue component is mounted, the diffFileInfo is a reactive object
// keep in mind that this script block would be executed many times when loading more files, by "loadMoreFiles"
let diffFileInfo = window.config.pageData.diffFileInfo || {
files:[],
fileTreeIsVisible: false,
fileListIsVisible: false,
isLoadingNewData: false,
selectedItem: '',
};
diffFileInfo = Object.assign(diffFileInfo, diffData);
diffFileInfo.files.push(...diffDataFiles);
window.config.pageData.diffFileInfo = diffFileInfo;
</script>
<div id="diff-file-list"></div>
{{end}} {{end}}
<div id="diff-container"> <div id="diff-container">
{{if $showFileTree}} {{if $showFileTree}}
@ -106,7 +80,7 @@
{{$showFileViewToggle := or $isImage (and (not $file.IsIncomplete) $isCsv)}} {{$showFileViewToggle := or $isImage (and (not $file.IsIncomplete) $isCsv)}}
{{$isExpandable := or (gt $file.Addition 0) (gt $file.Deletion 0) $file.IsBin}} {{$isExpandable := or (gt $file.Addition 0) (gt $file.Deletion 0) $file.IsBin}}
{{$isReviewFile := and $.IsSigned $.PageIsPullFiles (not $.Repository.IsArchived) $.IsShowingAllCommits}} {{$isReviewFile := and $.IsSigned $.PageIsPullFiles (not $.Repository.IsArchived) $.IsShowingAllCommits}}
<div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}} tw-mt-0" id="diff-{{$file.NameHash}}" data-old-filename="{{$file.OldName}}" data-new-filename="{{$file.Name}}" {{if or ($file.ShouldBeHidden) (not $isExpandable)}}data-folded="true"{{end}}> <div class="diff-file-box file-content {{TabSizeClass $.Editorconfig $file.Name}} tw-mt-0" id="diff-{{$file.NameHash}}" data-old-filename="{{$file.OldName}}" data-new-filename="{{$file.Name}}" {{if or ($file.ShouldBeHidden) (not $isExpandable)}}data-folded="true"{{end}}>
<h4 class="diff-file-header sticky-2nd-row ui top attached header"> <h4 class="diff-file-header sticky-2nd-row ui top attached header">
<div class="diff-file-name tw-flex tw-flex-1 tw-items-center tw-gap-1 tw-flex-wrap"> <div class="diff-file-name tw-flex tw-flex-1 tw-items-center tw-gap-1 tw-flex-wrap">
<button class="fold-file btn interact-bg tw-p-1{{if not $isExpandable}} tw-invisible{{end}}"> <button class="fold-file btn interact-bg tw-p-1{{if not $isExpandable}} tw-invisible{{end}}">
@ -235,7 +209,7 @@
{{end}} {{end}}
{{if .Diff.IsIncomplete}} {{if .Diff.IsIncomplete}}
<div class="diff-file-box diff-box file-content tw-mt-2" id="diff-incomplete"> <div class="diff-file-box file-content tw-mt-2" id="diff-incomplete">
<h4 class="ui top attached header tw-font-normal tw-flex tw-items-center tw-justify-between"> <h4 class="ui top attached header tw-font-normal tw-flex tw-items-center tw-justify-between">
{{ctx.Locale.Tr "repo.diff.too_many_files"}} {{ctx.Locale.Tr "repo.diff.too_many_files"}}
<a class="ui basic tiny button" id="diff-show-more-files" data-href="?skip-to={{.Diff.End}}&file-only=true">{{ctx.Locale.Tr "repo.diff.show_more"}}</a> <a class="ui basic tiny button" id="diff-show-more-files" data-href="?skip-to={{.Diff.End}}&file-only=true">{{ctx.Locale.Tr "repo.diff.show_more"}}</a>

View File

@ -1,7 +1,6 @@
<div class="ui dropdown tiny basic button" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.options_button"}}"> <div class="ui dropdown tiny basic button" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.options_button"}}">
{{svg "octicon-kebab-horizontal"}} {{svg "octicon-kebab-horizontal"}}
<div class="menu"> <div class="menu">
<a class="item" id="show-file-list-btn">{{ctx.Locale.Tr "repo.diff.show_diff_stats"}}</a>
{{if .Issue.Index}} {{if .Issue.Index}}
<a class="item" href="{{$.RepoLink}}/pulls/{{.Issue.Index}}.patch" download="{{.Issue.Index}}.patch">{{ctx.Locale.Tr "repo.diff.download_patch"}}</a> <a class="item" href="{{$.RepoLink}}/pulls/{{.Issue.Index}}.patch" download="{{.Issue.Index}}.patch">{{ctx.Locale.Tr "repo.diff.download_patch"}}</a>
<a class="item" href="{{$.RepoLink}}/pulls/{{.Issue.Index}}.diff" download="{{.Issue.Index}}.diff">{{ctx.Locale.Tr "repo.diff.download_diff"}}</a> <a class="item" href="{{$.RepoLink}}/pulls/{{.Issue.Index}}.diff" download="{{.Issue.Index}}.diff">{{ctx.Locale.Tr "repo.diff.download_diff"}}</a>

View File

@ -1,6 +1,4 @@
{{if .Flash}}
{{template "base/alert" .}} {{template "base/alert" .}}
{{end}}
<form class="issue-content ui comment form form-fetch-action" id="new-issue" action="{{.Link}}" method="post"> <form class="issue-content ui comment form form-fetch-action" id="new-issue" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="issue-content-left"> <div class="issue-content-left">
@ -9,7 +7,10 @@
{{ctx.AvatarUtils.Avatar .SignedUser 40}} {{ctx.AvatarUtils.Avatar .SignedUser 40}}
<div class="ui segment content tw-my-0"> <div class="ui segment content tw-my-0">
<div class="field"> <div class="field">
<input name="title" class="js-autofocus-end" id="issue_title" placeholder="{{ctx.Locale.Tr "repo.milestones.title"}}" value="{{if .TitleQuery}}{{.TitleQuery}}{{else if .IssueTemplateTitle}}{{.IssueTemplateTitle}}{{else}}{{.title}}{{end}}" required maxlength="255" autocomplete="off"> <input name="title" data-global-init="initInputAutoFocusEnd" id="issue_title" required maxlength="255" autocomplete="off"
placeholder="{{ctx.Locale.Tr "repo.milestones.title"}}"
value="{{if .TitleQuery}}{{.TitleQuery}}{{else if .IssueTemplateTitle}}{{.IssueTemplateTitle}}{{else}}{{.title}}{{end}}"
>
{{if .PageIsComparePull}} {{if .PageIsComparePull}}
<div class="title_wip_desc" data-wip-prefixes="{{JsonUtils.EncodeToString .PullRequestWorkInProgressPrefixes}}">{{ctx.Locale.Tr "repo.pulls.title_wip_desc" (index .PullRequestWorkInProgressPrefixes 0)}}</div> <div class="title_wip_desc" data-wip-prefixes="{{JsonUtils.EncodeToString .PullRequestWorkInProgressPrefixes}}">{{ctx.Locale.Tr "repo.pulls.title_wip_desc" (index .PullRequestWorkInProgressPrefixes 0)}}</div>
{{end}} {{end}}

View File

@ -17,7 +17,7 @@
{{ctx.Locale.PrettyNumber .OpenCount}} {{ctx.Locale.Tr "repo.issues.open_title"}} {{ctx.Locale.PrettyNumber .OpenCount}} {{ctx.Locale.Tr "repo.issues.open_title"}}
</a> </a>
<a class="{{if eq .State "closed"}}active {{end}}item flex-text-inline" href="{{if eq .State "closed"}}{{$allStatesLink}}{{else}}{{$closedLink}}{{end}}"> <a class="{{if eq .State "closed"}}active {{end}}item flex-text-inline" href="{{if eq .State "closed"}}{{$allStatesLink}}{{else}}{{$closedLink}}{{end}}">
{{svg "octicon-check"}} {{svg "octicon-issue-closed"}}
{{ctx.Locale.PrettyNumber .ClosedCount}} {{ctx.Locale.Tr "repo.issues.closed_title"}} {{ctx.Locale.PrettyNumber .ClosedCount}} {{ctx.Locale.Tr "repo.issues.closed_title"}}
</a> </a>
</div> </div>

View File

@ -1,13 +1,18 @@
{{if and .Issue.IsPull .IsIssuePoster (not .Issue.IsClosed) .Issue.PullRequest.HeadRepo}} {{- $isHeadForkedRepo := and .Issue.PullRequest .Issue.PullRequest.HeadRepo (ne .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName) -}}
{{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}} {{if $isHeadForkedRepo}}
{{- $isPullPoster := and .Issue.IsPull .IsIssuePoster -}}
{{- $isPullEditable := and .Issue.PullRequest (not .Issue.IsClosed) (not .Repository.IsArchived) -}}
{{- $allowToChange := and $isPullPoster $isPullEditable -}}
<div class="divider"></div> <div class="divider"></div>
<div class="ui checkbox loading-icon-2px" id="allow-edits-from-maintainers" <div class="ui checkbox {{if not $allowToChange}}disabled{{end}} loading-icon-2px"
{{if $allowToChange}}
id="allow-edits-from-maintainers"
data-url="{{.Issue.Link}}" data-url="{{.Issue.Link}}"
data-tooltip-content="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_desc"}}" data-tooltip-content="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_desc"}}"
data-prompt-error="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_err"}}" data-prompt-error="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_err"}}"
{{end}}
> >
<label><strong>{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers"}}</strong></label> <label><strong>{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers"}}</strong></label>
<input type="checkbox" {{if .Issue.PullRequest.AllowMaintainerEdit}}checked{{end}}> <input type="checkbox" {{if .Issue.PullRequest.AllowMaintainerEdit}}checked{{end}} {{if not $allowToChange}}disabled{{end}}>
</div> </div>
{{end}}
{{end}} {{end}}

View File

@ -6,7 +6,7 @@
{{if $pageMeta.Issue}}data-update-url="{{$pageMeta.RepoLink}}/issues/assignee?issue_ids={{$pageMeta.Issue.ID}}"{{end}} {{if $pageMeta.Issue}}data-update-url="{{$pageMeta.RepoLink}}/issues/assignee?issue_ids={{$pageMeta.Issue.ID}}"{{end}}
> >
<input class="combo-value" name="assignee_ids" type="hidden" value="{{$data.SelectedAssigneeIDs}}"> <input class="combo-value" name="assignee_ids" type="hidden" value="{{$data.SelectedAssigneeIDs}}">
<div class="ui dropdown {{if not $pageMeta.CanModifyIssueOrPull}}disabled{{end}}"> <div class="ui dropdown text-flex-grow {{if not $pageMeta.CanModifyIssueOrPull}}disabled{{end}}">
<a class="text muted"> <a class="text muted">
<strong>{{ctx.Locale.Tr "repo.issues.new.assignees"}}</strong> {{if $pageMeta.CanModifyIssueOrPull}}{{svg "octicon-gear"}}{{end}} <strong>{{ctx.Locale.Tr "repo.issues.new.assignees"}}</strong> {{if $pageMeta.CanModifyIssueOrPull}}{{svg "octicon-gear"}}{{end}}
</a> </a>

View File

@ -4,7 +4,7 @@
{{if $pageMeta.Issue}}data-update-url="{{$pageMeta.RepoLink}}/issues/labels?issue_ids={{$pageMeta.Issue.ID}}"{{end}} {{if $pageMeta.Issue}}data-update-url="{{$pageMeta.RepoLink}}/issues/labels?issue_ids={{$pageMeta.Issue.ID}}"{{end}}
> >
<input class="combo-value" name="label_ids" type="hidden" value="{{$data.SelectedLabelIDs}}"> <input class="combo-value" name="label_ids" type="hidden" value="{{$data.SelectedLabelIDs}}">
<div class="ui dropdown {{if not $pageMeta.CanModifyIssueOrPull}}disabled{{end}}"> <div class="ui dropdown text-flex-grow {{if not $pageMeta.CanModifyIssueOrPull}}disabled{{end}}">
<a class="text muted"> <a class="text muted">
<strong>{{ctx.Locale.Tr "repo.issues.new.labels"}}</strong> {{if $pageMeta.CanModifyIssueOrPull}}{{svg "octicon-gear"}}{{end}} <strong>{{ctx.Locale.Tr "repo.issues.new.labels"}}</strong> {{if $pageMeta.CanModifyIssueOrPull}}{{svg "octicon-gear"}}{{end}}
</a> </a>

View File

@ -6,7 +6,7 @@
{{if $pageMeta.Issue}}data-update-url="{{$pageMeta.RepoLink}}/issues/milestone?issue_ids={{$pageMeta.Issue.ID}}"{{end}} {{if $pageMeta.Issue}}data-update-url="{{$pageMeta.RepoLink}}/issues/milestone?issue_ids={{$pageMeta.Issue.ID}}"{{end}}
> >
<input class="combo-value" name="milestone_id" type="hidden" value="{{$data.SelectedMilestoneID}}"> <input class="combo-value" name="milestone_id" type="hidden" value="{{$data.SelectedMilestoneID}}">
<div class="ui dropdown {{if not $pageMeta.CanModifyIssueOrPull}}disabled{{end}}"> <div class="ui dropdown text-flex-grow {{if not $pageMeta.CanModifyIssueOrPull}}disabled{{end}}">
<a class="text muted"> <a class="text muted">
<strong>{{ctx.Locale.Tr "repo.issues.new.milestone"}}</strong> {{if $pageMeta.CanModifyIssueOrPull}}{{svg "octicon-gear"}}{{end}} <strong>{{ctx.Locale.Tr "repo.issues.new.milestone"}}</strong> {{if $pageMeta.CanModifyIssueOrPull}}{{svg "octicon-gear"}}{{end}}
</a> </a>

View File

@ -6,7 +6,7 @@
{{if $pageMeta.Issue}}data-update-url="{{$pageMeta.RepoLink}}/issues/projects?issue_ids={{$pageMeta.Issue.ID}}"{{end}} {{if $pageMeta.Issue}}data-update-url="{{$pageMeta.RepoLink}}/issues/projects?issue_ids={{$pageMeta.Issue.ID}}"{{end}}
> >
<input class="combo-value" name="project_id" type="hidden" value="{{$data.SelectedProjectID}}"> <input class="combo-value" name="project_id" type="hidden" value="{{$data.SelectedProjectID}}">
<div class="ui dropdown {{if not $pageMeta.CanModifyIssueOrPull}}disabled{{end}}"> <div class="ui dropdown text-flex-grow {{if not $pageMeta.CanModifyIssueOrPull}}disabled{{end}}">
<a class="text muted"> <a class="text muted">
<strong>{{ctx.Locale.Tr "repo.issues.new.projects"}}</strong> {{if $pageMeta.CanModifyIssueOrPull}}{{svg "octicon-gear"}}{{end}} <strong>{{ctx.Locale.Tr "repo.issues.new.projects"}}</strong> {{if $pageMeta.CanModifyIssueOrPull}}{{svg "octicon-gear"}}{{end}}
</a> </a>

View File

@ -6,7 +6,7 @@
{{if $pageMeta.Issue}}data-update-url="{{$pageMeta.RepoLink}}/issues/request_review?issue_ids={{$pageMeta.Issue.ID}}"{{end}} {{if $pageMeta.Issue}}data-update-url="{{$pageMeta.RepoLink}}/issues/request_review?issue_ids={{$pageMeta.Issue.ID}}"{{end}}
> >
<input type="hidden" class="combo-value" name="reviewer_ids">{{/* match CreateIssueForm */}} <input type="hidden" class="combo-value" name="reviewer_ids">{{/* match CreateIssueForm */}}
<div class="ui dropdown {{if or (not $hasCandidates) (not $data.CanChooseReviewer)}}disabled{{end}}"> <div class="ui dropdown text-flex-grow {{if or (not $hasCandidates) (not $data.CanChooseReviewer)}}disabled{{end}}">
<a class="text muted"> <a class="text muted">
<strong>{{ctx.Locale.Tr "repo.issues.review.reviewers"}}</strong> {{if $data.CanChooseReviewer}}{{svg "octicon-gear"}}{{end}} <strong>{{ctx.Locale.Tr "repo.issues.review.reviewers"}}</strong> {{if $data.CanChooseReviewer}}{{svg "octicon-gear"}}{{end}}
</a> </a>

View File

@ -2,10 +2,12 @@
{{if and .CanUseTimetracker (not .Repository.IsArchived)}} {{if and .CanUseTimetracker (not .Repository.IsArchived)}}
<div class="divider"></div> <div class="divider"></div>
<div> <div>
<div class="ui dropdown jump"> <div class="ui dropdown text-flex-grow jump">
<a class="text muted"> <a class="text muted">
<strong>{{ctx.Locale.Tr "repo.issues.tracker"}}</strong> {{svg "octicon-gear"}} <div>
{{if $.IsStopwatchRunning}}{{svg "octicon-stopwatch"}}{{end}} <strong>{{ctx.Locale.Tr "repo.issues.tracker"}}</strong> {{if $.IsStopwatchRunning}}{{svg "octicon-stopwatch"}}{{end}}
</div>
{{svg "octicon-gear"}}
</a> </a>
<div class="menu"> <div class="menu">
<a class="item issue-set-time-estimate show-modal" data-modal="#issue-time-set-estimate-modal"> <a class="item issue-set-time-estimate show-modal" data-modal="#issue-time-set-estimate-modal">

View File

@ -3,7 +3,7 @@
<a class="muted">{{svg "octicon-smiley"}}</a> <a class="muted">{{svg "octicon-smiley"}}</a>
<div class="menu"> <div class="menu">
{{range $value := AllowedReactions}} {{range $value := AllowedReactions}}
<a class="item emoji comment-reaction-button" data-tooltip-content="{{$value}}" aria-label="{{$value}}" data-reaction-content="{{$value}}">{{ReactionToEmoji $value}}</a> <a class="item emoji" data-tooltip-content="{{$value}}" aria-label="{{$value}}" data-reaction-content="{{$value}}" data-global-click="onCommentReactionButtonClick">{{ReactionToEmoji $value}}</a>
{{end}} {{end}}
</div> </div>
</div> </div>

View File

@ -40,7 +40,7 @@
{{if $diff}} {{if $diff}}
{{$file := (index $diff.Files 0)}} {{$file := (index $diff.Files 0)}}
<div id="code-preview-{{$comment.ID}}" class="ui table segment{{if $resolved}} tw-hidden{{end}}"> <div id="code-preview-{{$comment.ID}}" class="ui table segment{{if $resolved}} tw-hidden{{end}}">
<div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}}"> <div class="diff-file-box file-content {{TabSizeClass $.Editorconfig $file.Name}}">
<div class="file-body file-code code-view code-diff code-diff-unified unicode-escaped"> <div class="file-body file-code code-view code-diff code-diff-unified unicode-escaped">
<table> <table>
<tbody> <tbody>

View File

@ -1,9 +1,9 @@
<div class="bottom-reactions" data-action-url="{{$.ActionURL}}"> <div class="bottom-reactions" data-action-url="{{$.ActionURL}}">
{{range $key, $value := .Reactions}} {{range $key, $value := .Reactions}}
{{$hasReacted := $value.HasUser ctx.RootData.SignedUserID}} {{$hasReacted := $value.HasUser ctx.RootData.SignedUserID}}
<a role="button" class="ui label basic{{if $hasReacted}} primary{{end}}{{if not ctx.RootData.IsSigned}} disabled{{end}} comment-reaction-button" <a role="button" class="ui label basic{{if $hasReacted}} primary{{end}}{{if not ctx.RootData.IsSigned}} disabled{{end}}"
data-tooltip-content data-global-click="onCommentReactionButtonClick"
title="{{$value.GetFirstUsers}}{{if gt ($value.GetMoreUserCount) 0}} {{ctx.Locale.Tr "repo.reactions_more" $value.GetMoreUserCount}}{{end}}" data-tooltip-content title="{{$value.GetFirstUsers}}{{if gt ($value.GetMoreUserCount) 0}} {{ctx.Locale.Tr "repo.reactions_more" $value.GetMoreUserCount}}{{end}}"
aria-label="{{$value.GetFirstUsers}}{{if gt ($value.GetMoreUserCount) 0}} {{ctx.Locale.Tr "repo.reactions_more" $value.GetMoreUserCount}}{{end}}" aria-label="{{$value.GetFirstUsers}}{{if gt ($value.GetMoreUserCount) 0}} {{ctx.Locale.Tr "repo.reactions_more" $value.GetMoreUserCount}}{{end}}"
data-tooltip-placement="bottom-start" data-tooltip-placement="bottom-start"
data-reaction-content="{{$key}}" data-has-reacted="{{$hasReacted}}"> data-reaction-content="{{$key}}" data-has-reacted="{{$hasReacted}}">

View File

@ -153,6 +153,11 @@
</div> </div>
{{end}} {{end}}
</div> </div>
{{else}}
<div class="tw-text-center tw-p-8">
<h3 class="tw-my-4">{{ctx.Locale.Tr "repo.issues.filter_no_results"}}</h3>
<p class="tw-text-placeholder-text">{{ctx.Locale.Tr "repo.issues.filter_no_results_placeholder"}}</p>
</div>
{{end}} {{end}}
{{if .IssueIndexerUnavailable}} {{if .IssueIndexerUnavailable}}
<div class="ui error message"> <div class="ui error message">

View File

@ -11,7 +11,7 @@
<div class="repository search"> <div class="repository search">
{{range $result := .SearchResults}} {{range $result := .SearchResults}}
{{$repo := or $.Repo (index $.RepoMaps .RepoID)}} {{$repo := or $.Repo (index $.RepoMaps .RepoID)}}
<div class="diff-file-box diff-box file-content non-diff-file-content repo-search-result"> <div class="diff-file-box file-content non-diff-file-content repo-search-result">
<h4 class="ui top attached header tw-font-normal tw-flex tw-flex-wrap"> <h4 class="ui top attached header tw-font-normal tw-flex tw-flex-wrap">
{{if not $.Repo}} {{if not $.Repo}}
<span class="file tw-flex-1"> <span class="file tw-flex-1">

View File

@ -0,0 +1,6 @@
{
"info": {
"version": "{{AppVer | JSEscape}}"
},
"basePath": "{{AppSubUrl | JSEscape}}/api/v1"
}

View File

@ -12047,6 +12047,12 @@
"in": "path", "in": "path",
"required": true "required": true
}, },
{
"type": "string",
"description": "Filter by target base branch of the pull request",
"name": "base_branch",
"in": "query"
},
{ {
"enum": [ "enum": [
"open", "open",

View File

@ -50,49 +50,41 @@
</div> </div>
</div> </div>
<div class="ui bottom attached segment"> <div class="ui bottom attached segment">
<h5 class="ui top header"> <details {{if or .name (not .Tokens)}}open{{end}}>
{{ctx.Locale.Tr "settings.generate_new_token"}} <summary><h4 class="ui header tw-inline-block tw-my-2">{{ctx.Locale.Tr "settings.generate_new_token"}}</h4></summary>
</h5> <form class="ui form ignore-dirty" action="{{.Link}}" method="post">
<form id="scoped-access-form" class="ui form ignore-dirty" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="field {{if .Err_Name}}error{{end}}"> <div class="field {{if .Err_Name}}error{{end}}">
<label for="name">{{ctx.Locale.Tr "settings.token_name"}}</label> <label for="name">{{ctx.Locale.Tr "settings.token_name"}}</label>
<input id="name" name="name" value="{{.name}}" autofocus required maxlength="255"> <input id="name" name="name" value="{{.name}}" required maxlength="255">
</div> </div>
<div class="field"> <div class="field">
<label>{{ctx.Locale.Tr "settings.repo_and_org_access"}}</label> <div class="tw-my-2">{{ctx.Locale.Tr "settings.repo_and_org_access"}}</div>
<label class="tw-cursor-pointer"> <label class="gt-checkbox">
<input class="enable-system tw-mt-1 tw-mr-1" type="radio" name="scope" value="{{$.AccessTokenScopePublicOnly}}"> <input type="radio" name="scope-public-only" value="{{$.AccessTokenScopePublicOnly}}"> {{ctx.Locale.Tr "settings.permissions_public_only"}}
{{ctx.Locale.Tr "settings.permissions_public_only"}}
</label> </label>
<label class="tw-cursor-pointer"> <label class="gt-checkbox">
<input class="enable-system tw-mt-1 tw-mr-1" type="radio" name="scope" value="" checked> <input type="radio" name="scope-public-only" value="" checked> {{ctx.Locale.Tr "settings.permissions_access_all"}}
{{ctx.Locale.Tr "settings.permissions_access_all"}}
</label> </label>
</div> </div>
<details class="ui optional field"> <div>
<summary class="tw-pb-4 tw-pl-1"> <div class="tw-my-2">{{ctx.Locale.Tr "settings.access_token_desc" (HTMLFormat `href="%s/api/swagger" target="_blank"` AppSubUrl) (`href="https://docs.gitea.com/development/oauth2-provider#scopes" target="_blank"`|SafeHTML)}}</div>
{{ctx.Locale.Tr "settings.select_permissions"}} <table class="ui table unstackable tw-my-2">
</summary> {{range $category := .TokenCategories}}
<p class="activity meta"> <tr>
<i>{{ctx.Locale.Tr "settings.access_token_desc" (HTMLFormat `href="%s/api/swagger" target="_blank"` AppSubUrl) (`href="https://docs.gitea.com/development/oauth2-provider#scopes" target="_blank"`|SafeHTML)}}</i> <td>{{$category}}</td>
</p> <td><label class="gt-checkbox"><input type="radio" name="scope-{{$category}}" value="" checked> {{ctx.Locale.Tr "settings.permission_no_access"}}</label></td>
<div id="scoped-access-token-selector" <td><label class="gt-checkbox"><input type="radio" name="scope-{{$category}}" value="read:{{$category}}"> {{ctx.Locale.Tr "settings.permission_read"}}</label></td>
data-is-admin="{{if .IsAdmin}}true{{else}}false{{end}}" <td><label class="gt-checkbox"><input type="radio" name="scope-{{$category}}" value="write:{{$category}}"> {{ctx.Locale.Tr "settings.permission_write"}}</label></td>
data-no-access-label="{{ctx.Locale.Tr "settings.permission_no_access"}}" </tr>
data-read-label="{{ctx.Locale.Tr "settings.permission_read"}}" {{end}}
data-write-label="{{ctx.Locale.Tr "settings.permission_write"}}" </table>
data-locale-component-failed-to-load="{{ctx.Locale.Tr "graphs.component_failed_to_load"}}"
>
</div> </div>
</details> <button class="ui primary button">
<button id="scoped-access-submit" class="ui primary button">
{{ctx.Locale.Tr "settings.generate_token"}} {{ctx.Locale.Tr "settings.generate_token"}}
</button> </button>
</form>{{/* Fomantic ".ui.form .warning.message" is hidden by default, so put the warning message out of the form*/}} </form>
<div id="scoped-access-warning" class="ui warning message center tw-hidden"> </details>
{{ctx.Locale.Tr "settings.at_least_one_permission"}}
</div>
</div> </div>
{{if .EnableOAuth2}} {{if .EnableOAuth2}}

View File

@ -48,9 +48,8 @@
</div> </div>
<div class="ui bottom attached segment"> <div class="ui bottom attached segment">
<h5 class="ui top header"> <details {{if .application_name}}open{{end}}>
{{ctx.Locale.Tr "settings.create_oauth2_application"}} <summary><h4 class="ui header tw-inline-block tw-my-2">{{ctx.Locale.Tr "settings.create_oauth2_application"}}</h4></summary>
</h5>
<form class="ui form ignore-dirty" action="{{.Link}}/oauth2" method="post"> <form class="ui form ignore-dirty" action="{{.Link}}/oauth2" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="field {{if .Err_AppName}}error{{end}}"> <div class="field {{if .Err_AppName}}error{{end}}">
@ -77,4 +76,5 @@
{{ctx.Locale.Tr "settings.create_oauth2_application_button"}} {{ctx.Locale.Tr "settings.create_oauth2_application_button"}}
</button> </button>
</form> </form>
</details>
</div> </div>

View File

@ -76,7 +76,7 @@ func TestAPIAdminOrgCreateNotAdmin(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
nonAdminUsername := "user2" nonAdminUsername := "user2"
session := loginUser(t, nonAdminUsername) session := loginUser(t, nonAdminUsername)
token := getTokenForLoggedInUser(t, session) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
org := api.CreateOrgOption{ org := api.CreateOrgOption{
UserName: "user2_org", UserName: "user2_org",
FullName: "User2's organization", FullName: "User2's organization",

View File

@ -76,7 +76,7 @@ func TestAPIAdminDeleteUnauthorizedKey(t *testing.T) {
var newPublicKey api.PublicKey var newPublicKey api.PublicKey
DecodeJSON(t, resp, &newPublicKey) DecodeJSON(t, resp, &newPublicKey)
token = getUserToken(t, normalUsername) token = getUserToken(t, normalUsername, auth_model.AccessTokenScopeAll)
req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d", adminUsername, newPublicKey.ID). req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d", adminUsername, newPublicKey.ID).
AddTokenAuth(token) AddTokenAuth(token)
MakeRequest(t, req, http.StatusForbidden) MakeRequest(t, req, http.StatusForbidden)
@ -139,7 +139,7 @@ func TestAPIListUsersNotLoggedIn(t *testing.T) {
func TestAPIListUsersNonAdmin(t *testing.T) { func TestAPIListUsersNonAdmin(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
nonAdminUsername := "user2" nonAdminUsername := "user2"
token := getUserToken(t, nonAdminUsername) token := getUserToken(t, nonAdminUsername, auth_model.AccessTokenScopeAll)
req := NewRequest(t, "GET", "/api/v1/admin/users"). req := NewRequest(t, "GET", "/api/v1/admin/users").
AddTokenAuth(token) AddTokenAuth(token)
MakeRequest(t, req, http.StatusForbidden) MakeRequest(t, req, http.StatusForbidden)

View File

@ -33,6 +33,10 @@ type APITestContext struct {
func NewAPITestContext(t *testing.T, username, reponame string, scope ...auth.AccessTokenScope) APITestContext { func NewAPITestContext(t *testing.T, username, reponame string, scope ...auth.AccessTokenScope) APITestContext {
session := loginUser(t, username) session := loginUser(t, username)
if len(scope) == 0 {
// FIXME: legacy logic: no scope means all
scope = []auth.AccessTokenScope{auth.AccessTokenScopeAll}
}
token := getTokenForLoggedInUser(t, session, scope...) token := getTokenForLoggedInUser(t, session, scope...)
return APITestContext{ return APITestContext{
Session: session, Session: session,

View File

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/packages" "code.gitea.io/gitea/models/packages"
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"
composer_module "code.gitea.io/gitea/modules/packages/composer" composer_module "code.gitea.io/gitea/modules/packages/composer"
@ -217,5 +218,39 @@ func TestPackageComposer(t *testing.T) {
assert.Equal(t, "4f5fa464c3cb808a1df191dbf6cb75363f8b7072", pkgs[0].Dist.Checksum) assert.Equal(t, "4f5fa464c3cb808a1df191dbf6cb75363f8b7072", pkgs[0].Dist.Checksum)
assert.Len(t, pkgs[0].Bin, 1) assert.Len(t, pkgs[0].Bin, 1)
assert.Equal(t, packageBin, pkgs[0].Bin[0]) assert.Equal(t, packageBin, pkgs[0].Bin[0])
// Test package linked to repository
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
userPkgs, err := packages.GetPackagesByType(db.DefaultContext, user.ID, packages.TypeComposer)
assert.NoError(t, err)
assert.Len(t, userPkgs, 1)
assert.EqualValues(t, 0, userPkgs[0].RepoID)
err = packages.SetRepositoryLink(db.DefaultContext, userPkgs[0].ID, repo1.ID)
assert.NoError(t, err)
req = NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)).
AddBasicAuth(user.Name)
resp = MakeRequest(t, req, http.StatusOK)
result = composer.PackageMetadataResponse{}
DecodeJSON(t, resp, &result)
assert.Contains(t, result.Packages, packageName)
pkgs = result.Packages[packageName]
assert.Len(t, pkgs, 1)
assert.Equal(t, packageName, pkgs[0].Name)
assert.Equal(t, packageVersion, pkgs[0].Version)
assert.Equal(t, packageType, pkgs[0].Type)
assert.Equal(t, packageDescription, pkgs[0].Description)
assert.Len(t, pkgs[0].Authors, 1)
assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name)
assert.Equal(t, "zip", pkgs[0].Dist.Type)
assert.Equal(t, "4f5fa464c3cb808a1df191dbf6cb75363f8b7072", pkgs[0].Dist.Checksum)
assert.Len(t, pkgs[0].Bin, 1)
assert.Equal(t, packageBin, pkgs[0].Bin[0])
assert.Equal(t, repo1.HTMLURL(), pkgs[0].Source.URL)
assert.Equal(t, "git", pkgs[0].Source.Type)
assert.Equal(t, packageVersion, pkgs[0].Source.Reference)
}) })
} }

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