diff --git a/.editorconfig b/.editorconfig index c0946ac997..e23e4cd649 100644 --- a/.editorconfig +++ b/.editorconfig @@ -17,6 +17,7 @@ insert_final_newline = false [templates/swagger/v1_json.tmpl] indent_style = space +insert_final_newline = false [templates/user/auth/oidc_wellknown.tmpl] indent_style = space diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 83410dc07c..f2af1709e4 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -336,7 +336,7 @@ module.exports = { '@typescript-eslint/no-unsafe-unary-minus': [2], '@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-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-empty-export': [0], '@typescript-eslint/no-wrapper-object-types': [2], @@ -693,7 +693,7 @@ module.exports = { 'no-unused-labels': [2], 'no-unused-private-class-members': [2], '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-useless-backreference': [2], 'no-useless-call': [2], diff --git a/.github/labeler.yml b/.github/labeler.yml index 46efbcb194..0af43cd029 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -41,7 +41,7 @@ modifies/internal: - ".dockerignore" - "docker/**" - ".editorconfig" - - ".eslintrc.yaml" + - ".eslintrc.cjs" - ".golangci.yml" - ".gitpod.yml" - ".markdownlint.yaml" @@ -49,7 +49,7 @@ modifies/internal: - "stylelint.config.js" - ".yamllint.yaml" - ".github/**" - - ".gitea/" + - ".gitea/**" - ".devcontainer/**" - "build.go" - "build/**" @@ -73,9 +73,9 @@ modifies/go: modifies/frontend: - changed-files: - any-glob-to-any-file: - - "**/*.js" - - "**/*.ts" - - "**/*.vue" + - "*.js" + - "*.ts" + - "web_src/**" docs-update-needed: - changed-files: diff --git a/.github/workflows/files-changed.yml b/.github/workflows/files-changed.yml index 7c1fb02442..be27537924 100644 --- a/.github/workflows/files-changed.yml +++ b/.github/workflows/files-changed.yml @@ -51,14 +51,16 @@ jobs: - "options/locale/locale_en-US.ini" frontend: - - "**/*.js" + - "*.js" + - "*.ts" - "web_src/**" + - "tools/*.js" + - "tools/*.ts" - "assets/emoji.json" - "package.json" - "package-lock.json" - "Makefile" - - ".eslintrc.yaml" - - "stylelint.config.js" + - ".eslintrc.cjs" - ".npmrc" docs: @@ -85,6 +87,7 @@ jobs: swagger: - "templates/swagger/v1_json.tmpl" + - "templates/swagger/v1_input.json" - "Makefile" - "package.json" - "package-lock.json" diff --git a/.github/workflows/release-tag-version.yml b/.github/workflows/release-tag-version.yml index f67b76f408..08bb9baecf 100644 --- a/.github/workflows/release-tag-version.yml +++ b/.github/workflows/release-tag-version.yml @@ -88,9 +88,9 @@ jobs: # 1.2 # 1.2.3 tags: | + type=semver,pattern={{version}} type=semver,pattern={{major}} type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{version}} - name: Login to Docker Hub uses: docker/login-action@v3 with: @@ -126,9 +126,9 @@ jobs: # 1.2 # 1.2.3 tags: | + type=semver,pattern={{version}} type=semver,pattern={{major}} type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{version}} - name: Login to Docker Hub uses: docker/login-action@v3 with: diff --git a/Makefile b/Makefile index 89a6f1261f..d4b416156f 100644 --- a/Makefile +++ b/Makefile @@ -165,10 +165,8 @@ ifdef DEPS_PLAYWRIGHT endif SWAGGER_SPEC := templates/swagger/v1_json.tmpl -SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape}}/api/v1"|g -SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape}}/api/v1"|"basePath": "/api/v1"|g +SWAGGER_SPEC_INPUT := templates/swagger/v1_input.json SWAGGER_EXCLUDE := code.gitea.io/sdk -SWAGGER_NEWLINE_COMMAND := -e '$$a\' TEST_MYSQL_HOST ?= mysql:3306 TEST_MYSQL_DBNAME ?= testgitea @@ -271,10 +269,8 @@ endif .PHONY: generate-swagger generate-swagger: $(SWAGGER_SPEC) ## generate the swagger spec from code comments -$(SWAGGER_SPEC): $(GO_SOURCES_NO_BINDATA) - $(GO) run $(SWAGGER_PACKAGE) generate spec -x "$(SWAGGER_EXCLUDE)" -o './$(SWAGGER_SPEC)' - $(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)' - $(SED_INPLACE) $(SWAGGER_NEWLINE_COMMAND) './$(SWAGGER_SPEC)' +$(SWAGGER_SPEC): $(GO_SOURCES_NO_BINDATA) $(SWAGGER_SPEC_INPUT) + $(GO) run $(SWAGGER_PACKAGE) generate spec --exclude "$(SWAGGER_EXCLUDE)" --input "$(SWAGGER_SPEC_INPUT)" --output './$(SWAGGER_SPEC)' .PHONY: swagger-check swagger-check: generate-swagger @@ -287,9 +283,11 @@ swagger-check: generate-swagger .PHONY: swagger-validate 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)' - $(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 checks: checks-frontend checks-backend ## run various consistency checks @@ -380,6 +378,7 @@ lint-go-gopls: ## lint go files with gopls .PHONY: lint-editorconfig lint-editorconfig: + @echo "Running editorconfig check..." @$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) $(EDITORCONFIG_FILES) .PHONY: lint-actions @@ -471,7 +470,9 @@ tidy-check: tidy go-licenses: $(GO_LICENSE_FILE) ## regenerate go licenses $(GO_LICENSE_FILE): go.mod go.sum - -$(GO) run $(GO_LICENSES_PACKAGE) save . --force --save_path=$(GO_LICENSE_TMP_DIR) 2>/dev/null + @rm -rf $(GO_LICENSE_FILE) + $(GO) install $(GO_LICENSES_PACKAGE) + -GOOS=linux CGO_ENABLED=1 go-licenses save . --force --save_path=$(GO_LICENSE_TMP_DIR) 2>/dev/null $(GO) run build/generate-go-licenses.go $(GO_LICENSE_TMP_DIR) $(GO_LICENSE_FILE) @rm -rf $(GO_LICENSE_TMP_DIR) diff --git a/cmd/web_acme.go b/cmd/web_acme.go index 5daf0f55f2..bca4ae0212 100644 --- a/cmd/web_acme.go +++ b/cmd/web_acme.go @@ -54,10 +54,6 @@ func runACME(listenAddr string, m http.Handler) error { 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 var certPool *x509.CertPool 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) } } - 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, TrustedRoots: certPool, Email: setting.AcmeEmail, @@ -77,8 +79,10 @@ func runACME(listenAddr string, m http.Handler) error { ListenHost: setting.HTTPAddr, AltTLSALPNPort: altTLSALPNPort, AltHTTPPort: altHTTPPort, - }) + } + magic := certmagic.NewDefault() + myACME := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME) magic.Issuers = []certmagic.Issuer{myACME} // this obtains certificates or renews them if necessary diff --git a/go.mod b/go.mod index ca5d47aff4..4b7025eb92 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ go 1.24 godebug x509negativeserial=1 require ( - code.gitea.io/actions-proto-go v0.4.0 + code.gitea.io/actions-proto-go v0.4.1 code.gitea.io/gitea-vet v0.2.3 code.gitea.io/sdk/gitea v0.20.0 codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 @@ -24,7 +24,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 - github.com/ProtonMail/go-crypto v1.1.5 + github.com/ProtonMail/go-crypto v1.1.6 github.com/PuerkitoBio/goquery v1.10.2 github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3 github.com/alecthomas/chroma/v2 v2.15.0 @@ -117,10 +117,10 @@ require ( github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-meta v1.1.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/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/sys v0.30.0 golang.org/x/text v0.22.0 @@ -318,7 +318,7 @@ replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1 replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 -replace github.com/nektos/act => gitea.com/gitea/act v0.261.3 +replace github.com/nektos/act => gitea.com/gitea/act v0.261.4 // TODO: the only difference is in `PutObject`: the fork doesn't use `NewVerifyingReader(r, sha256.New(), oid, expectedSize)`, need to figure out why replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-transfer v0.2.0 diff --git a/go.sum b/go.sum index f2bae16405..18a586e404 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= -code.gitea.io/actions-proto-go v0.4.0 h1:OsPBPhodXuQnsspG1sQ4eRE1PeoZyofd7+i73zCwnsU= -code.gitea.io/actions-proto-go v0.4.0/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas= +code.gitea.io/actions-proto-go v0.4.1 h1:l0EYhjsgpUe/1VABo2eK7zcoNX2W44WOnb0MSLrKfls= +code.gitea.io/actions-proto-go v0.4.1/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas= code.gitea.io/gitea-vet v0.2.3 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI= code.gitea.io/gitea-vet v0.2.3/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= code.gitea.io/sdk/gitea v0.20.0 h1:Zm/QDwwZK1awoM4AxdjeAQbxolzx2rIP8dDfmKu+KoU= @@ -16,8 +16,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs= -gitea.com/gitea/act v0.261.3 h1:BhiYpGJQKGq0XMYYICCYAN4KnsEWHyLbA6dxhZwFcV4= -gitea.com/gitea/act v0.261.3/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok= +gitea.com/gitea/act v0.261.4 h1:Tf9eLlvsYFtKcpuxlMvf9yT3g4Hshb2Beqw6C1STuH8= +gitea.com/gitea/act v0.261.4/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok= gitea.com/gitea/git-lfs-transfer v0.2.0 h1:baHaNoBSRaeq/xKayEXwiDQtlIjps4Ac/Ll4KqLMB40= gitea.com/gitea/git-lfs-transfer v0.2.0/go.mod h1:UrXUCm3xLQkq15fu7qlXHUMlrhdlXHoi13KH2Dfiits= gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso= @@ -71,8 +71,8 @@ github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSC github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= -github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8= github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU= github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= @@ -831,8 +831,9 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 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.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/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= 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.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= 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.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/models/activities/action.go b/models/activities/action.go index adc442b88b..52dffe07fd 100644 --- a/models/activities/action.go +++ b/models/activities/action.go @@ -454,6 +454,24 @@ func ActivityReadable(user, doer *user_model.User) bool { 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) { cond := builder.NewCond() @@ -534,17 +552,7 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder. cond = cond.And(builder.Eq{"is_deleted": false}) } - if opts.Date != "" { - 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()}) - } - } + cond = cond.And(FeedDateCond(opts)) return cond, nil } diff --git a/models/activities/action_list.go b/models/activities/action_list.go index 5f9acb8f2a..f7ea48f03e 100644 --- a/models/activities/action_list.go +++ b/models/activities/action_list.go @@ -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") } - cond, err := ActivityQueryCondition(ctx, opts) - if err != nil { - return nil, 0, err + var err error + var cond builder.Cond + // if the actor is the requested user or is an administrator, we can skip the ActivityQueryCondition + if opts.Actor != nil && opts.RequestedUser != nil && (opts.Actor.IsAdmin || opts.Actor.ID == opts.RequestedUser.ID) { + cond = builder.Eq{ + "user_id": opts.RequestedUser.ID, + }.And( + FeedDateCond(opts), + ) + + if !opts.IncludeDeleted { + cond = cond.And(builder.Eq{"is_deleted": false}) + } + + if !opts.IncludePrivate { + cond = cond.And(builder.Eq{"is_private": false}) + } + if opts.OnlyPerformedBy { + cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID}) + } + } else { + cond, err = ActivityQueryCondition(ctx, opts) + if err != nil { + return nil, 0, err + } } actions := make([]*Action, 0, opts.PageSize) diff --git a/models/admin/task.go b/models/admin/task.go index 10f8e6d570..0541a8ec78 100644 --- a/models/admin/task.go +++ b/models/admin/task.go @@ -44,7 +44,7 @@ func init() { // TranslatableMessage represents JSON struct that can be translated with a Locale type TranslatableMessage struct { Format string - Args []any `json:"omitempty"` + Args []any `json:",omitempty"` } // LoadRepo loads repository of the task diff --git a/models/auth/access_token_scope.go b/models/auth/access_token_scope.go index 897ff3fc9e..0e5b2e96e6 100644 --- a/models/auth/access_token_scope.go +++ b/models/auth/access_token_scope.go @@ -5,6 +5,7 @@ package auth import ( "fmt" + "slices" "strings" "code.gitea.io/gitea/models/perm" @@ -14,7 +15,7 @@ import ( type AccessTokenScopeCategory int const ( - AccessTokenScopeCategoryActivityPub = iota + AccessTokenScopeCategoryActivityPub AccessTokenScopeCategory = iota AccessTokenScopeCategoryAdmin AccessTokenScopeCategoryMisc // WARN: this is now just a placeholder, don't remove it which will change the following values AccessTokenScopeCategoryNotification @@ -193,6 +194,14 @@ var accessTokenScopes = map[AccessTokenScopeLevel]map[AccessTokenScopeCategory]A }, } +func GetAccessTokenCategories() (res []string) { + for _, cat := range accessTokenScopes[Read] { + res = append(res, strings.TrimPrefix(string(cat), "read:")) + } + slices.Sort(res) + return res +} + // GetRequiredScopes gets the specific scopes for a given level and categories func GetRequiredScopes(level AccessTokenScopeLevel, scopeCategories ...AccessTokenScopeCategory) []AccessTokenScope { scopes := make([]AccessTokenScope, 0, len(scopeCategories)) @@ -270,6 +279,9 @@ func (s AccessTokenScope) parse() (accessTokenScopeBitmap, error) { // StringSlice returns the AccessTokenScope as a []string func (s AccessTokenScope) StringSlice() []string { + if s == "" { + return nil + } return strings.Split(string(s), ",") } diff --git a/models/auth/access_token_scope_test.go b/models/auth/access_token_scope_test.go index a6097e45d7..9e4aa83633 100644 --- a/models/auth/access_token_scope_test.go +++ b/models/auth/access_token_scope_test.go @@ -17,6 +17,7 @@ type scopeTestNormalize struct { } func TestAccessTokenScope_Normalize(t *testing.T) { + assert.Equal(t, []string{"activitypub", "admin", "issue", "misc", "notification", "organization", "package", "repository", "user"}, GetAccessTokenCategories()) tests := []scopeTestNormalize{ {"", "", nil}, {"write:misc,write:notification,read:package,write:notification,public-only", "public-only,write:misc,write:notification,read:package", nil}, @@ -25,7 +26,7 @@ func TestAccessTokenScope_Normalize(t *testing.T) { {"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user,public-only", "public-only,all", nil}, } - for _, scope := range []string{"activitypub", "admin", "misc", "notification", "organization", "package", "issue", "repository", "user"} { + for _, scope := range GetAccessTokenCategories() { tests = append(tests, scopeTestNormalize{AccessTokenScope(fmt.Sprintf("read:%s", scope)), AccessTokenScope(fmt.Sprintf("read:%s", scope)), nil}, scopeTestNormalize{AccessTokenScope(fmt.Sprintf("write:%s", scope)), AccessTokenScope(fmt.Sprintf("write:%s", scope)), nil}, @@ -59,7 +60,7 @@ func TestAccessTokenScope_HasScope(t *testing.T) { {"public-only", "read:issue", false, nil}, } - for _, scope := range []string{"activitypub", "admin", "misc", "notification", "organization", "package", "issue", "repository", "user"} { + for _, scope := range GetAccessTokenCategories() { tests = append(tests, scopeTestHasScope{ AccessTokenScope(fmt.Sprintf("read:%s", scope)), diff --git a/models/issues/pull_list.go b/models/issues/pull_list.go index 1ddb94e566..b685175f8e 100644 --- a/models/issues/pull_list.go +++ b/models/issues/pull_list.go @@ -28,11 +28,16 @@ type PullRequestsOptions struct { Labels []int64 MilestoneID int64 PosterID int64 + BaseBranch string } func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullRequestsOptions) *xorm.Session { 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") switch opts.State { case "closed", "open": @@ -56,7 +61,7 @@ func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullR } // GetUnmergedPullRequestsByHeadInfo returns all pull requests that are open and has not been merged -func GetUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch string) ([]*PullRequest, error) { +func GetUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch string) (PullRequestList, error) { prs := make([]*PullRequest, 0, 2) sess := db.GetEngine(ctx). Join("INNER", "issue", "issue.id = pull_request.issue_id"). @@ -111,7 +116,7 @@ func HasUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch // GetUnmergedPullRequestsByBaseInfo returns all pull requests that are open and has not been merged // by given base information (repo and branch). -func GetUnmergedPullRequestsByBaseInfo(ctx context.Context, repoID int64, branch string) ([]*PullRequest, error) { +func GetUnmergedPullRequestsByBaseInfo(ctx context.Context, repoID int64, branch string) (PullRequestList, error) { prs := make([]*PullRequest, 0, 2) return prs, db.GetEngine(ctx). Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?", diff --git a/models/issues/pull_list_test.go b/models/issues/pull_list_test.go index c7a898ca4e..f5553e7885 100644 --- a/models/issues/pull_list_test.go +++ b/models/issues/pull_list_test.go @@ -16,11 +16,11 @@ import ( func TestPullRequestList_LoadAttributes(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - prs := []*issues_model.PullRequest{ + prs := issues_model.PullRequestList{ unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}), unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}), } - assert.NoError(t, issues_model.PullRequestList(prs).LoadAttributes(db.DefaultContext)) + assert.NoError(t, prs.LoadAttributes(db.DefaultContext)) for _, pr := range prs { assert.NotNil(t, pr.Issue) assert.Equal(t, pr.IssueID, pr.Issue.ID) @@ -32,11 +32,11 @@ func TestPullRequestList_LoadAttributes(t *testing.T) { func TestPullRequestList_LoadReviewCommentsCounts(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - prs := []*issues_model.PullRequest{ + prs := issues_model.PullRequestList{ unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}), unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}), } - reviewComments, err := issues_model.PullRequestList(prs).LoadReviewCommentsCounts(db.DefaultContext) + reviewComments, err := prs.LoadReviewCommentsCounts(db.DefaultContext) assert.NoError(t, err) assert.Len(t, reviewComments, 2) for _, pr := range prs { @@ -47,11 +47,11 @@ func TestPullRequestList_LoadReviewCommentsCounts(t *testing.T) { func TestPullRequestList_LoadReviews(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - prs := []*issues_model.PullRequest{ + prs := issues_model.PullRequestList{ unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}), unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}), } - reviewList, err := issues_model.PullRequestList(prs).LoadReviews(db.DefaultContext) + reviewList, err := prs.LoadReviews(db.DefaultContext) assert.NoError(t, err) // 1, 7, 8, 9, 10, 22 assert.Len(t, reviewList, 6) diff --git a/models/organization/org_list.go b/models/organization/org_list.go index 4c4168af1f..78ac0e704a 100644 --- a/models/organization/org_list.go +++ b/models/organization/org_list.go @@ -124,6 +124,7 @@ func GetUserOrgsList(ctx context.Context, user *user_model.User) ([]*MinimalOrg, if err := db.GetEngine(ctx).Select(columnsStr). Table("user"). Where(builder.In("`user`.`id`", queryUserOrgIDs(user.ID, true))). + OrderBy("`user`.lower_name ASC"). Find(&orgs); err != nil { return nil, err } diff --git a/models/packages/descriptor.go b/models/packages/descriptor.go index 803b73c968..d251fcc4a9 100644 --- a/models/packages/descriptor.go +++ b/models/packages/descriptor.go @@ -110,9 +110,12 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc if err != nil { return nil, err } - repository, err := repo_model.GetRepositoryByID(ctx, p.RepoID) - if err != nil && !repo_model.IsErrRepoNotExist(err) { - return nil, err + var repository *repo_model.Repository + if p.RepoID > 0 { + repository, err = repo_model.GetRepositoryByID(ctx, p.RepoID) + if err != nil && !repo_model.IsErrRepoNotExist(err) { + return nil, err + } } creator, err := user_model.GetUserByID(ctx, pv.CreatorID) if err != nil { diff --git a/models/repo/repo.go b/models/repo/repo.go index 4e27dbaf14..d42792faa2 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -646,13 +646,15 @@ func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML { type CloneLink struct { SSH 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 { 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 { sshUser := setting.SSH.User 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)) } +// 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 { - cl := new(CloneLink) - cl.SSH = ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName) - cl.HTTPS = ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName) - return cl + return &CloneLink{ + SSH: ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName), + HTTPS: ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName), + Tea: ComposeTeaCloneCommand(ctx, repo.OwnerName, repoPathName), + } } // CloneLink returns clone URLs of repository. diff --git a/models/repo/user_repo.go b/models/repo/user_repo.go index a9b1360df1..232087d865 100644 --- a/models/repo/user_repo.go +++ b/models/repo/user_repo.go @@ -5,6 +5,7 @@ package repo import ( "context" + "strings" "code.gitea.io/gitea/models/db" "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 func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string, isShowFullName bool) ([]*user_model.User, error) { 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 { - prefixCond = prefixCond.Or(builder.Like{"full_name", "%" + search + "%"}) + prefixCond = prefixCond.Or(db.BuildCaseInsensitiveLike("full_name", "%"+search+"%")) } cond := builder.In("`user`.id", diff --git a/models/repo/user_repo_test.go b/models/repo/user_repo_test.go index 44ebe5f214..50c970344c 100644 --- a/models/repo/user_repo_test.go +++ b/models/repo/user_repo_test.go @@ -12,6 +12,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) 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) } } + +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) +} diff --git a/modules/markup/sanitizer_default_test.go b/modules/markup/sanitizer_default_test.go index e6fbae5056..5282916944 100644 --- a/modules/markup/sanitizer_default_test.go +++ b/modules/markup/sanitizer_default_test.go @@ -62,6 +62,10 @@ func TestSanitizer(t *testing.T) { `bad`, `bad`, `bad`, `bad`, `bad`, `bad`, + + // Some classes and attributes are used by the frontend framework and will execute JS code, so make sure they are removed + ``, `
txt
`, + `
txt
`, `
txt
`, } for i := 0; i < len(testCases); i += 2 { diff --git a/modules/setting/server.go b/modules/setting/server.go index d7a71578d4..e15b790906 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -169,20 +169,24 @@ func loadServerFrom(rootCfg ConfigProvider) { HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0") 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 protocolCfg := sec.Key("PROTOCOL").String() + if protocolCfg != "https" && EnableAcme { + log.Fatal("ACME could only be used with HTTPS protocol") + } + switch protocolCfg { case "https": Protocol = HTTPS - - // 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 { AcmeURL = sec.Key("ACME_URL").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") AcmeEmail = sec.Key("LETSENCRYPT_EMAIL").MustString("") } + if AcmeEmail == "" { + log.Fatal("ACME Email is not set (ACME_EMAIL).") + } } else { CertFile = sec.Key("CERT_FILE").String() KeyFile = sec.Key("KEY_FILE").String() diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 3f2ac68802..ec914d2b2e 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -911,7 +911,6 @@ delete_token_success=Token byl odstraněn. Aplikace, které jej používají ji repo_and_org_access=Repozitář a přístup organizace permissions_public_only=Pouze veřejnost permissions_access_all=Vše (veřejné, soukromé a omezené) -select_permissions=Vyberte oprávnění permission_not_set=Není nastaveno permission_no_access=Bez přístupu permission_read=Přečtené @@ -2580,7 +2579,6 @@ diff.commit=revize diff.git-notes=Poznámky diff.data_not_available=Rozdílový obsah není dostupný diff.options_button=Možnosti rozdílového porovnání -diff.show_diff_stats=Zobrazit statistiky diff.download_patch=Stáhněte soubor záplaty diff.download_diff=Stáhněte rozdílový soubor diff.show_split_view=Rozdělené zobrazení diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index f1eada3990..0bec9305aa 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -910,7 +910,6 @@ delete_token_success=Der Zugriffstoken wurde gelöscht. Anwendungen die diesen T repo_and_org_access=Repository- und Organisationszugriff permissions_public_only=Nur öffentlich permissions_access_all=Alle (öffentlich, privat und begrenzt) -select_permissions=Berechtigungen auswählen permission_not_set=Nicht festgelegt permission_no_access=Kein Zugriff permission_read=Lesen @@ -2569,7 +2568,6 @@ diff.commit=Commit diff.git-notes=Hinweise diff.data_not_available=Keine Diff-Daten verfügbar diff.options_button=Diff-Optionen -diff.show_diff_stats=Statistiken anzeigen diff.download_patch=Patch-Datei herunterladen diff.download_diff=Vergleichs-Datei herunterladen diff.show_split_view=Geteilte Ansicht diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index 7fb4151f17..fe338d8906 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -810,7 +810,6 @@ delete_token_success=Το διακριτικό έχει διαγραφεί. Οι repo_and_org_access=Πρόσβαση στο Αποθετήριο και Οργανισμό permissions_public_only=Δημόσια μόνο permissions_access_all=Όλα (δημόσια, ιδιωτικά, και περιορισμένα) -select_permissions=Επιλέξτε δικαιώματα permission_no_access=Καμία Πρόσβαση permission_read=Αναγνωσμένες permission_write=Ανάγνωση και Εγγραφή @@ -2317,7 +2316,6 @@ diff.commit=υποβολή diff.git-notes=Σημειώσεις diff.data_not_available=Δεν Υπάρχει Διαθέσιμο Περιεχόμενο Diff diff.options_button=Επιλογές Diff -diff.show_diff_stats=Εμφάνιση Στατιστικών diff.download_patch=Λήψη Αρχείου Patch diff.download_diff=Λήψη Αρχείου Diff diff.show_split_view=Διαιρεμένη Προβολή diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index c2c5b07b65..1da2fb61ad 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -917,7 +917,6 @@ delete_token_success = The token has been deleted. Applications using it no long repo_and_org_access = Repository and Organization Access permissions_public_only = Public only permissions_access_all = All (public, private, and limited) -select_permissions = Select permissions permission_not_set = Not set permission_no_access = No Access permission_read = Read @@ -1465,6 +1464,8 @@ issues.filter_milestones = Filter Milestone issues.filter_projects = Filter Project issues.filter_labels = Filter Label 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.title_empty = Title cannot be empty issues.new.labels = Labels @@ -2593,7 +2594,6 @@ diff.commit = commit diff.git-notes = Notes diff.data_not_available = Diff Content Not Available diff.options_button = Diff Options -diff.show_diff_stats = Show Stats diff.download_patch = Download Patch File diff.download_diff = Download Diff File diff.show_split_view = Split View diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index c399b1209c..f856eaebd6 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -806,7 +806,6 @@ delete_token_success=El token ha sido eliminado. Las aplicaciones que lo usen ya repo_and_org_access=Acceso al Repositorio y a la Organización permissions_public_only=Sólo público permissions_access_all=Todo (público, privado y limitado) -select_permissions=Seleccionar permisos permission_no_access=Sin acceso permission_read=Leídas permission_write=Lectura y Escritura @@ -2298,7 +2297,6 @@ diff.commit=commit diff.git-notes=Notas diff.data_not_available=El contenido del Diff no está disponible diff.options_button=Opciones de diferencias -diff.show_diff_stats=Mostrar estadísticas diff.download_patch=Descargar archivo de parche diff.download_diff=Descargar archivo de diferencias diff.show_split_view=Dividir vista diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index 3d34e01722..c82cfc61bd 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -1777,7 +1777,6 @@ diff.commit=کامیت diff.git-notes=یادداشت‌ها diff.data_not_available=محتوای تفاوت ها در دسترس نیست diff.options_button=تنظیمات (diff) تغییرات -diff.show_diff_stats=نمایش وضعیت diff.download_patch=دانلود پرونده وصله diff.download_diff=دانلود فایل تغییرات diff diff.show_split_view=مشاهده تقسیم شده diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index 9d652fabad..a2e17ef6ed 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -917,7 +917,6 @@ delete_token_success=Ce jeton a été supprimé. Les applications l'utilisant n' repo_and_org_access=Accès aux Organisations et Dépôts permissions_public_only=Publique uniquement permissions_access_all=Tout (public, privé et limité) -select_permissions=Sélectionner les autorisations permission_not_set=Non défini permission_no_access=Aucun accès permission_read=Lecture @@ -1701,7 +1700,9 @@ issues.time_estimate_invalid=Le format du temps estimé est invalide issues.start_tracking_history=`a commencé son travail %s.` issues.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 un autre ticket !` +issues.stop_tracking=Arrêter le minuteur issues.stop_tracking_history=a travaillé sur %[1]s %[2]s +issues.cancel_tracking=Abandonner issues.cancel_tracking_history=`a abandonné son minuteur %s.` issues.del_time=Supprimer ce minuteur du journal issues.add_time_history=a pointé du temps de travail sur %[1]s, %[2]s @@ -2590,7 +2591,6 @@ diff.commit=révision diff.git-notes=Notes diff.data_not_available=Contenu de la comparaison indisponible diff.options_button=Option de Diff -diff.show_diff_stats=Voir les Statistiques diff.download_patch=Télécharger le Fichier Patch diff.download_diff=Télécharger le Fichier des Différences diff.show_split_view=Vue séparée diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini index cc7051fb65..1009dbbdca 100644 --- a/options/locale/locale_ga-IE.ini +++ b/options/locale/locale_ga-IE.ini @@ -917,7 +917,6 @@ delete_token_success=Tá an comhartha scriosta. Níl rochtain ag iarratais a ús repo_and_org_access=Rochtain Stórála agus Eagraíochta permissions_public_only=Poiblí amháin permissions_access_all=Gach (poiblí, príobháideach agus teoranta) -select_permissions=Roghnaigh ceadanna permission_not_set=Níl leagtha permission_no_access=Gan rochtain permission_read=Léigh @@ -1464,6 +1463,8 @@ issues.filter_milestones=Cloch Mhíle Scagaire issues.filter_projects=Tionscadal Scagaire issues.filter_labels=Lipéad 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.title_empty=Ní féidir leis an teideal a bheith folamh issues.new.labels=Lipéid @@ -1701,7 +1702,9 @@ issues.time_estimate_invalid=Tá formáid meastachán ama neamhbhailí issues.start_tracking_history=thosaigh ag obair %s issues.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 eagrán eile!` +issues.stop_tracking=Stad uaineadóir issues.stop_tracking_history=d'oibrigh do %[1]s %[2]s +issues.cancel_tracking=Caith amach issues.cancel_tracking_history=`rianú ama curtha ar ceal %s` issues.del_time=Scrios an log ama seo issues.add_time_history=cuireadh am caite %[1]s %[2]s leis @@ -2590,7 +2593,6 @@ diff.commit=tiomantas diff.git-notes=Nótaí diff.data_not_available=Níl Ábhar Difríochtaí Ar Fáil diff.options_button=Roghanna Diff -diff.show_diff_stats=Taispeáin Staitisticí diff.download_patch=Íoslódáil an comhad paiste diff.download_diff=Íoslódáil Comhad Diff diff.show_split_view=Amharc Scoilt diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini index 4767a48547..08a71c27d2 100644 --- a/options/locale/locale_hu-HU.ini +++ b/options/locale/locale_hu-HU.ini @@ -1098,7 +1098,6 @@ diff.parent=szülő diff.commit=commit diff.git-notes=Megjegyzések diff.data_not_available=A különbségek nem megjeleníthetőek -diff.show_diff_stats=Statisztikák mutatása diff.show_split_view=Osztott nézet diff.show_unified_view=Egyesített nézet diff.stats_desc=%d fájl változott, egészen pontosan %d új sor hozzáadva és %d régi sor törölve diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index 29512f47f3..48a43210bf 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -1929,7 +1929,6 @@ diff.commit=commit diff.git-notes=Note diff.data_not_available=Dati Diff non disponibili diff.options_button=Opzioni Diff -diff.show_diff_stats=Mostra statistiche diff.download_patch=Scarica il file Patch diff.download_diff=Scarica il file Diff diff.show_split_view=Visualizzazione separata diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index bc29d530b4..cf0d4bffa7 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -385,6 +385,12 @@ show_only_public=公開のみ表示 issues.in_your_repos=あなたのリポジトリ +guide_title=アクティビティはありません +guide_desc=現在フォロー中のリポジトリやユーザーがないため、表示するコンテンツがありません。 以下のリンクから、興味のあるリポジトリやユーザーを探すことができます。 +explore_repos=リポジトリを探す +explore_users=ユーザーを探す +empty_org=組織はまだありません。 +empty_repo=リポジトリはまだありません。 [explore] repos=リポジトリ @@ -911,7 +917,6 @@ delete_token_success=トークンを削除しました。 削除したトーク repo_and_org_access=リポジトリと組織へのアクセス permissions_public_only=公開のみ permissions_access_all=すべて (公開、プライベート、限定) -select_permissions=許可の選択 permission_not_set=設定なし permission_no_access=アクセス不可 permission_read=読み取り @@ -1348,6 +1353,8 @@ editor.new_branch_name_desc=新しいブランチ名… editor.cancel=キャンセル editor.filename_cannot_be_empty=ファイル名は空にできません。 editor.filename_is_invalid=`ファイル名が不正です: "%s"` +editor.commit_email=コミット メールアドレス +editor.invalid_commit_email=コミットに使うメールアドレスが正しくありません。 editor.branch_does_not_exist=このリポジトリにブランチ "%s" は存在しません。 editor.branch_already_exists=ブランチ "%s" は、このリポジトリに既に存在します。 editor.directory_is_a_file=ディレクトリ名 "%s" はすでにリポジトリ内のファイルで使用されています。 @@ -1693,7 +1700,9 @@ issues.time_estimate_invalid=見積時間のフォーマットが不正です issues.start_tracking_history=が作業を開始 %s issues.tracker_auto_close=タイマーは、このイシューがクローズされると自動的に終了します issues.tracking_already_started=`別のイシューで既にタイムトラッキングを開始しています!` +issues.stop_tracking=タイマー終了 issues.stop_tracking_history=が %[1]s の作業を終了 %[2]s +issues.cancel_tracking=破棄 issues.cancel_tracking_history=`がタイムトラッキングを中止 %s` issues.del_time=このタイムログを削除 issues.add_time_history=が作業時間 %[1]s を追加 %[2]s @@ -2329,6 +2338,8 @@ settings.event_fork=フォーク settings.event_fork_desc=リポジトリがフォークされたとき。 settings.event_wiki=Wiki settings.event_wiki_desc=Wikiページが作成・名前変更・編集・削除されたとき。 +settings.event_statuses=ステータス +settings.event_statuses_desc=APIによってコミットのステータスが更新されたとき。 settings.event_release=リリース settings.event_release_desc=リポジトリでリリースが作成・更新・削除されたとき。 settings.event_push=プッシュ @@ -2580,7 +2591,6 @@ diff.commit=コミット diff.git-notes=Notes diff.data_not_available=差分はありません diff.options_button=差分オプション -diff.show_diff_stats=統計情報を表示 diff.download_patch=Patchファイルをダウンロード diff.download_diff=Diffファイルをダウンロード diff.show_split_view=分割表示 @@ -2876,6 +2886,14 @@ view_as_role=表示: %s view_as_public_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] maintenance=メンテナンス diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index d2df0813ae..7c30d0bb3c 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -814,7 +814,6 @@ delete_token_success=Pilnvara tika izdzēsta. Lietojumprogrammām, kas izmantoja repo_and_org_access=Repozitorija un organizācijas piekļuve permissions_public_only=Tikai publiskie permissions_access_all=Visi (publiskie, privātie un ierobežotie) -select_permissions=Norādiet tiesības permission_no_access=Nav piekļuves permission_read=Skatīšanās permission_write=Skatīšanās un raksīšanas @@ -2317,7 +2316,6 @@ diff.commit=revīzija diff.git-notes=Piezīmes diff.data_not_available=Satura salīdzināšana nav pieejama diff.options_button=Salīdzināšanas iespējas -diff.show_diff_stats=Rādīt statistiku diff.download_patch=Lejupielādēt ielāpa failu diff.download_diff=Lejupielādēt izmaiņu failu diff.show_split_view=Dalītais skats diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index c23df29e99..650e8d4e23 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -1864,7 +1864,6 @@ diff.commit=commit diff.git-notes=Notities diff.data_not_available=Diff gegevens niet beschikbaar diff.options_button=Diff opties -diff.show_diff_stats=Statistieken weergeven diff.download_patch=Download Patch-bestand diff.download_diff=Download Diff-bestand diff.show_split_view=Zij-aan-zij weergave diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index d03018c0d9..55a82e9629 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -1728,7 +1728,6 @@ diff.commit=commit diff.git-notes=Notatki diff.data_not_available=Informacje nt. zmian nie są dostępne diff.options_button=Opcje porównania -diff.show_diff_stats=Pokaż statystyki diff.download_patch=Ściągnij plik aktualizacji diff.download_diff=Ściągnij plik porównania diff.show_split_view=Widok podzielony diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 33aad76023..f4b479344d 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -812,7 +812,6 @@ delete_token_success=O token foi excluído. Os aplicativos que o utilizam já n repo_and_org_access=Acesso ao Repositório e Organização permissions_public_only=Apenas público permissions_access_all=Todos (público, privado e limitado) -select_permissions=Selecionar permissões permission_no_access=Sem acesso permission_read=Ler permission_write=Ler e escrever @@ -2282,7 +2281,6 @@ diff.commit=commit diff.git-notes=Notas diff.data_not_available=Conteúdo de diff não disponível diff.options_button=Opções de diferenças -diff.show_diff_stats=Mostrar estatísticas diff.download_patch=Baixar arquivo de patch diff.download_diff=Baixar arquivo de diferenças diff.show_split_view=Visão dividida diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 3582755d5e..6485fdf73a 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -917,7 +917,6 @@ delete_token_success=O código foi eliminado. Aplicações que o usavam deixaram repo_and_org_access=Acesso aos repositórios e às organizações permissions_public_only=Apenas público permissions_access_all=Tudo (público, privado e limitado) -select_permissions=Escolher permissões permission_not_set=Não definido permission_no_access=Sem acesso permission_read=Lidas @@ -1464,6 +1463,8 @@ issues.filter_milestones=Filtrar etapa issues.filter_projects=Filtrar planeamento issues.filter_labels=Filtrar rótulo 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.title_empty=O título não pode estar vazio issues.new.labels=Rótulos @@ -2592,7 +2593,6 @@ diff.commit=cometimento diff.git-notes=Notas diff.data_not_available=O conteúdo das diferenças não está disponível diff.options_button=Opções das diferenças -diff.show_diff_stats=Mostrar estatísticas diff.download_patch=Descarregar ficheiro patch diff.download_diff=Descarregar ficheiro diff diff.show_split_view=Visualização em 2 colunas diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 0aa776b78a..6140766291 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -807,7 +807,6 @@ delete_token_success=Токен удалён. Приложения, исполь repo_and_org_access=Доступ к репозиторию и организации permissions_public_only=Только публичные permissions_access_all=Все (публичные, приватные и ограниченные) -select_permissions=Выбрать разрешения permission_no_access=Нет доступа permission_read=Прочитанные permission_write=Чтение и запись @@ -2268,7 +2267,6 @@ diff.commit=Коммит diff.git-notes=Заметки diff.data_not_available=Разница недоступна diff.options_button=Опции Diff -diff.show_diff_stats=Показать статистику diff.download_patch=Скачать Patch файл diff.download_diff=Скачать Diff файл diff.show_split_view=Разделённый вид diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini index 80db8862fe..4857fa8d88 100644 --- a/options/locale/locale_si-LK.ini +++ b/options/locale/locale_si-LK.ini @@ -1739,7 +1739,6 @@ diff.commit=කැප diff.git-notes=සටහන් diff.data_not_available=Diff අන්තර්ගත ලබාගත නොහැක diff.options_button=විවිධ විකල්ප -diff.show_diff_stats=සංඛ්යාන පෙන්වන්න diff.download_patch=පැච් ගොනුව බාගත diff.download_diff=බාගත Dff ගොනුව diff.show_split_view=භේදය දැක්ම diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 0454512402..72e3f4f9c5 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -895,7 +895,6 @@ delete_token_success=Jeton silindi. Onu kullanan uygulamalar artık hesabınıza repo_and_org_access=Depo ve Organizasyon Erişimi permissions_public_only=Yalnızca herkese açık permissions_access_all=Tümü (herkese açık, özel ve sınırlı) -select_permissions=İzinleri seçin permission_not_set=Ayarlanmadı permission_no_access=Erişim Yok permission_read=Okunmuş @@ -2470,7 +2469,6 @@ diff.commit=işleme diff.git-notes=Notlar diff.data_not_available=Farklı İçerik Mevut Değil diff.options_button=Diff Seçenekleri -diff.show_diff_stats=İstatistikleri Göster diff.download_patch=Yama Dosyasını İndir diff.download_diff=Diff Dosyasını İndir diff.show_split_view=Görünümü Böl diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 25ebb843a9..4071659304 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -1789,7 +1789,6 @@ diff.commit=коміт diff.git-notes=Примітки diff.data_not_available=Різниця недоступна diff.options_button=Параметри порівняння -diff.show_diff_stats=Показати статистику diff.download_patch=Завантажити патч diff.download_diff=Завантажити файл різниці diff.show_split_view=Розділений перегляд diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 3b6aca4e92..fe44b7067b 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -910,7 +910,6 @@ delete_token_success=令牌已经被删除。使用该令牌的应用将不再 repo_and_org_access=仓库和组织访问权限 permissions_public_only=仅公开 permissions_access_all=全部(公开、私有和受限) -select_permissions=选择权限 permission_not_set=未设置 permission_no_access=无访问权限 permission_read=可读 @@ -2569,7 +2568,6 @@ diff.commit=当前提交 diff.git-notes=Notes diff.data_not_available=比较内容不可用 diff.options_button=Diff 选项 -diff.show_diff_stats=显示统计 diff.download_patch=下载 Patch 文件 diff.download_diff=下载 Diff 文件 diff.show_split_view=分列视图 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 737f183f73..4f3cfe20c8 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -907,7 +907,6 @@ delete_token_success=已刪除 Token。使用此 Token 的應用程式無法再 repo_and_org_access=儲存庫和組織存取 permissions_public_only=僅公開 permissions_access_all=全部 (公開、私有與受限) -select_permissions=選擇權限 permission_not_set=尚未設定 permission_no_access=沒有權限 permission_read=讀取 @@ -2560,7 +2559,6 @@ diff.commit=當前提交 diff.git-notes=備註 diff.data_not_available=沒有內容比較可以使用 diff.options_button=差異選項 -diff.show_diff_stats=顯示統計資料 diff.download_patch=下載 Patch 檔 diff.download_diff=下載差異檔 diff.show_split_view=分割檢視 diff --git a/routers/api/packages/composer/api.go b/routers/api/packages/composer/api.go index a3bcf80417..a3ea2c2f9a 100644 --- a/routers/api/packages/composer/api.go +++ b/routers/api/packages/composer/api.go @@ -66,6 +66,7 @@ type PackageMetadataResponse struct { } // PackageVersionMetadata contains package metadata +// https://getcomposer.org/doc/05-repositories.md#package type PackageVersionMetadata struct { *composer_module.Metadata Name string `json:"name"` @@ -73,6 +74,7 @@ type PackageVersionMetadata struct { Type string `json:"type"` Created time.Time `json:"time"` Dist Dist `json:"dist"` + Source Source `json:"source"` } // Dist contains package download information @@ -82,6 +84,13 @@ type Dist struct { Checksum string `json:"shasum"` } +// Source contains package source information +type Source struct { + URL string `json:"url"` + Type string `json:"type"` + Reference string `json:"reference"` +} + func createPackageMetadataResponse(registryURL string, pds []*packages_model.PackageDescriptor) *PackageMetadataResponse { versions := make([]*PackageVersionMetadata, 0, len(pds)) @@ -94,7 +103,7 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac } } - versions = append(versions, &PackageVersionMetadata{ + pkg := PackageVersionMetadata{ Name: pd.Package.Name, Version: pd.Version.Version, Type: packageType, @@ -105,7 +114,16 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac URL: fmt.Sprintf("%s/files/%s/%s/%s", registryURL, url.PathEscape(pd.Package.LowerName), url.PathEscape(pd.Version.LowerVersion), url.PathEscape(pd.Files[0].File.LowerName)), Checksum: pd.Files[0].Blob.HashSHA1, }, - }) + } + if pd.Repository != nil { + pkg.Source = Source{ + URL: pd.Repository.HTMLURL(), + Type: "git", + Reference: pd.Version.Version, + } + } + + versions = append(versions, &pkg) } return &PackageMetadataResponse{ diff --git a/routers/api/packages/maven/api.go b/routers/api/packages/maven/api.go index 167fe42b56..ec6b9cfb0e 100644 --- a/routers/api/packages/maven/api.go +++ b/routers/api/packages/maven/api.go @@ -8,7 +8,6 @@ import ( "strings" 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 @@ -22,7 +21,7 @@ type MetadataResponse struct { } // 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 versions := make([]string, 0, len(pds)) @@ -35,11 +34,9 @@ func createMetadataResponse(pds []*packages_model.PackageDescriptor) *MetadataRe latest := pds[len(pds)-1] - metadata := latest.Metadata.(*maven_module.Metadata) - resp := &MetadataResponse{ - GroupID: metadata.GroupID, - ArtifactID: metadata.ArtifactID, + GroupID: groupID, + ArtifactID: artifactID, Latest: latest.Version.Version, Version: versions, } diff --git a/routers/api/packages/maven/maven.go b/routers/api/packages/maven/maven.go index 4d04d4d1e9..4f9ced25b4 100644 --- a/routers/api/packages/maven/maven.go +++ b/routers/api/packages/maven/maven.go @@ -84,20 +84,19 @@ func handlePackageFile(ctx *context.Context, serveContent bool) { } func serveMavenMetadata(ctx *context.Context, params parameters) { - // /com/foo/project/maven-metadata.xml[.md5/.sha1/.sha256/.sha512] - - pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageName()) - if errors.Is(err, util.ErrNotExist) { - pvs, err = packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageNameLegacy()) - } + // 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") + pvsLegacy, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageNameLegacy()) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } - if len(pvs) == 0 { - apiError(ctx, http.StatusNotFound, packages_model.ErrPackageNotExist) + pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageName()) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) return } + pvs = append(pvsLegacy, pvs...) pds, err := packages_model.GetPackageDescriptors(ctx, pvs) if err != nil { @@ -110,7 +109,7 @@ func serveMavenMetadata(ctx *context.Context, params parameters) { 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 { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 907a2f08fe..bc76b5285e 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -7,8 +7,6 @@ // This documentation describes the Gitea API. // // Schemes: https, http -// BasePath: /api/v1 -// Version: {{AppVer | JSEscape}} // License: MIT http://opensource.org/licenses/MIT // // Consumes: diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go index c397d7972b..a54225f0fd 100644 --- a/routers/api/v1/repo/collaborators.go +++ b/routers/api/v1/repo/collaborators.go @@ -7,6 +7,7 @@ package repo import ( "errors" "net/http" + "strings" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" @@ -274,12 +275,13 @@ func GetRepoPermissions(ctx *context.APIContext) { // "403": // "$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") return } - collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator")) + collaborator, err := user_model.GetUserByName(ctx, collaboratorUsername) if err != nil { if user_model.IsErrUserNotExist(err) { ctx.APIError(http.StatusNotFound, err) diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 1f61ac031a..412f2cfb58 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -59,6 +59,10 @@ func ListPullRequests(ctx *context.APIContext) { // description: Name of the repo // type: string // required: true + // - name: base_branch + // in: query + // description: Filter by target base branch of the pull request + // type: string // - name: state // in: query // description: State of pull request @@ -132,6 +136,7 @@ func ListPullRequests(ctx *context.APIContext) { Labels: labelIDs, MilestoneID: ctx.FormInt64("milestone"), PosterID: posterID, + BaseBranch: ctx.FormTrim("base_branch"), }) if err != nil { ctx.APIErrorInternal(err) diff --git a/routers/web/devtest/mock_actions.go b/routers/web/devtest/mock_actions.go index e6539bb31f..3ce75dfad2 100644 --- a/routers/web/devtest/mock_actions.go +++ b/routers/web/devtest/mock_actions.go @@ -52,13 +52,22 @@ func generateMockStepsLog(logCur actions.LogCursor) (stepsLog []*actions.ViewSte return stepsLog } -func MockActionsRunsJobs(ctx *context.Context) { - req := web.GetForm(ctx).(*actions.ViewRequest) +func MockActionsView(ctx *context.Context) { + 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.State.Run.TitleHTML = `mock run title link` 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.WorkflowID = "workflow-id" resp.State.Run.WorkflowLink = "./workflow-link" @@ -85,6 +94,29 @@ func MockActionsRunsJobs(ctx *context.Context) { Size: 1024 * 1024, 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{ Summary: "step 0 (mock slow)", Duration: time.Hour.String(), diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 9c12ef9297..2728eda8b6 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -344,18 +344,30 @@ func Diff(ctx *context.Context) { ctx.Data["Reponame"] = repoName var parentCommit *git.Commit + var parentCommitID string if commit.ParentCount() > 0 { parentCommit, err = gitRepo.GetCommit(parents[0]) if err != nil { ctx.NotFound(err) return } + parentCommitID = parentCommit.ID.String() } setCompareContext(ctx, parentCommit, commit, userName, repoName) ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitID) ctx.Data["Commit"] = commit ctx.Data["Diff"] = diff + if !fileOnly { + diffTree, err := gitdiff.GetDiffTree(ctx, gitRepo, false, parentCommitID, commitID) + if err != nil { + ctx.ServerError("GetDiffTree", err) + return + } + + ctx.PageData["DiffFiles"] = transformDiffTreeForUI(diffTree, nil) + } + statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll) if err != nil { log.Error("GetLatestCommitStatus: %v", err) diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 71bce759a9..5165636a52 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -633,6 +633,16 @@ func PrepareCompareDiff( ctx.Data["Diff"] = diff ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0 + if !fileOnly { + diffTree, err := gitdiff.GetDiffTree(ctx, ci.HeadGitRepo, false, beforeCommitID, headCommitID) + if err != nil { + ctx.ServerError("GetDiffTree", err) + return false + } + + ctx.PageData["DiffFiles"] = transformDiffTreeForUI(diffTree, nil) + } + headCommit, err := ci.HeadGitRepo.GetCommit(headCommitID) if err != nil { ctx.ServerError("GetCommit", err) diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index e27040edc6..5b7b0188dc 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -78,7 +78,7 @@ func httpBase(ctx *context.Context) *serviceHandler { strings.HasSuffix(ctx.Req.URL.Path, "git-upload-archive") { isPull = true } else { - isPull = ctx.Req.Method == "GET" + isPull = ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET" } var accessMode perm.AccessMode diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 223f8d017e..0769f456ec 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -761,6 +761,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi var methodWithError string var diff *gitdiff.Diff + shouldGetUserSpecificDiff := false // if we're not logged in or only a single commit (or commit range) is shown we // have to load only the diff and not get the viewed information @@ -772,6 +773,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi } else { diff, err = gitdiff.SyncAndGetUserSpecificDiff(ctx, ctx.Doer.ID, pull, gitRepo, diffOptions, files...) methodWithError = "SyncAndGetUserSpecificDiff" + shouldGetUserSpecificDiff = true } if err != nil { ctx.ServerError(methodWithError, err) @@ -816,6 +818,27 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi } } + if !fileOnly { + // note: use mergeBase is set to false because we already have the merge base from the pull request info + diffTree, err := gitdiff.GetDiffTree(ctx, gitRepo, false, pull.MergeBase, headCommitID) + if err != nil { + ctx.ServerError("GetDiffTree", err) + return + } + + filesViewedState := make(map[string]pull_model.ViewedState) + if shouldGetUserSpecificDiff { + // This sort of sucks because we already fetch this when getting the diff + review, err := pull_model.GetNewestReviewState(ctx, ctx.Doer.ID, issue.ID) + if err == nil && review != nil && review.UpdatedFiles != nil { + // If there wasn't an error and we have a review with updated files, use that + filesViewedState = review.UpdatedFiles + } + } + + ctx.PageData["DiffFiles"] = transformDiffTreeForUI(diffTree, filesViewedState) + } + ctx.Data["Diff"] = diff ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0 diff --git a/routers/web/repo/tree.go b/routers/web/repo/tree.go index b4d214e4d0..4b8211c957 100644 --- a/routers/web/repo/tree.go +++ b/routers/web/repo/tree.go @@ -7,9 +7,11 @@ import ( "errors" "net/http" + pull_model "code.gitea.io/gitea/models/pull" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/services/context" + "code.gitea.io/gitea/services/gitdiff" files_service "code.gitea.io/gitea/services/repository/files" "github.com/go-enry/go-enry/v2" @@ -55,6 +57,36 @@ func isExcludedEntry(entry *git.TreeEntry) bool { return false } +type FileDiffFile struct { + Name string + NameHash string + IsSubmodule bool + IsViewed bool + Status string +} + +// transformDiffTreeForUI transforms a DiffTree into a slice of FileDiffFile for UI rendering +// it also takes a map of file names to their viewed state, which is used to mark files as viewed +func transformDiffTreeForUI(diffTree *gitdiff.DiffTree, filesViewedState map[string]pull_model.ViewedState) []FileDiffFile { + files := make([]FileDiffFile, 0, len(diffTree.Files)) + + for _, file := range diffTree.Files { + nameHash := git.HashFilePathForWebUI(file.HeadPath) + isSubmodule := file.HeadMode == git.EntryModeCommit + isViewed := filesViewedState[file.HeadPath] == pull_model.Viewed + + files = append(files, FileDiffFile{ + Name: file.HeadPath, + NameHash: nameHash, + IsSubmodule: isSubmodule, + IsViewed: isViewed, + Status: file.Status, + }) + } + + return files +} + func Tree(ctx *context.Context) { recursive := ctx.FormBool("recursive") diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index cf71d01dc1..1f6c97a5cc 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -6,12 +6,14 @@ package setting import ( "net/http" + "strings" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" @@ -39,18 +41,29 @@ func ApplicationsPost(ctx *context.Context) { ctx.Data["PageIsSettingsApplications"] = true ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) - if ctx.HasError() { - loadApplicationsData(ctx) - - ctx.HTML(http.StatusOK, tplSettingsApplications) - return + _ = ctx.Req.ParseForm() + var scopeNames []string + for k, v := range ctx.Req.Form { + if strings.HasPrefix(k, "scope-") { + scopeNames = append(scopeNames, v...) + } } - scope, err := form.GetScope() + scope, err := auth_model.AccessTokenScope(strings.Join(scopeNames, ",")).Normalize() if err != nil { ctx.ServerError("GetScope", err) return } + if scope == "" || scope == auth_model.AccessTokenScopePublicOnly { + ctx.Flash.Error(ctx.Tr("settings.at_least_one_permission"), true) + } + + if ctx.HasError() { + loadApplicationsData(ctx) + ctx.HTML(http.StatusOK, tplSettingsApplications) + return + } + t := &auth_model.AccessToken{ UID: ctx.Doer.ID, Name: form.Name, @@ -99,7 +112,14 @@ func loadApplicationsData(ctx *context.Context) { } ctx.Data["Tokens"] = tokens ctx.Data["EnableOAuth2"] = setting.OAuth2.Enabled - ctx.Data["IsAdmin"] = ctx.Doer.IsAdmin + + // Handle specific ordered token categories for admin or non-admin users + tokenCategoryNames := auth_model.GetAccessTokenCategories() + if !ctx.Doer.IsAdmin { + tokenCategoryNames = util.SliceRemoveAll(tokenCategoryNames, "admin") + } + ctx.Data["TokenCategories"] = tokenCategoryNames + if setting.OAuth2.Enabled { ctx.Data["Applications"], err = db.Find[auth_model.OAuth2Application](ctx, auth_model.FindOAuth2ApplicationsOptions{ OwnerID: ctx.Doer.ID, diff --git a/routers/web/web.go b/routers/web/web.go index bba31ffb3a..4116a68322 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1640,6 +1640,7 @@ func registerRoutes(m *web.Router) { m.Any("", devtest.List) m.Any("/fetch-action-test", devtest.FetchActionTest) 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) }) } diff --git a/services/context/api.go b/services/context/api.go index 230c3456d1..c163de036c 100644 --- a/services/context/api.go +++ b/services/context/api.go @@ -291,6 +291,11 @@ func RepoRefForAPI(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { ctx := GetAPIContext(req) + if ctx.Repo.Repository.IsEmpty { + ctx.APIErrorNotFound("repository is empty") + return + } + if ctx.Repo.GitRepo == nil { ctx.APIErrorInternal(fmt.Errorf("no open git repo")) return diff --git a/services/context/context.go b/services/context/context.go index 5e08fba442..f3a0f0bb5f 100644 --- a/services/context/context.go +++ b/services/context/context.go @@ -213,13 +213,16 @@ func Contexter() func(next http.Handler) http.Handler { // Attention: this function changes ctx.Data and ctx.Flash // If HasError is called, then before Redirect, the error message should be stored by ctx.Flash.Error(ctx.GetErrMsg()) again. func (ctx *Context) HasError() bool { - hasErr, ok := ctx.Data["HasError"] - if !ok { + hasErr, _ := ctx.Data["HasError"].(bool) + hasErr = hasErr || ctx.Flash.ErrorMsg != "" + if !hasErr { return false } - ctx.Flash.ErrorMsg = ctx.GetErrMsg() + if ctx.Flash.ErrorMsg == "" { + ctx.Flash.ErrorMsg = ctx.GetErrMsg() + } ctx.Data["Flash"] = ctx.Flash - return hasErr.(bool) + return hasErr } // GetErrMsg returns error message in form validation. diff --git a/services/forms/user_form.go b/services/forms/user_form.go index ed79936add..c9ce71e886 100644 --- a/services/forms/user_form.go +++ b/services/forms/user_form.go @@ -7,9 +7,7 @@ package forms import ( "mime/multipart" "net/http" - "strings" - auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web/middleware" @@ -347,8 +345,7 @@ func (f *EditVariableForm) Validate(req *http.Request, errs binding.Errors) bind // NewAccessTokenForm form for creating access token type NewAccessTokenForm struct { - Name string `binding:"Required;MaxSize(255)" locale:"settings.token_name"` - Scope []string + Name string `binding:"Required;MaxSize(255)" locale:"settings.token_name"` } // Validate validates the fields @@ -357,12 +354,6 @@ func (f *NewAccessTokenForm) Validate(req *http.Request, errs binding.Errors) bi return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } -func (f *NewAccessTokenForm) GetScope() (auth_model.AccessTokenScope, error) { - scope := strings.Join(f.Scope, ",") - s, err := auth_model.AccessTokenScope(scope).Normalize() - return s, err -} - // EditOAuth2ApplicationForm form for editing oauth2 applications type EditOAuth2ApplicationForm struct { Name string `binding:"Required;MaxSize(255)" form:"application_name"` diff --git a/services/forms/user_form_test.go b/services/forms/user_form_test.go index 66050187c9..b4120f20ed 100644 --- a/services/forms/user_form_test.go +++ b/services/forms/user_form_test.go @@ -4,10 +4,8 @@ package forms import ( - "strconv" "testing" - auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/modules/setting" "github.com/gobwas/glob" @@ -104,28 +102,3 @@ func TestRegisterForm_IsDomainAllowed_BlockedEmail(t *testing.T) { assert.Equal(t, v.valid, form.IsEmailDomainAllowed()) } } - -func TestNewAccessTokenForm_GetScope(t *testing.T) { - tests := []struct { - form NewAccessTokenForm - scope auth_model.AccessTokenScope - expectedErr error - }{ - { - form: NewAccessTokenForm{Name: "test", Scope: []string{"read:repository"}}, - scope: "read:repository", - }, - { - form: NewAccessTokenForm{Name: "test", Scope: []string{"read:repository", "write:user"}}, - scope: "read:repository,write:user", - }, - } - - for i, test := range tests { - t.Run(strconv.Itoa(i), func(t *testing.T) { - scope, err := test.form.GetScope() - assert.Equal(t, test.expectedErr, err) - assert.Equal(t, test.scope, scope) - }) - } -} diff --git a/services/packages/package_update.go b/services/packages/package_update.go index 8d851fac53..4a22ee7a62 100644 --- a/services/packages/package_update.go +++ b/services/packages/package_update.go @@ -44,16 +44,17 @@ func UnlinkFromRepository(ctx context.Context, pkg *packages_model.Package, doer } repo, err := repo_model.GetRepositoryByID(ctx, pkg.RepoID) - if err != nil { + if err != nil && !repo_model.IsErrRepoNotExist(err) { return fmt.Errorf("error getting repository %d: %w", pkg.RepoID, err) } - - perms, err := access_model.GetUserRepoPermission(ctx, repo, doer) - if err != nil { - return fmt.Errorf("error getting permissions for user %d on repository %d: %w", doer.ID, repo.ID, err) - } - if !perms.CanWrite(unit.TypePackages) { - return util.ErrPermissionDenied + if err == nil { + perms, err := access_model.GetUserRepoPermission(ctx, repo, doer) + if err != nil { + return fmt.Errorf("error getting permissions for user %d on repository %d: %w", doer.ID, repo.ID, err) + } + if !perms.CanWrite(unit.TypePackages) { + return util.ErrPermissionDenied + } } user, err := user_model.GetUserByID(ctx, pkg.OwnerID) diff --git a/services/pull/pull.go b/services/pull/pull.go index 5d3758eca6..e4db3127b9 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -407,11 +407,10 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string, } if isSync { - requests := issues_model.PullRequestList(prs) - if err = requests.LoadAttributes(ctx); err != nil { + if err = prs.LoadAttributes(ctx); err != nil { log.Error("PullRequestList.LoadAttributes: %v", err) } - if invalidationErr := checkForInvalidation(ctx, requests, repoID, doer, branch); invalidationErr != nil { + if invalidationErr := checkForInvalidation(ctx, prs, repoID, doer, branch); invalidationErr != nil { log.Error("checkForInvalidation: %v", invalidationErr) } if err == nil { @@ -645,7 +644,7 @@ func retargetBranchPulls(ctx context.Context, doer *user_model.User, repoID int6 return err } - if err := issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil { + if err := prs.LoadAttributes(ctx); err != nil { return err } @@ -672,11 +671,11 @@ func AdjustPullsCausedByBranchDeleted(ctx context.Context, doer *user_model.User return err } - if err := issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil { + if err := prs.LoadAttributes(ctx); err != nil { return err } - issues_model.PullRequestList(prs).SetHeadRepo(repo) - if err := issues_model.PullRequestList(prs).LoadRepositories(ctx); err != nil { + prs.SetHeadRepo(repo) + if err := prs.LoadRepositories(ctx); err != nil { return err } @@ -707,11 +706,11 @@ func AdjustPullsCausedByBranchDeleted(ctx context.Context, doer *user_model.User return err } - if err := issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil { + if err := prs.LoadAttributes(ctx); err != nil { return err } - issues_model.PullRequestList(prs).SetBaseRepo(repo) - if err := issues_model.PullRequestList(prs).LoadRepositories(ctx); err != nil { + prs.SetBaseRepo(repo) + if err := prs.LoadRepositories(ctx); err != nil { return err } @@ -744,7 +743,7 @@ func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *re return err } - if err = issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil { + if err = prs.LoadAttributes(ctx); err != nil { return err } diff --git a/services/repository/delete.go b/services/repository/delete.go index fb3fffdca7..3b953d3ec7 100644 --- a/services/repository/delete.go +++ b/services/repository/delete.go @@ -14,6 +14,7 @@ import ( git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" + packages_model "code.gitea.io/gitea/models/packages" access_model "code.gitea.io/gitea/models/perm/access" project_model "code.gitea.io/gitea/models/project" repo_model "code.gitea.io/gitea/models/repo" @@ -267,6 +268,11 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID 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 { return err } diff --git a/services/repository/repository.go b/services/repository/repository.go index 59b4491132..fcc617979e 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -11,7 +11,6 @@ import ( "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" - packages_model "code.gitea.io/gitea/models/packages" repo_model "code.gitea.io/gitea/models/repo" system_model "code.gitea.io/gitea/models/system" "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) } - if err := DeleteRepositoryDirectly(ctx, doer, repo.ID); err != nil { - return err - } - - return packages_model.UnlinkRepositoryFromAllPackages(ctx, repo.ID) + return DeleteRepositoryDirectly(ctx, doer, repo.ID) } // PushCreateRepo creates a repository when a new repository is pushed to an appropriate namespace diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go index 4707602cdf..df32d5741e 100644 --- a/services/webhook/deliver.go +++ b/services/webhook/deliver.go @@ -18,6 +18,7 @@ import ( "sync" "time" + user_model "code.gitea.io/gitea/models/user" webhook_model "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/hostmatcher" @@ -92,10 +93,10 @@ func newDefaultRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook } body = []byte(t.PayloadContent) - return req, body, addDefaultHeaders(req, []byte(w.Secret), t, body) + return req, body, addDefaultHeaders(req, []byte(w.Secret), w, t, body) } -func addDefaultHeaders(req *http.Request, secret []byte, t *webhook_model.HookTask, payloadContent []byte) error { +func addDefaultHeaders(req *http.Request, secret []byte, w *webhook_model.Webhook, t *webhook_model.HookTask, payloadContent []byte) error { var signatureSHA1 string var signatureSHA256 string if len(secret) > 0 { @@ -112,10 +113,27 @@ func addDefaultHeaders(req *http.Request, secret []byte, t *webhook_model.HookTa event := t.EventType.Event() eventType := string(t.EventType) + targetType := "default" + if w.IsSystemWebhook { + targetType = "system" + } else if w.RepoID != 0 { + targetType = "repository" + } else if w.OwnerID != 0 { + owner, err := user_model.GetUserByID(req.Context(), w.OwnerID) + if owner != nil && err == nil { + if owner.IsOrganization() { + targetType = "organization" + } else { + targetType = "user" + } + } + } + req.Header.Add("X-Gitea-Delivery", t.UUID) req.Header.Add("X-Gitea-Event", event) req.Header.Add("X-Gitea-Event-Type", eventType) req.Header.Add("X-Gitea-Signature", signatureSHA256) + req.Header.Add("X-Gitea-Hook-Installation-Target-Type", targetType) req.Header.Add("X-Gogs-Delivery", t.UUID) req.Header.Add("X-Gogs-Event", event) req.Header.Add("X-Gogs-Event-Type", eventType) @@ -125,6 +143,7 @@ func addDefaultHeaders(req *http.Request, secret []byte, t *webhook_model.HookTa req.Header["X-GitHub-Delivery"] = []string{t.UUID} req.Header["X-GitHub-Event"] = []string{event} req.Header["X-GitHub-Event-Type"] = []string{eventType} + req.Header["X-GitHub-Hook-Installation-Target-Type"] = []string{targetType} return nil } diff --git a/services/webhook/matrix.go b/services/webhook/matrix.go index ec21712837..fb602f3860 100644 --- a/services/webhook/matrix.go +++ b/services/webhook/matrix.go @@ -56,7 +56,7 @@ func newMatrixRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_mo } req.Header.Set("Content-Type", "application/json") - return req, body, addDefaultHeaders(req, []byte(w.Secret), t, body) // likely useless, but has always been sent historially + return req, body, addDefaultHeaders(req, []byte(w.Secret), w, t, body) // likely useless, but has always been sent historially } const matrixPayloadSizeLimit = 1024 * 64 diff --git a/services/webhook/payloader.go b/services/webhook/payloader.go index c29ad8ac92..d98c20f479 100644 --- a/services/webhook/payloader.go +++ b/services/webhook/payloader.go @@ -107,7 +107,7 @@ func newJSONRequest[T any](pc payloadConvertor[T], w *webhook_model.Webhook, t * req.Header.Set("Content-Type", "application/json") if withDefaultHeaders { - return req, body, addDefaultHeaders(req, []byte(w.Secret), t, body) + return req, body, addDefaultHeaders(req, []byte(w.Secret), w, t, body) } return req, body, nil } diff --git a/templates/admin/user/view_details.tmpl b/templates/admin/user/view_details.tmpl index be2f32b5ec..db61bc9359 100644 --- a/templates/admin/user/view_details.tmpl +++ b/templates/admin/user/view_details.tmpl @@ -9,30 +9,25 @@ {{if .User.IsAdmin}} {{ctx.Locale.Tr "admin.users.admin"}} {{end}} + {{if .User.IsTypeBot}} + {{ctx.Locale.Tr "admin.users.bot"}} + {{end}}
{{ctx.Locale.Tr "admin.users.auth_source"}}: - {{if eq .LoginSource.ID 0}} - {{ctx.Locale.Tr "admin.users.local"}} - {{else}} - {{.LoginSource.Name}} - {{end}} + {{Iif (eq .LoginSource.ID 0) (ctx.Locale.Tr "admin.users.local") .LoginSource.Name}}
{{ctx.Locale.Tr "admin.users.activated"}}: - {{if .User.IsActive}} - {{svg "octicon-check"}} - {{else}} - {{svg "octicon-x"}} - {{end}} + {{svg (Iif .User.IsActive "octicon-check" "octicon-x")}} +
+
+ {{ctx.Locale.Tr "admin.users.prohibit_login"}}: + {{svg (Iif .User.ProhibitLogin "octicon-check" "octicon-x")}}
{{ctx.Locale.Tr "admin.users.restricted"}}: - {{if .User.IsRestricted}} - {{svg "octicon-check"}} - {{else}} - {{svg "octicon-x"}} - {{end}} + {{svg (Iif .User.IsRestricted "octicon-check" "octicon-x")}}
{{ctx.Locale.Tr "settings.visibility"}}: @@ -42,11 +37,7 @@
{{ctx.Locale.Tr "admin.users.2fa"}}: - {{if .TwoFactorEnabled}} - {{svg "octicon-check"}} - {{else}} - {{svg "octicon-x"}} - {{end}} + {{svg (Iif .TwoFactorEnabled "octicon-check" "octicon-x")}}
{{if .User.Language}}
diff --git a/templates/admin/user/view_emails.tmpl b/templates/admin/user/view_emails.tmpl index 22ce305a88..7e77206f1c 100644 --- a/templates/admin/user/view_emails.tmpl +++ b/templates/admin/user/view_emails.tmpl @@ -3,7 +3,7 @@
- {{.Email}} + {{.Email}} {{if .IsPrimary}}
{{ctx.Locale.Tr "settings.primary"}}
{{end}} diff --git a/templates/base/alert.tmpl b/templates/base/alert.tmpl index 760d3bfa2c..3f6d77a645 100644 --- a/templates/base/alert.tmpl +++ b/templates/base/alert.tmpl @@ -1,20 +1,20 @@ -{{if .Flash.ErrorMsg}} +{{- if .Flash.ErrorMsg -}}

{{.Flash.ErrorMsg | SanitizeHTML}}

-{{end}} -{{if .Flash.SuccessMsg}} +{{- end -}} +{{- if .Flash.SuccessMsg -}}

{{.Flash.SuccessMsg | SanitizeHTML}}

-{{end}} -{{if .Flash.InfoMsg}} +{{- end -}} +{{- if .Flash.InfoMsg -}}

{{.Flash.InfoMsg | SanitizeHTML}}

-{{end}} -{{if .Flash.WarningMsg}} +{{- end -}} +{{- if .Flash.WarningMsg -}}

{{.Flash.WarningMsg | SanitizeHTML}}

-{{end}} +{{- end -}} diff --git a/templates/base/paginate.tmpl b/templates/base/paginate.tmpl index 9a7a6322f7..253892c009 100644 --- a/templates/base/paginate.tmpl +++ b/templates/base/paginate.tmpl @@ -17,7 +17,7 @@ {{if eq .Num -1}} ... {{else}} - {{.Num}} + {{.Num}} {{end}} {{end}} diff --git a/templates/devtest/repo-action-view.tmpl b/templates/devtest/repo-action-view.tmpl index 9c6bdf13da..677eccc062 100644 --- a/templates/devtest/repo-action-view.tmpl +++ b/templates/devtest/repo-action-view.tmpl @@ -1,8 +1,13 @@ {{template "base/head" .}}
+ {{template "repo/actions/view_component" (dict - "RunIndex" 1 - "JobIndex" 2 + "RunIndex" (or .RunID 10) + "JobIndex" (or .JobID 100) "ActionsURL" (print AppSubUrl "/devtest/actions-mock") )}}
diff --git a/templates/repo/clone_panel.tmpl b/templates/repo/clone_panel.tmpl index b813860150..2ed8f52fbe 100644 --- a/templates/repo/clone_panel.tmpl +++ b/templates/repo/clone_panel.tmpl @@ -14,6 +14,7 @@ {{if $.CloneButtonShowSSH}} {{end}} +
diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index a3b64b8a11..1a0a5c04a8 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -1,6 +1,6 @@ {{$showFileTree := (and (not .DiffNotAvailable) (gt .Diff.NumFiles 1))}}
-
+
{{if $showFileTree}}
{{end}} - -
{{end}}
{{if $showFileTree}} @@ -106,7 +80,7 @@ {{$showFileViewToggle := or $isImage (and (not $file.IsIncomplete) $isCsv)}} {{$isExpandable := or (gt $file.Addition 0) (gt $file.Deletion 0) $file.IsBin}} {{$isReviewFile := and $.IsSigned $.PageIsPullFiles (not $.Repository.IsArchived) $.IsShowingAllCommits}} -
+

+ +
{{if .EnableOAuth2}} diff --git a/templates/user/settings/applications_oauth2_list.tmpl b/templates/user/settings/applications_oauth2_list.tmpl index 61098e118b..418d8e9cfc 100644 --- a/templates/user/settings/applications_oauth2_list.tmpl +++ b/templates/user/settings/applications_oauth2_list.tmpl @@ -48,33 +48,33 @@

-
- {{ctx.Locale.Tr "settings.create_oauth2_application"}} -
-
- {{.CsrfTokenHtml}} -
- - -
-
- - -
-
-
- - +
+

{{ctx.Locale.Tr "settings.create_oauth2_application"}}

+ + {{.CsrfTokenHtml}} +
+ +
-
-
-
- - +
+ +
-
- - +
+
+ + +
+
+
+
+ + +
+
+ + +
diff --git a/tests/integration/api_admin_org_test.go b/tests/integration/api_admin_org_test.go index a29d0ba1d7..b243856127 100644 --- a/tests/integration/api_admin_org_test.go +++ b/tests/integration/api_admin_org_test.go @@ -76,7 +76,7 @@ func TestAPIAdminOrgCreateNotAdmin(t *testing.T) { defer tests.PrepareTestEnv(t)() nonAdminUsername := "user2" session := loginUser(t, nonAdminUsername) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) org := api.CreateOrgOption{ UserName: "user2_org", FullName: "User2's organization", diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index 66209ee4e0..b42f05fc55 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -76,7 +76,7 @@ func TestAPIAdminDeleteUnauthorizedKey(t *testing.T) { var newPublicKey api.PublicKey DecodeJSON(t, resp, &newPublicKey) - token = getUserToken(t, normalUsername) + token = getUserToken(t, normalUsername, auth_model.AccessTokenScopeAll) req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d", adminUsername, newPublicKey.ID). AddTokenAuth(token) MakeRequest(t, req, http.StatusForbidden) @@ -139,7 +139,7 @@ func TestAPIListUsersNotLoggedIn(t *testing.T) { func TestAPIListUsersNonAdmin(t *testing.T) { defer tests.PrepareTestEnv(t)() nonAdminUsername := "user2" - token := getUserToken(t, nonAdminUsername) + token := getUserToken(t, nonAdminUsername, auth_model.AccessTokenScopeAll) req := NewRequest(t, "GET", "/api/v1/admin/users"). AddTokenAuth(token) MakeRequest(t, req, http.StatusForbidden) diff --git a/tests/integration/api_helper_for_declarative_test.go b/tests/integration/api_helper_for_declarative_test.go index 96669b46f0..f3a595540f 100644 --- a/tests/integration/api_helper_for_declarative_test.go +++ b/tests/integration/api_helper_for_declarative_test.go @@ -33,6 +33,10 @@ type APITestContext struct { func NewAPITestContext(t *testing.T, username, reponame string, scope ...auth.AccessTokenScope) APITestContext { session := loginUser(t, username) + if len(scope) == 0 { + // FIXME: legacy logic: no scope means all + scope = []auth.AccessTokenScope{auth.AccessTokenScopeAll} + } token := getTokenForLoggedInUser(t, session, scope...) return APITestContext{ Session: session, diff --git a/tests/integration/api_packages_composer_test.go b/tests/integration/api_packages_composer_test.go index 51b047ab41..bc858c7476 100644 --- a/tests/integration/api_packages_composer_test.go +++ b/tests/integration/api_packages_composer_test.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/packages" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" composer_module "code.gitea.io/gitea/modules/packages/composer" @@ -217,5 +218,39 @@ func TestPackageComposer(t *testing.T) { assert.Equal(t, "4f5fa464c3cb808a1df191dbf6cb75363f8b7072", pkgs[0].Dist.Checksum) assert.Len(t, pkgs[0].Bin, 1) assert.Equal(t, packageBin, pkgs[0].Bin[0]) + + // Test package linked to repository + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + userPkgs, err := packages.GetPackagesByType(db.DefaultContext, user.ID, packages.TypeComposer) + assert.NoError(t, err) + assert.Len(t, userPkgs, 1) + assert.EqualValues(t, 0, userPkgs[0].RepoID) + + err = packages.SetRepositoryLink(db.DefaultContext, userPkgs[0].ID, repo1.ID) + assert.NoError(t, err) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)). + AddBasicAuth(user.Name) + resp = MakeRequest(t, req, http.StatusOK) + + result = composer.PackageMetadataResponse{} + DecodeJSON(t, resp, &result) + + assert.Contains(t, result.Packages, packageName) + pkgs = result.Packages[packageName] + assert.Len(t, pkgs, 1) + assert.Equal(t, packageName, pkgs[0].Name) + assert.Equal(t, packageVersion, pkgs[0].Version) + assert.Equal(t, packageType, pkgs[0].Type) + assert.Equal(t, packageDescription, pkgs[0].Description) + assert.Len(t, pkgs[0].Authors, 1) + assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name) + assert.Equal(t, "zip", pkgs[0].Dist.Type) + assert.Equal(t, "4f5fa464c3cb808a1df191dbf6cb75363f8b7072", pkgs[0].Dist.Checksum) + assert.Len(t, pkgs[0].Bin, 1) + assert.Equal(t, packageBin, pkgs[0].Bin[0]) + assert.Equal(t, repo1.HTMLURL(), pkgs[0].Source.URL) + assert.Equal(t, "git", pkgs[0].Source.Type) + assert.Equal(t, packageVersion, pkgs[0].Source.Reference) }) } diff --git a/tests/integration/api_packages_maven_test.go b/tests/integration/api_packages_maven_test.go index 486a5af93e..408c8805c2 100644 --- a/tests/integration/api_packages_maven_test.go +++ b/tests/integration/api_packages_maven_test.go @@ -80,6 +80,7 @@ func TestPackageMaven(t *testing.T) { t.Run("UploadLegacy", func(t *testing.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" 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) @@ -97,6 +98,13 @@ func TestPackageMaven(t *testing.T) { req = NewRequest(t, "GET", "/user2/-/packages/maven/com.gitea%3Alegacy-project/1.0.2") 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, "1.0.2") + + // 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) MakeRequest(t, req, http.StatusCreated) _, 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") 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, "1.0.2") + assert.Contains(t, respBody, "1.0.3") require.NoError(t, packages.DeletePackageByID(db.DefaultContext, p.ID)) }) diff --git a/tests/integration/api_repo_collaborator_test.go b/tests/integration/api_repo_collaborator_test.go index 463db1dfb1..11e2924e84 100644 --- a/tests/integration/api_repo_collaborator_test.go +++ b/tests/integration/api_repo_collaborator_test.go @@ -5,7 +5,6 @@ package integration import ( "net/http" - "net/url" "testing" auth_model "code.gitea.io/gitea/models/auth" @@ -14,132 +13,145 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" ) func TestAPIRepoCollaboratorPermission(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - repo2Owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo2.OwnerID}) + defer tests.PrepareTestEnv(t)() + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + repo2Owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo2.OwnerID}) - user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) - user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - user10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10}) - user11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 11}) - user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + user10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10}) + user11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 11}) + 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) { - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, repo2Owner.Name). - AddTokenAuth(testCtx.Token) - resp := MakeRequest(t, req, http.StatusOK) + 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). + AddTokenAuth(testCtx.Token) + resp := MakeRequest(t, req, http.StatusOK) - var repoPermission api.RepoCollaboratorPermission - DecodeJSON(t, resp, &repoPermission) + var repoPermission api.RepoCollaboratorPermission + 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("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeRead)) + t.Run("CollaboratorWithReadAccess", func(t *testing.T) { + 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). - AddTokenAuth(testCtx.Token) - resp := MakeRequest(t, req, http.StatusOK) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). + AddTokenAuth(testCtx.Token) + resp := MakeRequest(t, req, http.StatusOK) - var repoPermission api.RepoCollaboratorPermission - DecodeJSON(t, resp, &repoPermission) + var repoPermission api.RepoCollaboratorPermission + 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("AddUserAsCollaboratorWithWriteAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeWrite)) + t.Run("CollaboratorWithWriteAccess", func(t *testing.T) { + 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). - AddTokenAuth(testCtx.Token) - resp := MakeRequest(t, req, http.StatusOK) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). + AddTokenAuth(testCtx.Token) + resp := MakeRequest(t, req, http.StatusOK) - var repoPermission api.RepoCollaboratorPermission - DecodeJSON(t, resp, &repoPermission) + var repoPermission api.RepoCollaboratorPermission + 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("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeAdmin)) + t.Run("CollaboratorWithAdminAccess", func(t *testing.T) { + 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). - AddTokenAuth(testCtx.Token) - resp := MakeRequest(t, req, http.StatusOK) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). + AddTokenAuth(testCtx.Token) + resp := MakeRequest(t, req, http.StatusOK) - var repoPermission api.RepoCollaboratorPermission - DecodeJSON(t, resp, &repoPermission) + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) - assert.Equal(t, "admin", repoPermission.Permission) - }) + assert.Equal(t, "admin", repoPermission.Permission) + }) - 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"). - AddTokenAuth(testCtx.Token) - MakeRequest(t, req, http.StatusNotFound) - }) + 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"). + AddTokenAuth(testCtx.Token) + MakeRequest(t, req, http.StatusNotFound) + }) - t.Run("CollaboratorBlocked", func(t *testing.T) { - ctx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository) - ctx.ExpectedCode = http.StatusForbidden - doAPIAddCollaborator(ctx, user34.Name, perm.AccessModeAdmin)(t) - }) + t.Run("CollaboratorBlocked", func(t *testing.T) { + ctx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository) + ctx.ExpectedCode = http.StatusForbidden + doAPIAddCollaborator(ctx, user34.Name, perm.AccessModeAdmin)(t) + }) - t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) + 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) + _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) + 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) + var repoPermission api.RepoCollaboratorPermission + 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("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) + t.Run("CollaboratorCanReadOwnPermission", func(t *testing.T) { + session := loginUser(t, user5.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - _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(token) + resp = MakeRequest(t, req, http.StatusOK) - 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) + repoCollPerm := api.RepoCollaboratorPermission{} + DecodeJSON(t, resp, &repoCollPerm) - 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) + assert.Equal(t, "read", repoCollPerm.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) + }) } diff --git a/tests/integration/api_repo_git_blobs_test.go b/tests/integration/api_repo_git_blobs_test.go index 184362e7e3..9c4be31396 100644 --- a/tests/integration/api_repo_git_blobs_test.go +++ b/tests/integration/api_repo_git_blobs_test.go @@ -72,7 +72,7 @@ func TestAPIReposGitBlobs(t *testing.T) { // Login as User4. session = loginUser(t, user4.Name) - token4 := getTokenForLoggedInUser(t, session) + token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) // Test using org repo "org3/repo3" where user4 is a NOT collaborator req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/d56a3073c1dbb7b15963110a049d50cdb5db99fc?access=%s", org3.Name, repo3.Name, token4) diff --git a/tests/integration/api_repo_git_trees_test.go b/tests/integration/api_repo_git_trees_test.go index 8eec6d8d22..47063d9091 100644 --- a/tests/integration/api_repo_git_trees_test.go +++ b/tests/integration/api_repo_git_trees_test.go @@ -69,7 +69,7 @@ func TestAPIReposGitTrees(t *testing.T) { // Login as User4. session = loginUser(t, user4.Name) - token4 := getTokenForLoggedInUser(t, session) + token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) // Test using org repo "org3/repo3" where user4 is a NOT collaborator req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/trees/d56a3073c1dbb7b15963110a049d50cdb5db99fc?access=%s", org3.Name, repo3.Name, token4) diff --git a/tests/integration/empty_repo_test.go b/tests/integration/empty_repo_test.go index b19774a826..e122531dc9 100644 --- a/tests/integration/empty_repo_test.go +++ b/tests/integration/empty_repo_test.go @@ -60,12 +60,20 @@ func TestEmptyRepoAddFile(t *testing.T) { defer tests.PrepareTestEnv(t)() session := loginUser(t, "user30") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + + // test web page req := NewRequest(t, "GET", "/user30/empty") resp := session.MakeRequest(t, req, http.StatusOK) bodyString := resp.Body.String() assert.Contains(t, bodyString, "empty-repo-guide") 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) resp = session.MakeRequest(t, req, http.StatusOK) doc := NewHTMLParser(t, resp.Body).Find(`input[name="commit_choice"]`) diff --git a/tests/integration/git_smart_http_test.go b/tests/integration/git_smart_http_test.go index 15336b9b81..55d647672a 100644 --- a/tests/integration/git_smart_http_test.go +++ b/tests/integration/git_smart_http_test.go @@ -9,7 +9,10 @@ import ( "net/url" "testing" + "code.gitea.io/gitea/modules/util" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestGitSmartHTTP(t *testing.T) { @@ -18,51 +21,55 @@ func TestGitSmartHTTP(t *testing.T) { func testGitSmartHTTP(t *testing.T, u *url.URL) { kases := []struct { - p string - code int + method, path string + code int }{ { - p: "user2/repo1/info/refs", + path: "user2/repo1/info/refs", code: http.StatusOK, }, { - p: "user2/repo1/HEAD", + method: "HEAD", + path: "user2/repo1/info/refs", + code: http.StatusOK, + }, + { + path: "user2/repo1/HEAD", code: http.StatusOK, }, { - p: "user2/repo1/objects/info/alternates", + path: "user2/repo1/objects/info/alternates", code: http.StatusNotFound, }, { - p: "user2/repo1/objects/info/http-alternates", + path: "user2/repo1/objects/info/http-alternates", code: http.StatusNotFound, }, { - p: "user2/repo1/../../custom/conf/app.ini", + path: "user2/repo1/../../custom/conf/app.ini", code: http.StatusNotFound, }, { - p: "user2/repo1/objects/info/../../../../custom/conf/app.ini", + path: "user2/repo1/objects/info/../../../../custom/conf/app.ini", code: http.StatusNotFound, }, { - p: `user2/repo1/objects/info/..\..\..\..\custom\conf\app.ini`, + path: `user2/repo1/objects/info/..\..\..\..\custom\conf\app.ini`, code: http.StatusBadRequest, }, } for _, kase := range kases { - t.Run(kase.p, func(t *testing.T) { - p := u.String() + kase.p - req, err := http.NewRequest("GET", p, nil) - assert.NoError(t, err) + t.Run(kase.path, func(t *testing.T) { + req, err := http.NewRequest(util.IfZero(kase.method, "GET"), u.String()+kase.path, nil) + require.NoError(t, err) req.SetBasicAuth("user2", userPassword) resp, err := http.DefaultClient.Do(req) - assert.NoError(t, err) + require.NoError(t, err) defer resp.Body.Close() assert.EqualValues(t, kase.code, resp.StatusCode) _, err = io.ReadAll(resp.Body) - assert.NoError(t, err) + require.NoError(t, err) }) } } diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index 9e487924d1..2f6b7eae31 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -249,55 +249,19 @@ func loginUserWithPassword(t testing.TB, userName, password string) *TestSession // token has to be unique this counter take care of var tokenCounter int64 -// getTokenForLoggedInUser returns a token for a logged in user. -// The scope is an optional list of snake_case strings like the frontend form fields, -// but without the "scope_" prefix. +// getTokenForLoggedInUser returns a token for a logged-in user. func getTokenForLoggedInUser(t testing.TB, session *TestSession, scopes ...auth.AccessTokenScope) string { t.Helper() - var token string - req := NewRequest(t, "GET", "/user/settings/applications") - resp := session.MakeRequest(t, req, http.StatusOK) - var csrf string - for _, cookie := range resp.Result().Cookies() { - if cookie.Name != "_csrf" { - continue - } - csrf = cookie.Value - break - } - if csrf == "" { - doc := NewHTMLParser(t, resp.Body) - csrf = doc.GetCSRF() - } - assert.NotEmpty(t, csrf) urlValues := url.Values{} - urlValues.Add("_csrf", csrf) + urlValues.Add("_csrf", GetUserCSRFToken(t, session)) urlValues.Add("name", fmt.Sprintf("api-testing-token-%d", atomic.AddInt64(&tokenCounter, 1))) for _, scope := range scopes { - urlValues.Add("scope", string(scope)) + urlValues.Add("scope-dummy", string(scope)) // it only needs to start with "scope-" to be accepted } - req = NewRequestWithURLValues(t, "POST", "/user/settings/applications", urlValues) - resp = session.MakeRequest(t, req, http.StatusSeeOther) - - // Log the flash values on failure - if !assert.Equal(t, []string{"/user/settings/applications"}, resp.Result().Header["Location"]) { - for _, cookie := range resp.Result().Cookies() { - if cookie.Name != gitea_context.CookieNameFlash { - continue - } - flash, _ := url.ParseQuery(cookie.Value) - for key, value := range flash { - t.Logf("Flash %q: %q", key, value) - } - } - } - - req = NewRequest(t, "GET", "/user/settings/applications") - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - token = htmlDoc.doc.Find(".ui.info p").Text() - assert.NotEmpty(t, token) - return token + req := NewRequestWithURLValues(t, "POST", "/user/settings/applications", urlValues) + session.MakeRequest(t, req, http.StatusSeeOther) + flashes := session.GetCookieFlashMessage() + return flashes.InfoMsg } type RequestWrapper struct { diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index 8c568a1272..38d0e7fe1f 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -130,8 +130,13 @@ func TestViewRepo1CloneLinkAnonymous(t *testing.T) { link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link") assert.True(t, exists, "The template has changed") assert.Equal(t, setting.AppURL+"user2/repo1.git", link) + _, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link") 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) { @@ -146,10 +151,15 @@ func TestViewRepo1CloneLinkAuthorized(t *testing.T) { link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link") assert.True(t, exists, "The template has changed") assert.Equal(t, setting.AppURL+"user2/repo1.git", link) + link, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link") 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) 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) { diff --git a/tests/integration/repo_webhook_test.go b/tests/integration/repo_webhook_test.go index 5fa0f5bc4e..883d9b224d 100644 --- a/tests/integration/repo_webhook_test.go +++ b/tests/integration/repo_webhook_test.go @@ -530,7 +530,9 @@ func Test_WebhookStatus(t *testing.T) { var triggeredEvent string provider := newMockWebhookProvider(func(r *http.Request) { assert.Contains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should contain status") + assert.Contains(t, r.Header["X-Github-Hook-Installation-Target-Type"], "repository", "X-GitHub-Hook-Installation-Target-Type should contain repository") assert.Contains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should contain status") + assert.Contains(t, r.Header["X-Gitea-Hook-Installation-Target-Type"], "repository", "X-Gitea-Hook-Installation-Target-Type should contain repository") assert.Contains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should contain status") content, _ := io.ReadAll(r.Body) var payload api.CommitStatusPayload diff --git a/web_src/css/base.css b/web_src/css/base.css index 76d7d82a5c..68015e4031 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -1188,3 +1188,13 @@ the "!important" is necessary to override Fomantic UI menu item styles, meanwhil overflow: hidden !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; +} diff --git a/web_src/css/index.css b/web_src/css/index.css index ce1a23b245..630aa3c2ef 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -19,7 +19,6 @@ @import "./modules/dimmer.css"; @import "./modules/modal.css"; -@import "./modules/select.css"; @import "./modules/tippy.css"; @import "./modules/breadcrumb.css"; @import "./modules/comment.css"; diff --git a/web_src/css/modules/checkbox.css b/web_src/css/modules/checkbox.css index 0a3a71acaa..f7e61ba360 100644 --- a/web_src/css/modules/checkbox.css +++ b/web_src/css/modules/checkbox.css @@ -119,3 +119,13 @@ input[type="radio"] { .ui.toggle.checkbox input:focus:checked ~ label::before { background: var(--color-primary) !important; } + +label.gt-checkbox { + display: inline-flex; + align-items: center; + gap: 0.25em; +} + +.ui.form .field > label.gt-checkbox { + display: flex; +} diff --git a/web_src/css/modules/select.css b/web_src/css/modules/select.css deleted file mode 100644 index 1d7d749d4a..0000000000 --- a/web_src/css/modules/select.css +++ /dev/null @@ -1,25 +0,0 @@ -.gitea-select { - position: relative; -} - -.gitea-select select { - appearance: none; /* hide default triangle */ -} - -/* ::before and ::after pseudo elements don't work on select elements, - so we need to put it on the parent. */ -.gitea-select::after { - position: absolute; - top: 12px; - right: 8px; - pointer-events: none; - content: ""; - width: 14px; - height: 14px; - mask-size: cover; - -webkit-mask-size: cover; - mask-image: var(--octicon-chevron-right); - -webkit-mask-image: var(--octicon-chevron-right); - transform: rotate(90deg); /* point the chevron down */ - background: currentcolor; -} diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 2752174f86..87af299dad 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -1085,10 +1085,6 @@ td .commit-summary { height: 30px; } -.repository .diff-box .resolved-placeholder .button { - padding: 8px 12px; -} - .repository .diff-file-box .header { background-color: var(--color-box-header); } diff --git a/web_src/js/components/DiffFileList.vue b/web_src/js/components/DiffFileList.vue deleted file mode 100644 index 6570c92781..0000000000 --- a/web_src/js/components/DiffFileList.vue +++ /dev/null @@ -1,60 +0,0 @@ - - - diff --git a/web_src/js/components/DiffFileTree.vue b/web_src/js/components/DiffFileTree.vue index d00d03565f..381a1c3ca4 100644 --- a/web_src/js/components/DiffFileTree.vue +++ b/web_src/js/components/DiffFileTree.vue @@ -1,75 +1,18 @@ diff --git a/web_src/js/components/DiffFileTreeItem.vue b/web_src/js/components/DiffFileTreeItem.vue index d3be10e3e9..5ee0e5bcaa 100644 --- a/web_src/js/components/DiffFileTreeItem.vue +++ b/web_src/js/components/DiffFileTreeItem.vue @@ -2,21 +2,7 @@ import {SvgIcon, type SvgName} from '../svg.ts'; import {diffTreeStore} from '../modules/stores.ts'; import {ref} from 'vue'; - -type File = { - Name: string; - NameHash: string; - Type: number; - IsViewed: boolean; - IsSubmodule: boolean; -} - -export type Item = { - name: string; - isFile: boolean; - file?: File; - children?: Item[]; -}; +import type {Item, File, FileStatus} from '../utils/filetree.ts'; defineProps<{ item: Item, @@ -25,15 +11,16 @@ defineProps<{ const store = diffTreeStore(); const collapsed = ref(false); -function getIconForDiffType(pType: number) { - const diffTypes: Record}> = { - '1': {name: 'octicon-diff-added', classes: ['text', 'green']}, - '2': {name: 'octicon-diff-modified', classes: ['text', 'yellow']}, - '3': {name: 'octicon-diff-removed', classes: ['text', 'red']}, - '4': {name: 'octicon-diff-renamed', classes: ['text', 'teal']}, - '5': {name: 'octicon-diff-renamed', classes: ['text', 'green']}, // there is no octicon for copied, so renamed should be ok +function getIconForDiffStatus(pType: FileStatus) { + const diffTypes: Record }> = { + 'added': {name: 'octicon-diff-added', classes: ['text', 'green']}, + 'modified': {name: 'octicon-diff-modified', classes: ['text', 'yellow']}, + 'deleted': {name: 'octicon-diff-removed', classes: ['text', 'red']}, + 'renamed': {name: 'octicon-diff-renamed', classes: ['text', 'teal']}, + 'copied': {name: 'octicon-diff-renamed', classes: ['text', 'green']}, + 'typechange': {name: 'octicon-diff-modified', classes: ['text', 'green']}, // there is no octicon for copied, so renamed should be ok }; - return diffTypes[String(pType)]; + return diffTypes[pType]; } function fileIcon(file: File) { @@ -48,27 +35,37 @@ function fileIcon(file: File) {
{{ item.name }} - + -
- - - - {{ item.name }} -
-
- -
+