mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-27 07:44:43 +02:00
Merge branch 'main' into lunny/refactor_getpatch
This commit is contained in:
commit
e264a3e047
@ -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
|
||||||
|
@ -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
10
.github/labeler.yml
vendored
@ -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:
|
||||||
|
9
.github/workflows/files-changed.yml
vendored
9
.github/workflows/files-changed.yml
vendored
@ -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"
|
||||||
|
4
.github/workflows/release-tag-version.yml
vendored
4
.github/workflows/release-tag-version.yml
vendored
@ -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:
|
||||||
|
17
Makefile
17
Makefile
@ -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
|
||||||
|
@ -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
|
||||||
|
4
go.mod
4
go.mod
@ -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
|
||||||
|
7
go.sum
7
go.sum
@ -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=
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -208,9 +208,31 @@ 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
|
||||||
if err != nil {
|
var cond builder.Cond
|
||||||
return nil, 0, err
|
// 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 {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actions := make([]*Action, 0, opts.PageSize)
|
actions := make([]*Action, 0, opts.PageSize)
|
||||||
|
@ -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
|
||||||
|
@ -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":
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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",
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -169,20 +169,24 @@ 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")
|
||||||
|
|
||||||
|
// 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 sec.HasKey("ENABLE_ACME") {
|
||||||
|
EnableAcme = sec.Key("ENABLE_ACME").MustBool(false)
|
||||||
|
} else {
|
||||||
|
deprecatedSetting(rootCfg, "server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME", "v1.19.0")
|
||||||
|
EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false)
|
||||||
|
}
|
||||||
|
|
||||||
Protocol = HTTP
|
Protocol = HTTP
|
||||||
protocolCfg := sec.Key("PROTOCOL").String()
|
protocolCfg := sec.Key("PROTOCOL").String()
|
||||||
|
if protocolCfg != "https" && EnableAcme {
|
||||||
|
log.Fatal("ACME could only be used with HTTPS protocol")
|
||||||
|
}
|
||||||
|
|
||||||
switch protocolCfg {
|
switch protocolCfg {
|
||||||
case "https":
|
case "https":
|
||||||
Protocol = HTTPS
|
Protocol = HTTPS
|
||||||
|
|
||||||
// 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 sec.HasKey("ENABLE_ACME") {
|
|
||||||
EnableAcme = sec.Key("ENABLE_ACME").MustBool(false)
|
|
||||||
} else {
|
|
||||||
deprecatedSetting(rootCfg, "server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME", "v1.19.0")
|
|
||||||
EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false)
|
|
||||||
}
|
|
||||||
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()
|
||||||
|
@ -1465,6 +1465,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
|
||||||
|
@ -1701,7 +1701,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
|
||||||
|
@ -1464,6 +1464,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 +1703,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
|
||||||
|
@ -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=リポジトリ
|
||||||
@ -1348,6 +1354,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 +1701,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 +2339,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=プッシュ
|
||||||
@ -2876,6 +2888,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=メンテナンス
|
||||||
|
@ -1464,6 +1464,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
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
@ -61,6 +61,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
|
||||||
@ -134,6 +138,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)
|
||||||
|
@ -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(),
|
||||||
|
@ -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
|
||||||
|
@ -1634,6 +1634,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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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">
|
||||||
|
@ -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}}
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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}}
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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">
|
||||||
|
@ -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">
|
||||||
|
6
templates/swagger/v1_input.json
Normal file
6
templates/swagger/v1_input.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"version": "{{AppVer | JSEscape}}"
|
||||||
|
},
|
||||||
|
"basePath": "{{AppSubUrl | JSEscape}}/api/v1"
|
||||||
|
}
|
6
templates/swagger/v1_json.tmpl
generated
6
templates/swagger/v1_json.tmpl
generated
@ -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",
|
||||||
|
@ -80,6 +80,7 @@ func TestPackageMaven(t *testing.T) {
|
|||||||
t.Run("UploadLegacy", func(t *testing.T) {
|
t.Run("UploadLegacy", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
// try to upload a package with legacy package name (will be saved as "GroupID-ArtifactID")
|
||||||
legacyRootLink := "/api/packages/user2/maven/com/gitea/legacy-project"
|
legacyRootLink := "/api/packages/user2/maven/com/gitea/legacy-project"
|
||||||
req := NewRequestWithBody(t, "PUT", legacyRootLink+"/1.0.2/any-file-name?use_legacy_package_name=1", strings.NewReader("test-content")).AddBasicAuth(user.Name)
|
req := NewRequestWithBody(t, "PUT", legacyRootLink+"/1.0.2/any-file-name?use_legacy_package_name=1", strings.NewReader("test-content")).AddBasicAuth(user.Name)
|
||||||
MakeRequest(t, req, http.StatusCreated)
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
@ -97,6 +98,13 @@ func TestPackageMaven(t *testing.T) {
|
|||||||
req = NewRequest(t, "GET", "/user2/-/packages/maven/com.gitea%3Alegacy-project/1.0.2")
|
req = NewRequest(t, "GET", "/user2/-/packages/maven/com.gitea%3Alegacy-project/1.0.2")
|
||||||
MakeRequest(t, req, http.StatusNotFound)
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
|
||||||
|
// legacy package names should also be able to be listed
|
||||||
|
req = NewRequest(t, "GET", legacyRootLink+"/maven-metadata.xml").AddBasicAuth(user.Name)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
respBody := resp.Body.String()
|
||||||
|
assert.Contains(t, respBody, "<version>1.0.2</version>")
|
||||||
|
|
||||||
|
// then upload a package with correct package name (will be saved as "GroupID:ArtifactID")
|
||||||
req = NewRequestWithBody(t, "PUT", legacyRootLink+"/1.0.3/any-file-name", strings.NewReader("test-content")).AddBasicAuth(user.Name)
|
req = NewRequestWithBody(t, "PUT", legacyRootLink+"/1.0.3/any-file-name", strings.NewReader("test-content")).AddBasicAuth(user.Name)
|
||||||
MakeRequest(t, req, http.StatusCreated)
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
_, err = packages.GetPackageByName(db.DefaultContext, user.ID, packages.TypeMaven, "com.gitea-legacy-project")
|
_, err = packages.GetPackageByName(db.DefaultContext, user.ID, packages.TypeMaven, "com.gitea-legacy-project")
|
||||||
@ -114,6 +122,12 @@ func TestPackageMaven(t *testing.T) {
|
|||||||
req = NewRequest(t, "GET", "/user2/-/packages/maven/com.gitea%3Alegacy-project/1.0.2")
|
req = NewRequest(t, "GET", "/user2/-/packages/maven/com.gitea%3Alegacy-project/1.0.2")
|
||||||
MakeRequest(t, req, http.StatusOK)
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
// now 2 packages should be listed
|
||||||
|
req = NewRequest(t, "GET", legacyRootLink+"/maven-metadata.xml").AddBasicAuth(user.Name)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
respBody = resp.Body.String()
|
||||||
|
assert.Contains(t, respBody, "<version>1.0.2</version>")
|
||||||
|
assert.Contains(t, respBody, "<version>1.0.3</version>")
|
||||||
require.NoError(t, packages.DeletePackageByID(db.DefaultContext, p.ID))
|
require.NoError(t, packages.DeletePackageByID(db.DefaultContext, p.ID))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ package integration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
@ -14,132 +13,145 @@ import (
|
|||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAPIRepoCollaboratorPermission(t *testing.T) {
|
func TestAPIRepoCollaboratorPermission(t *testing.T) {
|
||||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
defer tests.PrepareTestEnv(t)()
|
||||||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||||
repo2Owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo2.OwnerID})
|
repo2Owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo2.OwnerID})
|
||||||
|
|
||||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||||
user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
||||||
user10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10})
|
user10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10})
|
||||||
user11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 11})
|
user11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 11})
|
||||||
user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34})
|
user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34})
|
||||||
|
|
||||||
testCtx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository)
|
testCtx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
t.Run("RepoOwnerShouldBeOwner", func(t *testing.T) {
|
t.Run("RepoOwnerShouldBeOwner", func(t *testing.T) {
|
||||||
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, repo2Owner.Name).
|
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, repo2Owner.Name).
|
||||||
AddTokenAuth(testCtx.Token)
|
AddTokenAuth(testCtx.Token)
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
var repoPermission api.RepoCollaboratorPermission
|
var repoPermission api.RepoCollaboratorPermission
|
||||||
DecodeJSON(t, resp, &repoPermission)
|
DecodeJSON(t, resp, &repoPermission)
|
||||||
|
|
||||||
assert.Equal(t, "owner", repoPermission.Permission)
|
assert.Equal(t, "owner", repoPermission.Permission)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("CollaboratorWithReadAccess", func(t *testing.T) {
|
t.Run("CollaboratorWithReadAccess", func(t *testing.T) {
|
||||||
t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeRead))
|
t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeRead))
|
||||||
|
|
||||||
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name).
|
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name).
|
||||||
AddTokenAuth(testCtx.Token)
|
AddTokenAuth(testCtx.Token)
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
var repoPermission api.RepoCollaboratorPermission
|
var repoPermission api.RepoCollaboratorPermission
|
||||||
DecodeJSON(t, resp, &repoPermission)
|
DecodeJSON(t, resp, &repoPermission)
|
||||||
|
|
||||||
assert.Equal(t, "read", repoPermission.Permission)
|
assert.Equal(t, "read", repoPermission.Permission)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("CollaboratorWithWriteAccess", func(t *testing.T) {
|
t.Run("CollaboratorWithWriteAccess", func(t *testing.T) {
|
||||||
t.Run("AddUserAsCollaboratorWithWriteAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeWrite))
|
t.Run("AddUserAsCollaboratorWithWriteAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeWrite))
|
||||||
|
|
||||||
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name).
|
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name).
|
||||||
AddTokenAuth(testCtx.Token)
|
AddTokenAuth(testCtx.Token)
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
var repoPermission api.RepoCollaboratorPermission
|
var repoPermission api.RepoCollaboratorPermission
|
||||||
DecodeJSON(t, resp, &repoPermission)
|
DecodeJSON(t, resp, &repoPermission)
|
||||||
|
|
||||||
assert.Equal(t, "write", repoPermission.Permission)
|
assert.Equal(t, "write", repoPermission.Permission)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("CollaboratorWithAdminAccess", func(t *testing.T) {
|
t.Run("CollaboratorWithAdminAccess", func(t *testing.T) {
|
||||||
t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeAdmin))
|
t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeAdmin))
|
||||||
|
|
||||||
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name).
|
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name).
|
||||||
AddTokenAuth(testCtx.Token)
|
AddTokenAuth(testCtx.Token)
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
var repoPermission api.RepoCollaboratorPermission
|
var repoPermission api.RepoCollaboratorPermission
|
||||||
DecodeJSON(t, resp, &repoPermission)
|
DecodeJSON(t, resp, &repoPermission)
|
||||||
|
|
||||||
assert.Equal(t, "admin", repoPermission.Permission)
|
assert.Equal(t, "admin", repoPermission.Permission)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("CollaboratorNotFound", func(t *testing.T) {
|
t.Run("CollaboratorNotFound", func(t *testing.T) {
|
||||||
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, "non-existent-user").
|
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, "non-existent-user").
|
||||||
AddTokenAuth(testCtx.Token)
|
AddTokenAuth(testCtx.Token)
|
||||||
MakeRequest(t, req, http.StatusNotFound)
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("CollaboratorBlocked", func(t *testing.T) {
|
t.Run("CollaboratorBlocked", func(t *testing.T) {
|
||||||
ctx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository)
|
ctx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository)
|
||||||
ctx.ExpectedCode = http.StatusForbidden
|
ctx.ExpectedCode = http.StatusForbidden
|
||||||
doAPIAddCollaborator(ctx, user34.Name, perm.AccessModeAdmin)(t)
|
doAPIAddCollaborator(ctx, user34.Name, perm.AccessModeAdmin)(t)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) {
|
t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) {
|
||||||
t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead))
|
t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead))
|
||||||
|
|
||||||
_session := loginUser(t, user5.Name)
|
_session := loginUser(t, user5.Name)
|
||||||
_testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository)
|
_testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
|
||||||
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name).
|
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name).
|
||||||
AddTokenAuth(_testCtx.Token)
|
AddTokenAuth(_testCtx.Token)
|
||||||
resp := _session.MakeRequest(t, req, http.StatusOK)
|
resp := _session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
var repoPermission api.RepoCollaboratorPermission
|
var repoPermission api.RepoCollaboratorPermission
|
||||||
DecodeJSON(t, resp, &repoPermission)
|
DecodeJSON(t, resp, &repoPermission)
|
||||||
|
|
||||||
assert.Equal(t, "read", repoPermission.Permission)
|
assert.Equal(t, "read", repoPermission.Permission)
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) {
|
t.Run("CollaboratorCanReadOwnPermission", func(t *testing.T) {
|
||||||
t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead))
|
session := loginUser(t, user5.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
|
||||||
_session := loginUser(t, user5.Name)
|
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name).AddTokenAuth(token)
|
||||||
_testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository)
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name).
|
repoCollPerm := api.RepoCollaboratorPermission{}
|
||||||
AddTokenAuth(_testCtx.Token)
|
DecodeJSON(t, resp, &repoCollPerm)
|
||||||
resp := _session.MakeRequest(t, req, http.StatusOK)
|
|
||||||
|
|
||||||
var repoPermission api.RepoCollaboratorPermission
|
assert.Equal(t, "read", repoCollPerm.Permission)
|
||||||
DecodeJSON(t, resp, &repoPermission)
|
|
||||||
|
|
||||||
assert.Equal(t, "read", repoPermission.Permission)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("RepoAdminCanQueryACollaboratorsPermissions", func(t *testing.T) {
|
|
||||||
t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user10.Name, perm.AccessModeAdmin))
|
|
||||||
t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user11.Name, perm.AccessModeRead))
|
|
||||||
|
|
||||||
_session := loginUser(t, user10.Name)
|
|
||||||
_testCtx := NewAPITestContext(t, user10.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository)
|
|
||||||
|
|
||||||
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user11.Name).
|
|
||||||
AddTokenAuth(_testCtx.Token)
|
|
||||||
resp := _session.MakeRequest(t, req, http.StatusOK)
|
|
||||||
|
|
||||||
var repoPermission api.RepoCollaboratorPermission
|
|
||||||
DecodeJSON(t, resp, &repoPermission)
|
|
||||||
|
|
||||||
assert.Equal(t, "read", repoPermission.Permission)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) {
|
||||||
|
t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead))
|
||||||
|
|
||||||
|
_session := loginUser(t, user5.Name)
|
||||||
|
_testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
|
||||||
|
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name).
|
||||||
|
AddTokenAuth(_testCtx.Token)
|
||||||
|
resp := _session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
var repoPermission api.RepoCollaboratorPermission
|
||||||
|
DecodeJSON(t, resp, &repoPermission)
|
||||||
|
|
||||||
|
assert.Equal(t, "read", repoPermission.Permission)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("RepoAdminCanQueryACollaboratorsPermissions", func(t *testing.T) {
|
||||||
|
t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user10.Name, perm.AccessModeAdmin))
|
||||||
|
t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user11.Name, perm.AccessModeRead))
|
||||||
|
|
||||||
|
_session := loginUser(t, user10.Name)
|
||||||
|
_testCtx := NewAPITestContext(t, user10.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
|
||||||
|
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user11.Name).
|
||||||
|
AddTokenAuth(_testCtx.Token)
|
||||||
|
resp := _session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
var repoPermission api.RepoCollaboratorPermission
|
||||||
|
DecodeJSON(t, resp, &repoPermission)
|
||||||
|
|
||||||
|
assert.Equal(t, "read", repoPermission.Permission)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -60,12 +60,20 @@ func TestEmptyRepoAddFile(t *testing.T) {
|
|||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
session := loginUser(t, "user30")
|
session := loginUser(t, "user30")
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
|
||||||
|
// test web page
|
||||||
req := NewRequest(t, "GET", "/user30/empty")
|
req := NewRequest(t, "GET", "/user30/empty")
|
||||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
bodyString := resp.Body.String()
|
bodyString := resp.Body.String()
|
||||||
assert.Contains(t, bodyString, "empty-repo-guide")
|
assert.Contains(t, bodyString, "empty-repo-guide")
|
||||||
assert.True(t, test.IsNormalPageCompleted(bodyString))
|
assert.True(t, test.IsNormalPageCompleted(bodyString))
|
||||||
|
|
||||||
|
// test api
|
||||||
|
req = NewRequest(t, "GET", "/api/v1/repos/user30/empty/raw/main/README.md").AddTokenAuth(token)
|
||||||
|
session.MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
|
||||||
|
// create a new file
|
||||||
req = NewRequest(t, "GET", "/user30/empty/_new/"+setting.Repository.DefaultBranch)
|
req = NewRequest(t, "GET", "/user30/empty/_new/"+setting.Repository.DefaultBranch)
|
||||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
doc := NewHTMLParser(t, resp.Body).Find(`input[name="commit_choice"]`)
|
doc := NewHTMLParser(t, resp.Body).Find(`input[name="commit_choice"]`)
|
||||||
|
@ -9,7 +9,10 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGitSmartHTTP(t *testing.T) {
|
func TestGitSmartHTTP(t *testing.T) {
|
||||||
@ -18,51 +21,55 @@ func TestGitSmartHTTP(t *testing.T) {
|
|||||||
|
|
||||||
func testGitSmartHTTP(t *testing.T, u *url.URL) {
|
func testGitSmartHTTP(t *testing.T, u *url.URL) {
|
||||||
kases := []struct {
|
kases := []struct {
|
||||||
p string
|
method, path string
|
||||||
code int
|
code int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
p: "user2/repo1/info/refs",
|
path: "user2/repo1/info/refs",
|
||||||
code: http.StatusOK,
|
code: http.StatusOK,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
p: "user2/repo1/HEAD",
|
method: "HEAD",
|
||||||
|
path: "user2/repo1/info/refs",
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "user2/repo1/HEAD",
|
||||||
code: http.StatusOK,
|
code: http.StatusOK,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
p: "user2/repo1/objects/info/alternates",
|
path: "user2/repo1/objects/info/alternates",
|
||||||
code: http.StatusNotFound,
|
code: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
p: "user2/repo1/objects/info/http-alternates",
|
path: "user2/repo1/objects/info/http-alternates",
|
||||||
code: http.StatusNotFound,
|
code: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
p: "user2/repo1/../../custom/conf/app.ini",
|
path: "user2/repo1/../../custom/conf/app.ini",
|
||||||
code: http.StatusNotFound,
|
code: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
p: "user2/repo1/objects/info/../../../../custom/conf/app.ini",
|
path: "user2/repo1/objects/info/../../../../custom/conf/app.ini",
|
||||||
code: http.StatusNotFound,
|
code: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
p: `user2/repo1/objects/info/..\..\..\..\custom\conf\app.ini`,
|
path: `user2/repo1/objects/info/..\..\..\..\custom\conf\app.ini`,
|
||||||
code: http.StatusBadRequest,
|
code: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, kase := range kases {
|
for _, kase := range kases {
|
||||||
t.Run(kase.p, func(t *testing.T) {
|
t.Run(kase.path, func(t *testing.T) {
|
||||||
p := u.String() + kase.p
|
req, err := http.NewRequest(util.IfZero(kase.method, "GET"), u.String()+kase.path, nil)
|
||||||
req, err := http.NewRequest("GET", p, nil)
|
require.NoError(t, err)
|
||||||
assert.NoError(t, err)
|
|
||||||
req.SetBasicAuth("user2", userPassword)
|
req.SetBasicAuth("user2", userPassword)
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
assert.EqualValues(t, kase.code, resp.StatusCode)
|
assert.EqualValues(t, kase.code, resp.StatusCode)
|
||||||
_, err = io.ReadAll(resp.Body)
|
_, err = io.ReadAll(resp.Body)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,8 +130,13 @@ func TestViewRepo1CloneLinkAnonymous(t *testing.T) {
|
|||||||
link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link")
|
link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link")
|
||||||
assert.True(t, exists, "The template has changed")
|
assert.True(t, exists, "The template has changed")
|
||||||
assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
|
assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
|
||||||
|
|
||||||
_, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link")
|
_, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link")
|
||||||
assert.False(t, exists)
|
assert.False(t, exists)
|
||||||
|
|
||||||
|
link, exists = htmlDoc.doc.Find(".repo-clone-tea").Attr("data-link")
|
||||||
|
assert.True(t, exists, "The template has changed")
|
||||||
|
assert.Equal(t, "tea clone user2/repo1", link)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViewRepo1CloneLinkAuthorized(t *testing.T) {
|
func TestViewRepo1CloneLinkAuthorized(t *testing.T) {
|
||||||
@ -146,10 +151,15 @@ func TestViewRepo1CloneLinkAuthorized(t *testing.T) {
|
|||||||
link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link")
|
link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link")
|
||||||
assert.True(t, exists, "The template has changed")
|
assert.True(t, exists, "The template has changed")
|
||||||
assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
|
assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
|
||||||
|
|
||||||
link, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link")
|
link, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link")
|
||||||
assert.True(t, exists, "The template has changed")
|
assert.True(t, exists, "The template has changed")
|
||||||
sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.SSH.User, setting.SSH.Domain, setting.SSH.Port)
|
sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.SSH.User, setting.SSH.Domain, setting.SSH.Port)
|
||||||
assert.Equal(t, sshURL, link)
|
assert.Equal(t, sshURL, link)
|
||||||
|
|
||||||
|
link, exists = htmlDoc.doc.Find(".repo-clone-tea").Attr("data-link")
|
||||||
|
assert.True(t, exists, "The template has changed")
|
||||||
|
assert.Equal(t, "tea clone user2/repo1", link)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViewRepoWithSymlinks(t *testing.T) {
|
func TestViewRepoWithSymlinks(t *testing.T) {
|
||||||
|
@ -1188,3 +1188,13 @@ the "!important" is necessary to override Fomantic UI menu item styles, meanwhil
|
|||||||
overflow: hidden !important;
|
overflow: hidden !important;
|
||||||
text-overflow: ellipsis !important;
|
text-overflow: ellipsis !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ui.dropdown.text-flex-grow {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.dropdown.text-flex-grow > .text {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
@ -105,7 +105,6 @@ export default defineComponent({
|
|||||||
intervalID: null as IntervalId | null,
|
intervalID: null as IntervalId | null,
|
||||||
currentJobStepsStates: [] as Array<Record<string, any>>,
|
currentJobStepsStates: [] as Array<Record<string, any>>,
|
||||||
artifacts: [] as Array<Record<string, any>>,
|
artifacts: [] as Array<Record<string, any>>,
|
||||||
onHoverRerunIndex: -1,
|
|
||||||
menuVisible: false,
|
menuVisible: false,
|
||||||
isFullScreen: false,
|
isFullScreen: false,
|
||||||
timeVisible: {
|
timeVisible: {
|
||||||
@ -120,7 +119,7 @@ export default defineComponent({
|
|||||||
link: '',
|
link: '',
|
||||||
title: '',
|
title: '',
|
||||||
titleHTML: '',
|
titleHTML: '',
|
||||||
status: 'unknown' as RunStatus,
|
status: '' as RunStatus, // do not show the status before initialized, otherwise it would show an incorrect "error" icon
|
||||||
canCancel: false,
|
canCancel: false,
|
||||||
canApprove: false,
|
canApprove: false,
|
||||||
canRerun: false,
|
canRerun: false,
|
||||||
@ -492,13 +491,13 @@ export default defineComponent({
|
|||||||
<div class="action-view-left">
|
<div class="action-view-left">
|
||||||
<div class="job-group-section">
|
<div class="job-group-section">
|
||||||
<div class="job-brief-list">
|
<div class="job-brief-list">
|
||||||
<a class="job-brief-item" :href="run.link+'/jobs/'+index" :class="parseInt(jobIndex) === index ? 'selected' : ''" v-for="(job, index) in run.jobs" :key="job.id" @mouseenter="onHoverRerunIndex = job.id" @mouseleave="onHoverRerunIndex = -1">
|
<a class="job-brief-item" :href="run.link+'/jobs/'+index" :class="parseInt(jobIndex) === index ? 'selected' : ''" v-for="(job, index) in run.jobs" :key="job.id">
|
||||||
<div class="job-brief-item-left">
|
<div class="job-brief-item-left">
|
||||||
<ActionRunStatus :locale-status="locale.status[job.status]" :status="job.status"/>
|
<ActionRunStatus :locale-status="locale.status[job.status]" :status="job.status"/>
|
||||||
<span class="job-brief-name tw-mx-2 gt-ellipsis">{{ job.name }}</span>
|
<span class="job-brief-name tw-mx-2 gt-ellipsis">{{ job.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="job-brief-item-right">
|
<span class="job-brief-item-right">
|
||||||
<SvgIcon name="octicon-sync" role="button" :data-tooltip-content="locale.rerun" class="job-brief-rerun tw-mx-2 link-action" :data-url="`${run.link}/jobs/${index}/rerun`" v-if="job.canRerun && onHoverRerunIndex === job.id"/>
|
<SvgIcon name="octicon-sync" role="button" :data-tooltip-content="locale.rerun" class="job-brief-rerun tw-mx-2 link-action" :data-url="`${run.link}/jobs/${index}/rerun`" v-if="job.canRerun"/>
|
||||||
<span class="step-summary-duration">{{ job.duration }}</span>
|
<span class="step-summary-duration">{{ job.duration }}</span>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
@ -721,11 +720,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
.job-brief-item .job-brief-rerun {
|
.job-brief-item .job-brief-rerun {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.job-brief-item .job-brief-rerun:hover {
|
|
||||||
transform: scale(130%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.job-brief-item .job-brief-item-left {
|
.job-brief-item .job-brief-item-left {
|
||||||
|
7
web_src/js/features/repo-common.test.ts
Normal file
7
web_src/js/features/repo-common.test.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import {substituteRepoOpenWithUrl} from './repo-common.ts';
|
||||||
|
|
||||||
|
test('substituteRepoOpenWithUrl', () => {
|
||||||
|
// For example: "x-github-client://openRepo/https://github.com/go-gitea/gitea"
|
||||||
|
expect(substituteRepoOpenWithUrl('proto://a/{url}', 'https://gitea')).toEqual('proto://a/https://gitea');
|
||||||
|
expect(substituteRepoOpenWithUrl('proto://a?link={url}', 'https://gitea')).toEqual('proto://a?link=https%3A%2F%2Fgitea');
|
||||||
|
});
|
@ -42,23 +42,60 @@ export function initRepoActivityTopAuthorsChart() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function substituteRepoOpenWithUrl(tmpl: string, url: string): string {
|
||||||
|
const pos = tmpl.indexOf('{url}');
|
||||||
|
if (pos === -1) return tmpl;
|
||||||
|
const posQuestionMark = tmpl.indexOf('?');
|
||||||
|
const needEncode = posQuestionMark >= 0 && posQuestionMark < pos;
|
||||||
|
return tmpl.replace('{url}', needEncode ? encodeURIComponent(url) : url);
|
||||||
|
}
|
||||||
|
|
||||||
function initCloneSchemeUrlSelection(parent: Element) {
|
function initCloneSchemeUrlSelection(parent: Element) {
|
||||||
const elCloneUrlInput = parent.querySelector<HTMLInputElement>('.repo-clone-url');
|
const elCloneUrlInput = parent.querySelector<HTMLInputElement>('.repo-clone-url');
|
||||||
|
|
||||||
const tabSsh = parent.querySelector('.repo-clone-ssh');
|
|
||||||
const tabHttps = parent.querySelector('.repo-clone-https');
|
const tabHttps = parent.querySelector('.repo-clone-https');
|
||||||
|
const tabSsh = parent.querySelector('.repo-clone-ssh');
|
||||||
|
const tabTea = parent.querySelector('.repo-clone-tea');
|
||||||
const updateClonePanelUi = function() {
|
const updateClonePanelUi = function() {
|
||||||
const scheme = localStorage.getItem('repo-clone-protocol') || 'https';
|
let scheme = localStorage.getItem('repo-clone-protocol');
|
||||||
const isSSH = scheme === 'ssh' && Boolean(tabSsh) || scheme !== 'ssh' && !tabHttps;
|
if (!['https', 'ssh', 'tea'].includes(scheme)) {
|
||||||
if (tabHttps) {
|
scheme = 'https';
|
||||||
tabHttps.textContent = window.origin.split(':')[0].toUpperCase(); // show "HTTP" or "HTTPS"
|
}
|
||||||
tabHttps.classList.toggle('active', !isSSH);
|
|
||||||
}
|
// Fallbacks if the scheme preference is not available in the tabs, for example: empty repo page, there are only HTTPS and SSH
|
||||||
if (tabSsh) {
|
if (scheme === 'tea' && !tabTea) {
|
||||||
tabSsh.classList.toggle('active', isSSH);
|
scheme = 'https';
|
||||||
|
}
|
||||||
|
if (scheme === 'https' && !tabHttps) {
|
||||||
|
scheme = 'ssh';
|
||||||
|
} else if (scheme === 'ssh' && !tabSsh) {
|
||||||
|
scheme = 'https';
|
||||||
|
}
|
||||||
|
|
||||||
|
const isHttps = scheme === 'https';
|
||||||
|
const isSsh = scheme === 'ssh';
|
||||||
|
const isTea = scheme === 'tea';
|
||||||
|
|
||||||
|
if (tabHttps) {
|
||||||
|
tabHttps.textContent = window.origin.split(':')[0].toUpperCase(); // show "HTTP" or "HTTPS"
|
||||||
|
tabHttps.classList.toggle('active', isHttps);
|
||||||
|
}
|
||||||
|
if (tabSsh) {
|
||||||
|
tabSsh.classList.toggle('active', isSsh);
|
||||||
|
}
|
||||||
|
if (tabTea) {
|
||||||
|
tabTea.classList.toggle('active', isTea);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tab: Element;
|
||||||
|
if (isHttps) {
|
||||||
|
tab = tabHttps;
|
||||||
|
} else if (isSsh) {
|
||||||
|
tab = tabSsh;
|
||||||
|
} else if (isTea) {
|
||||||
|
tab = tabTea;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tab = isSSH ? tabSsh : tabHttps;
|
|
||||||
if (!tab) return;
|
if (!tab) return;
|
||||||
const link = toOriginUrl(tab.getAttribute('data-link'));
|
const link = toOriginUrl(tab.getAttribute('data-link'));
|
||||||
|
|
||||||
@ -70,18 +107,22 @@ function initCloneSchemeUrlSelection(parent: Element) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const el of parent.querySelectorAll<HTMLAnchorElement>('.js-clone-url-editor')) {
|
for (const el of parent.querySelectorAll<HTMLAnchorElement>('.js-clone-url-editor')) {
|
||||||
el.href = el.getAttribute('data-href-template').replace('{url}', encodeURIComponent(link));
|
el.href = substituteRepoOpenWithUrl(el.getAttribute('data-href-template'), link);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
updateClonePanelUi();
|
updateClonePanelUi();
|
||||||
// tabSsh or tabHttps might not both exist, eg: guest view, or one is disabled by the server
|
// tabSsh or tabHttps might not both exist, eg: guest view, or one is disabled by the server
|
||||||
|
tabHttps?.addEventListener('click', () => {
|
||||||
|
localStorage.setItem('repo-clone-protocol', 'https');
|
||||||
|
updateClonePanelUi();
|
||||||
|
});
|
||||||
tabSsh?.addEventListener('click', () => {
|
tabSsh?.addEventListener('click', () => {
|
||||||
localStorage.setItem('repo-clone-protocol', 'ssh');
|
localStorage.setItem('repo-clone-protocol', 'ssh');
|
||||||
updateClonePanelUi();
|
updateClonePanelUi();
|
||||||
});
|
});
|
||||||
tabHttps?.addEventListener('click', () => {
|
tabTea?.addEventListener('click', () => {
|
||||||
localStorage.setItem('repo-clone-protocol', 'https');
|
localStorage.setItem('repo-clone-protocol', 'tea');
|
||||||
updateClonePanelUi();
|
updateClonePanelUi();
|
||||||
});
|
});
|
||||||
elCloneUrlInput.addEventListener('focus', () => {
|
elCloneUrlInput.addEventListener('focus', () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user