diff --git a/Makefile b/Makefile index 5ed50a67382c..ab112584c65f 100644 --- a/Makefile +++ b/Makefile @@ -646,7 +646,9 @@ release-sources: | $(DIST_DIRS) echo $(VERSION) > $(STORED_VERSION_FILE) # bsdtar needs a ^ to prevent matching subdirectories $(eval EXCL := --exclude=$(shell tar --help | grep -q bsdtar && echo "^")./) - tar $(addprefix $(EXCL),$(TAR_EXCLUDES)) -czf $(DIST)/release/gitea-src-$(VERSION).tar.gz . +# use transform to a add a release-folder prefix; in bsdtar the transform parameter equivalent is -s + $(eval TRANSFORM := $(shell tar --help | grep -q bsdtar && echo "-s '/^./gitea-src-$(VERSION)/'" || echo "--transform 's|^./|gitea-src-$(VERSION)/|'")) + tar $(addprefix $(EXCL),$(TAR_EXCLUDES)) $(TRANSFORM) -czf $(DIST)/release/gitea-src-$(VERSION).tar.gz . rm -f $(STORED_VERSION_FILE) .PHONY: release-docs diff --git a/build.go b/build.go index aa561413407b..d379745c6dfc 100644 --- a/build.go +++ b/build.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build vendor -// +build vendor package main diff --git a/build/code-batch-process.go b/build/code-batch-process.go index b2290af77157..0f8dbd40febd 100644 --- a/build/code-batch-process.go +++ b/build/code-batch-process.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build ignore -// +build ignore package main diff --git a/build/generate-bindata.go b/build/generate-bindata.go index 7fdf9d761610..ab81dd89382e 100644 --- a/build/generate-bindata.go +++ b/build/generate-bindata.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build ignore -// +build ignore package main diff --git a/build/generate-emoji.go b/build/generate-emoji.go index 2f3536342d41..a22f2a4571e1 100644 --- a/build/generate-emoji.go +++ b/build/generate-emoji.go @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file. //go:build ignore -// +build ignore package main diff --git a/build/generate-gitignores.go b/build/generate-gitignores.go index 0f7d719d40b1..1e09c83a6a71 100644 --- a/build/generate-gitignores.go +++ b/build/generate-gitignores.go @@ -1,5 +1,4 @@ //go:build ignore -// +build ignore package main diff --git a/build/generate-licenses.go b/build/generate-licenses.go index 0f9b9f369fec..02b41a229a4e 100644 --- a/build/generate-licenses.go +++ b/build/generate-licenses.go @@ -1,5 +1,4 @@ //go:build ignore -// +build ignore package main diff --git a/build/gitea-format-imports.go b/build/gitea-format-imports.go index 67c8397b2d9a..c685ae68eeee 100644 --- a/build/gitea-format-imports.go +++ b/build/gitea-format-imports.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build ignore -// +build ignore package main diff --git a/build/gocovmerge.go b/build/gocovmerge.go index 1d2652129f9f..dfe70efdad4e 100644 --- a/build/gocovmerge.go +++ b/build/gocovmerge.go @@ -7,7 +7,6 @@ // merges them into one profile //go:build ignore -// +build ignore package main diff --git a/cmd/admin.go b/cmd/admin.go index e4a254c61390..fcf331751c3c 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -25,6 +25,7 @@ import ( repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" + "code.gitea.io/gitea/modules/util" auth_service "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/auth/source/smtp" @@ -114,6 +115,10 @@ var ( Name: "access-token", Usage: "Generate access token for the user", }, + cli.BoolFlag{ + Name: "restricted", + Usage: "Make a restricted user account", + }, }, } @@ -551,7 +556,7 @@ func runCreateUser(c *cli.Context) error { // If this is the first user being created. // Take it as the admin and don't force a password update. - if n := user_model.CountUsers(); n == 0 { + if n := user_model.CountUsers(nil); n == 0 { changePassword = false } @@ -559,17 +564,26 @@ func runCreateUser(c *cli.Context) error { changePassword = c.Bool("must-change-password") } + restricted := util.OptionalBoolNone + + if c.IsSet("restricted") { + restricted = util.OptionalBoolOf(c.Bool("restricted")) + } + u := &user_model.User{ Name: username, Email: c.String("email"), Passwd: password, - IsActive: true, IsAdmin: c.Bool("admin"), MustChangePassword: changePassword, - Theme: setting.UI.DefaultTheme, } - if err := user_model.CreateUser(u); err != nil { + overwriteDefault := &user_model.CreateUserOverwriteOptions{ + IsActive: util.OptionalBoolTrue, + IsRestricted: restricted, + } + + if err := user_model.CreateUser(u, overwriteDefault); err != nil { return fmt.Errorf("CreateUser: %v", err) } diff --git a/cmd/dump.go b/cmd/dump.go index f72ef05e948f..ea41c0c029d9 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -7,6 +7,7 @@ package cmd import ( "fmt" + "io" "os" "path" "path/filepath" @@ -25,10 +26,21 @@ import ( "github.com/urfave/cli" ) -func addFile(w archiver.Writer, filePath, absPath string, verbose bool) error { +func addReader(w archiver.Writer, r io.ReadCloser, info os.FileInfo, customName string, verbose bool) error { if verbose { - log.Info("Adding file %s\n", filePath) + log.Info("Adding file %s", customName) } + + return w.Write(archiver.File{ + FileInfo: archiver.FileInfo{ + FileInfo: info, + CustomName: customName, + }, + ReadCloser: r, + }) +} + +func addFile(w archiver.Writer, filePath, absPath string, verbose bool) error { file, err := os.Open(absPath) if err != nil { return err @@ -39,13 +51,7 @@ func addFile(w archiver.Writer, filePath, absPath string, verbose bool) error { return err } - return w.Write(archiver.File{ - FileInfo: archiver.FileInfo{ - FileInfo: fileInfo, - CustomName: filePath, - }, - ReadCloser: file, - }) + return addReader(w, file, fileInfo, filePath, verbose) } func isSubdir(upper, lower string) (bool, error) { @@ -136,6 +142,10 @@ It can be used for backup and capture Gitea server image to send to maintainer`, Name: "skip-attachment-data", Usage: "Skip attachment data", }, + cli.BoolFlag{ + Name: "skip-package-data", + Usage: "Skip package data", + }, cli.GenericFlag{ Name: "type", Value: outputTypeEnum, @@ -241,13 +251,7 @@ func runDump(ctx *cli.Context) error { return err } - return w.Write(archiver.File{ - FileInfo: archiver.FileInfo{ - FileInfo: info, - CustomName: path.Join("data", "lfs", objPath), - }, - ReadCloser: object, - }) + return addReader(w, object, info, path.Join("data", "lfs", objPath), verbose) }); err != nil { fatal("Failed to dump LFS objects: %v", err) } @@ -326,6 +330,7 @@ func runDump(ctx *cli.Context) error { excludes = append(excludes, setting.RepoRootPath) excludes = append(excludes, setting.LFS.Path) excludes = append(excludes, setting.Attachment.Path) + excludes = append(excludes, setting.Packages.Path) excludes = append(excludes, setting.LogRootPath) excludes = append(excludes, absFileName) if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil { @@ -341,17 +346,24 @@ func runDump(ctx *cli.Context) error { return err } - return w.Write(archiver.File{ - FileInfo: archiver.FileInfo{ - FileInfo: info, - CustomName: path.Join("data", "attachments", objPath), - }, - ReadCloser: object, - }) + return addReader(w, object, info, path.Join("data", "attachments", objPath), verbose) }); err != nil { fatal("Failed to dump attachments: %v", err) } + if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") { + log.Info("Skip dumping package data") + } else if err := storage.Packages.IterateObjects(func(objPath string, object storage.Object) error { + info, err := object.Stat() + if err != nil { + return err + } + + return addReader(w, object, info, path.Join("data", "packages", objPath), verbose) + }); err != nil { + fatal("Failed to dump packages: %v", err) + } + // Doesn't check if LogRootPath exists before processing --skip-log intentionally, // ensuring that it's clear the dump is skipped whether the directory's initialized // yet or not. diff --git a/cmd/embedded.go b/cmd/embedded.go index 2930e5d3077d..30fc7103d838 100644 --- a/cmd/embedded.go +++ b/cmd/embedded.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build bindata -// +build bindata package cmd diff --git a/cmd/embedded_stub.go b/cmd/embedded_stub.go index 0e9e3e6ec3e1..26228256f2a0 100644 --- a/cmd/embedded_stub.go +++ b/cmd/embedded_stub.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !bindata -// +build !bindata package cmd diff --git a/contrib/upgrade.sh b/contrib/upgrade.sh index 9a5e903b6b68..3a98c277d6b3 100755 --- a/contrib/upgrade.sh +++ b/contrib/upgrade.sh @@ -24,7 +24,8 @@ function giteacmd { if [[ $sudocmd = "su" ]]; then - "$sudocmd" - "$giteauser" -c "$giteabin" --config "$giteaconf" --work-path "$giteahome" "$@" + # `-c` only accept one string as argument. + "$sudocmd" - "$giteauser" -c "$(printf "%q " "$giteabin" "--config" "$giteaconf" "--work-path" "$giteahome" "$@")" else "$sudocmd" --user "$giteauser" "$giteabin" --config "$giteaconf" --work-path "$giteahome" "$@" fi diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 4b5c2b102272..0d0d8097552d 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -398,6 +398,7 @@ INTERNAL_TOKEN= ;; By modifying the Gitea database, users can gain Gitea administrator privileges. ;; It also enables them to access other resources available to the user on the operating system that is running the Gitea instance and perform arbitrary actions in the name of the Gitea OS user. ;; WARNING: This maybe harmful to you website or your operating system. +;; WARNING: Setting this to true does not change existing hooks in git repos; adjust it before if necessary. ;DISABLE_GIT_HOOKS = true ;; ;; Set to true to disable webhooks feature. @@ -2239,6 +2240,9 @@ PATH = ;; ;; Enable/Disable federation capabilities ; ENABLED = true +;; +;; Enable/Disable user statistics for nodeinfo if federation is enabled +; SHARE_USER_STATISTICS = true ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 9d70269bf2fd..5400f3208ea9 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -497,6 +497,7 @@ Certain queues have defaults that override the defaults set in `[queue]` (this o It also enables them to access other resources available to the user on the operating system that is running the Gitea instance and perform arbitrary actions in the name of the Gitea OS user. This maybe harmful to you website or your operating system. + Setting this to true does not change existing hooks in git repos; adjust it before if necessary. - `DISABLE_WEBHOOKS`: **false**: Set to `true` to disable webhooks feature. - `ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET`: **true**: Set to `false` to allow local users to push to gitea-repositories without setting up the Gitea environment. This is not recommended and if you want local users to push to Gitea repositories you should set the environment appropriately. - `IMPORT_LOCAL_PATHS`: **false**: Set to `false` to prevent all users (including admin) from importing local path on server. @@ -1084,6 +1085,7 @@ Task queue configuration has been moved to `queue.task`. However, the below conf ## Federation (`federation`) - `ENABLED`: **true**: Enable/Disable federation capabilities +- `SHARE_USER_STATISTICS`: **true**: Enable/Disable user statistics for nodeinfo if federation is enabled ## Packages (`packages`) diff --git a/docs/content/doc/installation/from-binary.en-us.md b/docs/content/doc/installation/from-binary.en-us.md index 59a92758e018..d3486d815099 100644 --- a/docs/content/doc/installation/from-binary.en-us.md +++ b/docs/content/doc/installation/from-binary.en-us.md @@ -50,7 +50,8 @@ Of note, configuring `GITEA_WORK_DIR` will tell Gitea where to base its working ### Prepare environment -Check that Git is installed on the server. If it is not, install it first. +Check that Git is installed on the server. If it is not, install it first. Gitea requires Git version >= 2.0. + ```sh git --version ``` diff --git a/docs/content/doc/installation/on-kubernetes.en-us.md b/docs/content/doc/installation/on-kubernetes.en-us.md index 9fe869254c1d..abfbdf16798b 100644 --- a/docs/content/doc/installation/on-kubernetes.en-us.md +++ b/docs/content/doc/installation/on-kubernetes.en-us.md @@ -25,3 +25,47 @@ helm install gitea gitea-charts/gitea ``` If you would like to customize your install, which includes kubernetes ingress, please refer to the complete [Gitea helm chart configuration details](https://gitea.com/gitea/helm-chart/) + +## Health check endpoint + +Gitea comes with a health check endpoint `/api/healthz`, you can configure it in kubernetes like this: + +```yaml + livenessProbe: + httpGet: + path: /api/healthz + port: http + initialDelaySeconds: 200 + timeoutSeconds: 5 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 10 +``` + +a successful health check response will respond with http code `200`, here's example: + +``` +HTTP/1.1 200 OK + + +{ + "status": "pass", + "description": "Gitea: Git with a cup of tea", + "checks": { + "cache:ping": [ + { + "status": "pass", + "time": "2022-02-19T09:16:08Z" + } + ], + "database:ping": [ + { + "status": "pass", + "time": "2022-02-19T09:16:08Z" + } + ] + } +} +``` + +for more information, please reference to kubernetes documentation [Define a liveness HTTP request](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-a-liveness-http-request) diff --git a/docs/content/doc/installation/on-kubernetes.zh-tw.md b/docs/content/doc/installation/on-kubernetes.zh-tw.md index 9add5c4ee1cf..5ea412aa000d 100644 --- a/docs/content/doc/installation/on-kubernetes.zh-tw.md +++ b/docs/content/doc/installation/on-kubernetes.zh-tw.md @@ -25,3 +25,47 @@ helm install gitea gitea-charts/gitea ``` 若您想自訂安裝(包括使用 kubernetes ingress),請前往完整的 [Gitea helm chart configuration details](https://gitea.com/gitea/helm-chart/) + +##運行狀況檢查終端節點 + +Gitea 附帶了一個運行狀況檢查端點 `/api/healthz`,你可以像這樣在 kubernetes 中配置它: + +```yaml + livenessProbe: + httpGet: + path: /api/healthz + port: http + initialDelaySeconds: 200 + timeoutSeconds: 5 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 10 +``` + +成功的運行狀況檢查回應將使用 HTTP 代碼 `200` 進行回應,下面是示例: + +``` +HTTP/1.1 200 OK + + +{ + "status": "pass", + "description": "Gitea: Git with a cup of tea", + "checks": { + "cache:ping": [ + { + "status": "pass", + "time": "2022-02-19T09:16:08Z" + } + ], + "database:ping": [ + { + "status": "pass", + "time": "2022-02-19T09:16:08Z" + } + ] + } +} +``` + +有關更多信息,請參考kubernetes文檔[定義一個存活態 HTTP請求接口](https://kubernetes.io/zh/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) diff --git a/docs/content/doc/installation/with-docker.fr-fr.md b/docs/content/doc/installation/with-docker.fr-fr.md index 0011ba2ff442..176abf7a129d 100644 --- a/docs/content/doc/installation/with-docker.fr-fr.md +++ b/docs/content/doc/installation/with-docker.fr-fr.md @@ -43,7 +43,7 @@ Vous devriez avoir une instance fonctionnelle de Gitea. Pour accèder à l'inter ## Named Volumes -Ce guide aboutira à une installation avec les données Gita et PostgreSQL stockées dans des volumes nommés. Cela permet une sauvegarde, une restauration et des mises à niveau en toute simplicité. +Ce guide aboutira à une installation avec les données Gitea et PostgreSQL stockées dans des volumes nommés. Cela permet une sauvegarde, une restauration et des mises à niveau en toute simplicité. ### The Database diff --git a/docs/content/doc/usage/command-line.en-us.md b/docs/content/doc/usage/command-line.en-us.md index 3b75a5c843d2..8cc420ed11b1 100644 --- a/docs/content/doc/usage/command-line.en-us.md +++ b/docs/content/doc/usage/command-line.en-us.md @@ -313,8 +313,13 @@ in the current directory. - `--tempdir path`, `-t path`: Path to the temporary directory used. Optional. (default: /tmp). - `--skip-repository`, `-R`: Skip the repository dumping. Optional. - `--skip-custom-dir`: Skip dumping of the custom dir. Optional. + - `--skip-lfs-data`: Skip dumping of LFS data. Optional. + - `--skip-attachment-data`: Skip dumping of attachment data. Optional. + - `--skip-package-data`: Skip dumping of package data. Optional. + - `--skip-log`: Skip dumping of log data. Optional. - `--database`, `-d`: Specify the database SQL syntax. Optional. - `--verbose`, `-V`: If provided, shows additional details. Optional. + - `--type`: Set the dump output format. Optional. (default: zip) - Examples: - `gitea dump` - `gitea dump --verbose` diff --git a/integrations/api_nodeinfo_test.go b/integrations/api_nodeinfo_test.go index 1d25dc026928..822dbf3f0e95 100644 --- a/integrations/api_nodeinfo_test.go +++ b/integrations/api_nodeinfo_test.go @@ -26,6 +26,10 @@ func TestNodeinfo(t *testing.T) { resp := MakeRequest(t, req, http.StatusOK) var nodeinfo api.NodeInfo DecodeJSON(t, resp, &nodeinfo) + assert.True(t, nodeinfo.OpenRegistrations) assert.Equal(t, "gitea", nodeinfo.Software.Name) + assert.Equal(t, 23, nodeinfo.Usage.Users.Total) + assert.Equal(t, 15, nodeinfo.Usage.LocalPosts) + assert.Equal(t, 2, nodeinfo.Usage.LocalComments) }) } diff --git a/integrations/api_repo_collaborator_test.go b/integrations/api_repo_collaborator_test.go new file mode 100644 index 000000000000..fdca1d915024 --- /dev/null +++ b/integrations/api_repo_collaborator_test.go @@ -0,0 +1,131 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integrations + +import ( + "net/http" + "net/url" + "testing" + + "code.gitea.io/gitea/models/perm" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + + "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}).(*repo_model.Repository) + repo2Owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo2.OwnerID}).(*user_model.User) + + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User) + user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User) + user10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10}).(*user_model.User) + user11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 11}).(*user_model.User) + + session := loginUser(t, repo2Owner.Name) + testCtx := NewAPITestContext(t, repo2Owner.Name, repo2.Name) + + t.Run("RepoOwnerShouldBeOwner", func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, repo2Owner.Name, testCtx.Token) + resp := session.MakeRequest(t, req, http.StatusOK) + + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) + + assert.Equal(t, "owner", repoPermission.Permission) + }) + + 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?token=%s", repo2Owner.Name, repo2.Name, user4.Name, 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("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?token=%s", repo2Owner.Name, repo2.Name, user4.Name, testCtx.Token) + resp := session.MakeRequest(t, req, http.StatusOK) + + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) + + assert.Equal(t, "write", repoPermission.Permission) + }) + + 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?token=%s", repo2Owner.Name, repo2.Name, user4.Name, testCtx.Token) + resp := session.MakeRequest(t, req, http.StatusOK) + + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) + + 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?token=%s", repo2Owner.Name, repo2.Name, "non-existent-user", testCtx.Token) + session.MakeRequest(t, req, http.StatusNotFound) + }) + + 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) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, user5.Name, _testCtx.Token) + resp := _session.MakeRequest(t, req, http.StatusOK) + + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) + + assert.Equal(t, "read", repoPermission.Permission) + }) + + t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) + + _session := loginUser(t, user5.Name) + _testCtx := NewAPITestContext(t, user5.Name, repo2.Name) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, user5.Name, _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) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, user11.Name, _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/integrations/api_team_test.go b/integrations/api_team_test.go index daf1efa2be62..412fd4c73d7e 100644 --- a/integrations/api_team_test.go +++ b/integrations/api_team_test.go @@ -11,6 +11,7 @@ import ( "testing" "code.gitea.io/gitea/models/organization" + "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -239,3 +240,26 @@ func TestAPITeamSearch(t *testing.T) { req = NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s&token=%s", org.Name, "team", token5) MakeRequest(t, req, http.StatusForbidden) } + +func TestAPIGetTeamRepo(t *testing.T) { + defer prepareTestEnv(t)() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}).(*user_model.User) + teamRepo := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 24}).(*repo.Repository) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}).(*organization.Team) + + var results api.Repository + + token := getUserToken(t, user.Name) + req := NewRequestf(t, "GET", "/api/v1/teams/%d/repos/%s/?token=%s", team.ID, teamRepo.FullName(), token) + resp := MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &results) + assert.Equal(t, "big_test_private_4", teamRepo.Name) + + // no access if not organization member + user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User) + token5 := getUserToken(t, user5.Name) + + req = NewRequestf(t, "GET", "/api/v1/teams/%d/repos/%s/?token=%s", team.ID, teamRepo.FullName(), token5) + MakeRequest(t, req, http.StatusNotFound) +} diff --git a/integrations/integration_test.go b/integrations/integration_test.go index 8d2bfe9383f9..228b4123724a 100644 --- a/integrations/integration_test.go +++ b/integrations/integration_test.go @@ -112,6 +112,13 @@ func TestMain(m *testing.M) { } } + os.Unsetenv("GIT_AUTHOR_NAME") + os.Unsetenv("GIT_AUTHOR_EMAIL") + os.Unsetenv("GIT_AUTHOR_DATE") + os.Unsetenv("GIT_COMMITTER_NAME") + os.Unsetenv("GIT_COMMITTER_EMAIL") + os.Unsetenv("GIT_COMMITTER_DATE") + err := unittest.InitFixtures( unittest.FixturesOptions{ Dir: filepath.Join(filepath.Dir(setting.AppPath), "models/fixtures/"), diff --git a/integrations/pull_merge_test.go b/integrations/pull_merge_test.go index f8e8c79a8864..476f7ab52f9f 100644 --- a/integrations/pull_merge_test.go +++ b/integrations/pull_merge_test.go @@ -243,11 +243,11 @@ func TestCantMergeConflict(t *testing.T) { gitRepo, err := git.OpenRepository(git.DefaultContext, repo_model.RepoPath(user1.Name, repo1.Name)) assert.NoError(t, err) - err = pull.Merge(git.DefaultContext, pr, user1, gitRepo, repo_model.MergeStyleMerge, "", "CONFLICT") + err = pull.Merge(pr, user1, gitRepo, repo_model.MergeStyleMerge, "", "CONFLICT") assert.Error(t, err, "Merge should return an error due to conflict") assert.True(t, models.IsErrMergeConflicts(err), "Merge error is not a conflict error") - err = pull.Merge(git.DefaultContext, pr, user1, gitRepo, repo_model.MergeStyleRebase, "", "CONFLICT") + err = pull.Merge(pr, user1, gitRepo, repo_model.MergeStyleRebase, "", "CONFLICT") assert.Error(t, err, "Merge should return an error due to conflict") assert.True(t, models.IsErrRebaseConflicts(err), "Merge error is not a conflict error") gitRepo.Close() @@ -342,7 +342,7 @@ func TestCantMergeUnrelated(t *testing.T) { BaseBranch: "base", }).(*models.PullRequest) - err = pull.Merge(git.DefaultContext, pr, user1, gitRepo, repo_model.MergeStyleMerge, "", "UNRELATED") + err = pull.Merge(pr, user1, gitRepo, repo_model.MergeStyleMerge, "", "UNRELATED") assert.Error(t, err, "Merge should return an error due to unrelated") assert.True(t, models.IsErrMergeUnrelatedHistories(err), "Merge error is not a unrelated histories error") gitRepo.Close() diff --git a/models/action.go b/models/action.go index 24a609963347..6f2bff539c08 100644 --- a/models/action.go +++ b/models/action.go @@ -508,7 +508,7 @@ func notifyWatchers(ctx context.Context, actions ...*Action) error { permPR[i] = false continue } - perm, err := getUserRepoPermission(ctx, repo, user) + perm, err := GetUserRepoPermission(ctx, repo, user) if err != nil { permCode[i] = false permIssue[i] = false diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go index 2341e0862025..4d44a8842ad0 100644 --- a/models/auth/oauth2.go +++ b/models/auth/oauth2.go @@ -245,7 +245,7 @@ func deleteOAuth2Application(sess db.Engine, id, userid int64) error { "oauth2_authorization_code.grant_id = oauth2_grant.id AND oauth2_grant.application_id = ?", id).Find(&codes); err != nil { return err } - codeIDs := make([]int64, 0) + codeIDs := make([]int64, 0, len(codes)) for _, grant := range codes { codeIDs = append(codeIDs, grant.ID) } diff --git a/models/branches.go b/models/branches.go index 8156fd090827..47fd3dc0a432 100644 --- a/models/branches.go +++ b/models/branches.go @@ -104,7 +104,7 @@ func (protectBranch *ProtectedBranch) CanUserPush(userID int64) bool { } // IsUserMergeWhitelisted checks if some user is whitelisted to merge to this branch -func IsUserMergeWhitelisted(protectBranch *ProtectedBranch, userID int64, permissionInRepo Permission) bool { +func IsUserMergeWhitelisted(ctx context.Context, protectBranch *ProtectedBranch, userID int64, permissionInRepo Permission) bool { if !protectBranch.EnableMergeWhitelist { // Then we need to fall back on whether the user has write permission return permissionInRepo.CanWrite(unit.TypeCode) @@ -118,7 +118,7 @@ func IsUserMergeWhitelisted(protectBranch *ProtectedBranch, userID int64, permis return false } - in, err := organization.IsUserInTeams(db.DefaultContext, userID, protectBranch.MergeWhitelistTeamIDs) + in, err := organization.IsUserInTeams(ctx, userID, protectBranch.MergeWhitelistTeamIDs) if err != nil { log.Error("IsUserInTeams: %v", err) return false @@ -159,16 +159,16 @@ func isUserOfficialReviewer(ctx context.Context, protectBranch *ProtectedBranch, } // HasEnoughApprovals returns true if pr has enough granted approvals. -func (protectBranch *ProtectedBranch) HasEnoughApprovals(pr *PullRequest) bool { +func (protectBranch *ProtectedBranch) HasEnoughApprovals(ctx context.Context, pr *PullRequest) bool { if protectBranch.RequiredApprovals == 0 { return true } - return protectBranch.GetGrantedApprovalsCount(pr) >= protectBranch.RequiredApprovals + return protectBranch.GetGrantedApprovalsCount(ctx, pr) >= protectBranch.RequiredApprovals } // GetGrantedApprovalsCount returns the number of granted approvals for pr. A granted approval must be authored by a user in an approval whitelist. -func (protectBranch *ProtectedBranch) GetGrantedApprovalsCount(pr *PullRequest) int64 { - sess := db.GetEngine(db.DefaultContext).Where("issue_id = ?", pr.IssueID). +func (protectBranch *ProtectedBranch) GetGrantedApprovalsCount(ctx context.Context, pr *PullRequest) int64 { + sess := db.GetEngine(ctx).Where("issue_id = ?", pr.IssueID). And("type = ?", ReviewTypeApprove). And("official = ?", true). And("dismissed = ?", false) @@ -185,11 +185,11 @@ func (protectBranch *ProtectedBranch) GetGrantedApprovalsCount(pr *PullRequest) } // MergeBlockedByRejectedReview returns true if merge is blocked by rejected reviews -func (protectBranch *ProtectedBranch) MergeBlockedByRejectedReview(pr *PullRequest) bool { +func (protectBranch *ProtectedBranch) MergeBlockedByRejectedReview(ctx context.Context, pr *PullRequest) bool { if !protectBranch.BlockOnRejectedReviews { return false } - rejectExist, err := db.GetEngine(db.DefaultContext).Where("issue_id = ?", pr.IssueID). + rejectExist, err := db.GetEngine(ctx).Where("issue_id = ?", pr.IssueID). And("type = ?", ReviewTypeReject). And("official = ?", true). And("dismissed = ?", false). @@ -204,11 +204,11 @@ func (protectBranch *ProtectedBranch) MergeBlockedByRejectedReview(pr *PullReque // MergeBlockedByOfficialReviewRequests block merge because of some review request to official reviewer // of from official review -func (protectBranch *ProtectedBranch) MergeBlockedByOfficialReviewRequests(pr *PullRequest) bool { +func (protectBranch *ProtectedBranch) MergeBlockedByOfficialReviewRequests(ctx context.Context, pr *PullRequest) bool { if !protectBranch.BlockOnOfficialReviewRequests { return false } - has, err := db.GetEngine(db.DefaultContext).Where("issue_id = ?", pr.IssueID). + has, err := db.GetEngine(ctx).Where("issue_id = ?", pr.IssueID). And("type = ?", ReviewTypeRequest). And("official = ?", true). Exist(new(Review)) @@ -337,43 +337,43 @@ type WhitelistOptions struct { // If ID is 0, it creates a new record. Otherwise, updates existing record. // This function also performs check if whitelist user and team's IDs have been changed // to avoid unnecessary whitelist delete and regenerate. -func UpdateProtectBranch(repo *repo_model.Repository, protectBranch *ProtectedBranch, opts WhitelistOptions) (err error) { - if err = repo.GetOwner(db.DefaultContext); err != nil { +func UpdateProtectBranch(ctx context.Context, repo *repo_model.Repository, protectBranch *ProtectedBranch, opts WhitelistOptions) (err error) { + if err = repo.GetOwner(ctx); err != nil { return fmt.Errorf("GetOwner: %v", err) } - whitelist, err := updateUserWhitelist(repo, protectBranch.WhitelistUserIDs, opts.UserIDs) + whitelist, err := updateUserWhitelist(ctx, repo, protectBranch.WhitelistUserIDs, opts.UserIDs) if err != nil { return err } protectBranch.WhitelistUserIDs = whitelist - whitelist, err = updateUserWhitelist(repo, protectBranch.MergeWhitelistUserIDs, opts.MergeUserIDs) + whitelist, err = updateUserWhitelist(ctx, repo, protectBranch.MergeWhitelistUserIDs, opts.MergeUserIDs) if err != nil { return err } protectBranch.MergeWhitelistUserIDs = whitelist - whitelist, err = updateApprovalWhitelist(repo, protectBranch.ApprovalsWhitelistUserIDs, opts.ApprovalsUserIDs) + whitelist, err = updateApprovalWhitelist(ctx, repo, protectBranch.ApprovalsWhitelistUserIDs, opts.ApprovalsUserIDs) if err != nil { return err } protectBranch.ApprovalsWhitelistUserIDs = whitelist // if the repo is in an organization - whitelist, err = updateTeamWhitelist(repo, protectBranch.WhitelistTeamIDs, opts.TeamIDs) + whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.WhitelistTeamIDs, opts.TeamIDs) if err != nil { return err } protectBranch.WhitelistTeamIDs = whitelist - whitelist, err = updateTeamWhitelist(repo, protectBranch.MergeWhitelistTeamIDs, opts.MergeTeamIDs) + whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.MergeWhitelistTeamIDs, opts.MergeTeamIDs) if err != nil { return err } protectBranch.MergeWhitelistTeamIDs = whitelist - whitelist, err = updateTeamWhitelist(repo, protectBranch.ApprovalsWhitelistTeamIDs, opts.ApprovalsTeamIDs) + whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.ApprovalsWhitelistTeamIDs, opts.ApprovalsTeamIDs) if err != nil { return err } @@ -381,13 +381,13 @@ func UpdateProtectBranch(repo *repo_model.Repository, protectBranch *ProtectedBr // Make sure protectBranch.ID is not 0 for whitelists if protectBranch.ID == 0 { - if _, err = db.GetEngine(db.DefaultContext).Insert(protectBranch); err != nil { + if _, err = db.GetEngine(ctx).Insert(protectBranch); err != nil { return fmt.Errorf("Insert: %v", err) } return nil } - if _, err = db.GetEngine(db.DefaultContext).ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil { + if _, err = db.GetEngine(ctx).ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil { return fmt.Errorf("Update: %v", err) } @@ -416,7 +416,7 @@ func IsProtectedBranch(repoID int64, branchName string) (bool, error) { // updateApprovalWhitelist checks whether the user whitelist changed and returns a whitelist with // the users from newWhitelist which have explicit read or write access to the repo. -func updateApprovalWhitelist(repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) { +func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) { hasUsersChanged := !util.IsSliceInt64Eq(currentWhitelist, newWhitelist) if !hasUsersChanged { return currentWhitelist, nil @@ -424,7 +424,7 @@ func updateApprovalWhitelist(repo *repo_model.Repository, currentWhitelist, newW whitelist = make([]int64, 0, len(newWhitelist)) for _, userID := range newWhitelist { - if reader, err := IsRepoReader(repo, userID); err != nil { + if reader, err := IsRepoReader(ctx, repo, userID); err != nil { return nil, err } else if !reader { continue @@ -437,7 +437,7 @@ func updateApprovalWhitelist(repo *repo_model.Repository, currentWhitelist, newW // updateUserWhitelist checks whether the user whitelist changed and returns a whitelist with // the users from newWhitelist which have write access to the repo. -func updateUserWhitelist(repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) { +func updateUserWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) { hasUsersChanged := !util.IsSliceInt64Eq(currentWhitelist, newWhitelist) if !hasUsersChanged { return currentWhitelist, nil @@ -445,11 +445,11 @@ func updateUserWhitelist(repo *repo_model.Repository, currentWhitelist, newWhite whitelist = make([]int64, 0, len(newWhitelist)) for _, userID := range newWhitelist { - user, err := user_model.GetUserByID(userID) + user, err := user_model.GetUserByIDCtx(ctx, userID) if err != nil { return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err) } - perm, err := GetUserRepoPermission(repo, user) + perm, err := GetUserRepoPermission(ctx, repo, user) if err != nil { return nil, fmt.Errorf("GetUserRepoPermission [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err) } @@ -466,13 +466,13 @@ func updateUserWhitelist(repo *repo_model.Repository, currentWhitelist, newWhite // updateTeamWhitelist checks whether the team whitelist changed and returns a whitelist with // the teams from newWhitelist which have write access to the repo. -func updateTeamWhitelist(repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) { +func updateTeamWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) { hasTeamsChanged := !util.IsSliceInt64Eq(currentWhitelist, newWhitelist) if !hasTeamsChanged { return currentWhitelist, nil } - teams, err := organization.GetTeamsWithAccessToRepo(repo.OwnerID, repo.ID, perm.AccessModeRead) + teams, err := organization.GetTeamsWithAccessToRepo(ctx, repo.OwnerID, repo.ID, perm.AccessModeRead) if err != nil { return nil, fmt.Errorf("GetTeamsWithAccessToRepo [org_id: %d, repo_id: %d]: %v", repo.OwnerID, repo.ID, err) } diff --git a/models/branches_test.go b/models/branches_test.go index e1a71853f20b..0a0f125cc6e8 100644 --- a/models/branches_test.go +++ b/models/branches_test.go @@ -7,6 +7,7 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" @@ -99,11 +100,14 @@ func TestRenameBranch(t *testing.T) { repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) _isDefault := false - err := UpdateProtectBranch(repo1, &ProtectedBranch{ + ctx, committer, err := db.TxContext() + defer committer.Close() + assert.NoError(t, err) + assert.NoError(t, UpdateProtectBranch(ctx, repo1, &ProtectedBranch{ RepoID: repo1.ID, BranchName: "master", - }, WhitelistOptions{}) - assert.NoError(t, err) + }, WhitelistOptions{})) + assert.NoError(t, committer.Commit()) assert.NoError(t, RenameBranch(repo1, "master", "main", func(isDefault bool) error { _isDefault = isDefault diff --git a/models/commit_status.go b/models/commit_status.go index cd7497eed8e0..cf2143d30f4c 100644 --- a/models/commit_status.go +++ b/models/commit_status.go @@ -232,12 +232,13 @@ type CommitStatusIndex struct { // GetLatestCommitStatus returns all statuses with a unique context for a given commit. func GetLatestCommitStatus(repoID int64, sha string, listOptions db.ListOptions) ([]*CommitStatus, int64, error) { - return getLatestCommitStatus(db.GetEngine(db.DefaultContext), repoID, sha, listOptions) + return GetLatestCommitStatusCtx(db.DefaultContext, repoID, sha, listOptions) } -func getLatestCommitStatus(e db.Engine, repoID int64, sha string, listOptions db.ListOptions) ([]*CommitStatus, int64, error) { +// GetLatestCommitStatusCtx returns all statuses with a unique context for a given commit. +func GetLatestCommitStatusCtx(ctx context.Context, repoID int64, sha string, listOptions db.ListOptions) ([]*CommitStatus, int64, error) { ids := make([]int64, 0, 10) - sess := e.Table(&CommitStatus{}). + sess := db.GetEngine(ctx).Table(&CommitStatus{}). Where("repo_id = ?", repoID).And("sha = ?", sha). Select("max( id ) as id"). GroupBy("context_hash").OrderBy("max( id ) desc") @@ -252,7 +253,7 @@ func getLatestCommitStatus(e db.Engine, repoID int64, sha string, listOptions db if len(ids) == 0 { return statuses, count, nil } - return statuses, count, e.In("id", ids).Find(&statuses) + return statuses, count, db.GetEngine(ctx).In("id", ids).Find(&statuses) } // FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts diff --git a/models/db/context.go b/models/db/context.go index 1cd23d453ce4..c823952cf68d 100644 --- a/models/db/context.go +++ b/models/db/context.go @@ -103,7 +103,14 @@ func WithContext(f func(ctx *Context) error) error { } // WithTx represents executing database operations on a transaction -func WithTx(f func(ctx context.Context) error) error { +// you can optionally change the context to a parrent one +func WithTx(f func(ctx context.Context) error, stdCtx ...context.Context) error { + parentCtx := DefaultContext + if len(stdCtx) != 0 && stdCtx[0] != nil { + // TODO: make sure parent context has no open session + parentCtx = stdCtx[0] + } + sess := x.NewSession() defer sess.Close() if err := sess.Begin(); err != nil { @@ -111,7 +118,7 @@ func WithTx(f func(ctx context.Context) error) error { } if err := f(&Context{ - Context: DefaultContext, + Context: parentCtx, e: sess, }); err != nil { return err diff --git a/models/error.go b/models/error.go index 6846bf8320f4..c29c818589fb 100644 --- a/models/error.go +++ b/models/error.go @@ -296,7 +296,6 @@ type ErrInvalidCloneAddr struct { IsProtocolInvalid bool IsPermissionDenied bool LocalPath bool - NotResolvedIP bool } // IsErrInvalidCloneAddr checks if an error is a ErrInvalidCloneAddr. @@ -306,9 +305,6 @@ func IsErrInvalidCloneAddr(err error) bool { } func (err *ErrInvalidCloneAddr) Error() string { - if err.NotResolvedIP { - return fmt.Sprintf("migration/cloning from '%s' is not allowed: unknown hostname", err.Host) - } if err.IsInvalidPath { return fmt.Sprintf("migration/cloning from '%s' is not allowed: the provided path is invalid", err.Host) } diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index 670b30562130..67ba869c76b0 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -4,6 +4,7 @@ id: 1 lower_name: user1 name: user1 + login_name: user1 full_name: User One email: user1@example.com email_notifications_preference: enabled @@ -21,6 +22,7 @@ id: 2 lower_name: user2 name: user2 + login_name: user2 full_name: " < Ur Tw >< " email: user2@example.com keep_email_private: true @@ -42,6 +44,7 @@ id: 3 lower_name: user3 name: user3 + login_name: user3 full_name: " <<<< >> >> > >> > >>> >> " email: user3@example.com email_notifications_preference: onmention @@ -60,6 +63,7 @@ id: 4 lower_name: user4 name: user4 + login_name: user4 full_name: " " email: user4@example.com email_notifications_preference: onmention @@ -78,6 +82,7 @@ id: 5 lower_name: user5 name: user5 + login_name: user5 full_name: User Five email: user5@example.com email_notifications_preference: enabled @@ -97,6 +102,7 @@ id: 6 lower_name: user6 name: user6 + login_name: user6 full_name: User Six email: user6@example.com email_notifications_preference: enabled @@ -115,6 +121,7 @@ id: 7 lower_name: user7 name: user7 + login_name: user7 full_name: User Seven email: user7@example.com email_notifications_preference: disabled @@ -133,6 +140,7 @@ id: 8 lower_name: user8 name: user8 + login_name: user8 full_name: User Eight email: user8@example.com email_notifications_preference: enabled @@ -152,6 +160,7 @@ id: 9 lower_name: user9 name: user9 + login_name: user9 full_name: User Nine email: user9@example.com email_notifications_preference: onmention @@ -169,6 +178,7 @@ id: 10 lower_name: user10 name: user10 + login_name: user10 full_name: User Ten email: user10@example.com passwd_hash_algo: argon2 @@ -185,6 +195,7 @@ id: 11 lower_name: user11 name: user11 + login_name: user11 full_name: User Eleven email: user11@example.com passwd_hash_algo: argon2 @@ -201,6 +212,7 @@ id: 12 lower_name: user12 name: user12 + login_name: user12 full_name: User 12 email: user12@example.com passwd_hash_algo: argon2 @@ -217,6 +229,7 @@ id: 13 lower_name: user13 name: user13 + login_name: user13 full_name: User 13 email: user13@example.com passwd_hash_algo: argon2 @@ -233,6 +246,7 @@ id: 14 lower_name: user14 name: user14 + login_name: user14 full_name: User 14 email: user14@example.com passwd_hash_algo: argon2 @@ -249,6 +263,7 @@ id: 15 lower_name: user15 name: user15 + login_name: user15 full_name: User 15 email: user15@example.com passwd_hash_algo: argon2 @@ -265,6 +280,7 @@ id: 16 lower_name: user16 name: user16 + login_name: user16 full_name: User 16 email: user16@example.com passwd_hash_algo: argon2 @@ -281,6 +297,7 @@ id: 17 lower_name: user17 name: user17 + login_name: user17 full_name: User 17 email: user17@example.com passwd_hash_algo: argon2 @@ -299,6 +316,7 @@ id: 18 lower_name: user18 name: user18 + login_name: user18 full_name: User 18 email: user18@example.com passwd_hash_algo: argon2 @@ -315,6 +333,7 @@ id: 19 lower_name: user19 name: user19 + login_name: user19 full_name: User 19 email: user19@example.com passwd_hash_algo: argon2 @@ -333,6 +352,7 @@ id: 20 lower_name: user20 name: user20 + login_name: user20 full_name: User 20 email: user20@example.com passwd_hash_algo: argon2 @@ -349,6 +369,7 @@ id: 21 lower_name: user21 name: user21 + login_name: user21 full_name: User 21 email: user21@example.com passwd_hash_algo: argon2 @@ -365,6 +386,7 @@ id: 22 lower_name: limited_org name: limited_org + login_name: limited_org full_name: Limited Org email: limited_org@example.com passwd_hash_algo: argon2 @@ -384,6 +406,7 @@ id: 23 lower_name: privated_org name: privated_org + login_name: privated_org full_name: Privated Org email: privated_org@example.com passwd_hash_algo: argon2 @@ -403,6 +426,7 @@ id: 24 lower_name: user24 name: user24 + login_name: user24 full_name: "user24" email: user24@example.com keep_email_private: true @@ -423,6 +447,7 @@ id: 25 lower_name: org25 name: org25 + login_name: org25 full_name: "org25" email: org25@example.com passwd_hash_algo: argon2 @@ -440,6 +465,7 @@ id: 26 lower_name: org26 name: org26 + login_name: org26 full_name: "Org26" email: org26@example.com email_notifications_preference: onmention @@ -459,6 +485,7 @@ id: 27 lower_name: user27 name: user27 + login_name: user27 full_name: User Twenty-Seven email: user27@example.com email_notifications_preference: enabled @@ -475,6 +502,7 @@ id: 28 lower_name: user28 name: user28 + login_name: user28 full_name: "user27" email: user28@example.com keep_email_private: true @@ -495,6 +523,7 @@ id: 29 lower_name: user29 name: user29 + login_name: user29 full_name: User 29 email: user29@example.com passwd_hash_algo: argon2 @@ -512,6 +541,7 @@ id: 30 lower_name: user30 name: user30 + login_name: user30 full_name: User Thirty email: user30@example.com passwd_hash_algo: argon2 @@ -530,6 +560,7 @@ id: 31 lower_name: user31 name: user31 + login_name: user31 full_name: "user31" email: user31@example.com passwd_hash_algo: argon2 @@ -547,6 +578,7 @@ id: 32 lower_name: user32 name: user32 + login_name: user32 full_name: User 32 (U2F test) email: user32@example.com passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password diff --git a/models/issue.go b/models/issue.go index b1fa2d02ad1b..98e64adafd22 100644 --- a/models/issue.go +++ b/models/issue.go @@ -162,13 +162,9 @@ func (issue *Issue) GetPullRequest() (pr *PullRequest, err error) { } // LoadLabels loads labels -func (issue *Issue) LoadLabels() error { - return issue.loadLabels(db.GetEngine(db.DefaultContext)) -} - -func (issue *Issue) loadLabels(e db.Engine) (err error) { +func (issue *Issue) LoadLabels(ctx context.Context) (err error) { if issue.Labels == nil { - issue.Labels, err = getLabelsByIssueID(e, issue.ID) + issue.Labels, err = getLabelsByIssueID(db.GetEngine(ctx), issue.ID) if err != nil { return fmt.Errorf("getLabelsByIssueID [%d]: %v", issue.ID, err) } @@ -313,7 +309,7 @@ func (issue *Issue) loadAttributes(ctx context.Context) (err error) { return } - if err = issue.loadLabels(e); err != nil { + if err = issue.LoadLabels(ctx); err != nil { return } @@ -493,7 +489,7 @@ func ClearIssueLabels(issue *Issue, doer *user_model.User) (err error) { return err } - perm, err := getUserRepoPermission(ctx, issue.Repo, doer) + perm, err := GetUserRepoPermission(ctx, issue.Repo, doer) if err != nil { return err } @@ -539,7 +535,7 @@ func ReplaceIssueLabels(issue *Issue, labels []*Label, doer *user_model.User) (e return err } - if err = issue.loadLabels(db.GetEngine(ctx)); err != nil { + if err = issue.LoadLabels(ctx); err != nil { return err } @@ -587,7 +583,7 @@ func ReplaceIssueLabels(issue *Issue, labels []*Label, doer *user_model.User) (e } issue.Labels = nil - if err = issue.loadLabels(db.GetEngine(ctx)); err != nil { + if err = issue.LoadLabels(ctx); err != nil { return err } @@ -595,7 +591,7 @@ func ReplaceIssueLabels(issue *Issue, labels []*Label, doer *user_model.User) (e } // ReadBy sets issue to be read by given user. -func (issue *Issue) ReadBy(userID int64) error { +func (issue *Issue) ReadBy(ctx context.Context, userID int64) error { if err := UpdateIssueUserByRead(userID, issue.ID); err != nil { return err } @@ -639,7 +635,7 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use // Check for open dependencies if issue.IsClosed && issue.Repo.IsDependenciesEnabledCtx(ctx) { // only check if dependencies are enabled and we're about to close an issue, otherwise reopening an issue would fail when there are unsatisfied dependencies - noDeps, err := issueNoDependenciesLeft(e, issue) + noDeps, err := IssueNoDependenciesLeft(ctx, issue) if err != nil { return nil, err } @@ -697,13 +693,7 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use } // ChangeIssueStatus changes issue status to open or closed. -func ChangeIssueStatus(issue *Issue, doer *user_model.User, isClosed bool) (*Comment, error) { - ctx, committer, err := db.TxContext() - if err != nil { - return nil, err - } - defer committer.Close() - +func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isClosed bool) (*Comment, error) { if err := issue.LoadRepo(ctx); err != nil { return nil, err } @@ -711,16 +701,7 @@ func ChangeIssueStatus(issue *Issue, doer *user_model.User, isClosed bool) (*Com return nil, err } - comment, err := changeIssueStatus(ctx, issue, doer, isClosed, false) - if err != nil { - return nil, err - } - - if err = committer.Commit(); err != nil { - return nil, fmt.Errorf("Commit: %v", err) - } - - return comment, nil + return changeIssueStatus(ctx, issue, doer, isClosed, false) } // ChangeIssueTitle changes the title of this issue, as the given user. @@ -791,16 +772,11 @@ func ChangeIssueRef(issue *Issue, doer *user_model.User, oldRef string) (err err } // AddDeletePRBranchComment adds delete branch comment for pull request issue -func AddDeletePRBranchComment(doer *user_model.User, repo *repo_model.Repository, issueID int64, branchName string) error { - issue, err := getIssueByID(db.GetEngine(db.DefaultContext), issueID) - if err != nil { - return err - } - ctx, committer, err := db.TxContext() +func AddDeletePRBranchComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, issueID int64, branchName string) error { + issue, err := getIssueByID(db.GetEngine(ctx), issueID) if err != nil { return err } - defer committer.Close() opts := &CreateCommentOptions{ Type: CommentTypeDeleteBranch, Doer: doer, @@ -808,11 +784,8 @@ func AddDeletePRBranchComment(doer *user_model.User, repo *repo_model.Repository Issue: issue, OldRef: branchName, } - if _, err = CreateCommentCtx(ctx, opts); err != nil { - return err - } - - return committer.Commit() + _, err = CreateCommentCtx(ctx, opts) + return err } // UpdateIssueAttachments update attachments by UUIDs for the issue @@ -1194,7 +1167,8 @@ func GetIssuesByIDs(issueIDs []int64) ([]*Issue, error) { // IssuesOptions represents options of an issue. type IssuesOptions struct { db.ListOptions - RepoIDs []int64 // include all repos if empty + RepoID int64 // overwrites RepoCond if not 0 + RepoCond builder.Cond AssigneeID int64 PosterID int64 MentionedID int64 @@ -1285,15 +1259,15 @@ func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) { sess.In("issue.id", opts.IssueIDs) } - if len(opts.RepoIDs) > 0 { - applyReposCondition(sess, opts.RepoIDs) + if opts.RepoID != 0 { + opts.RepoCond = builder.Eq{"issue.repo_id": opts.RepoID} + } + if opts.RepoCond != nil { + sess.And(opts.RepoCond) } - switch opts.IsClosed { - case util.OptionalBoolTrue: - sess.And("issue.is_closed=?", true) - case util.OptionalBoolFalse: - sess.And("issue.is_closed=?", false) + if !opts.IsClosed.IsNone() { + sess.And("issue.is_closed=?", opts.IsClosed.IsTrue()) } if opts.AssigneeID > 0 { @@ -1412,10 +1386,6 @@ func issuePullAccessibleRepoCond(repoIDstr string, userID int64, org *organizati return cond } -func applyReposCondition(sess *xorm.Session, repoIDs []int64) *xorm.Session { - return sess.In("issue.repo_id", repoIDs) -} - func applyAssigneeCondition(sess *xorm.Session, assigneeID int64) *xorm.Session { return sess.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id"). And("issue_assignees.assignee_id = ?", assigneeID) @@ -1510,20 +1480,10 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) { func CountIssues(opts *IssuesOptions) (int64, error) { e := db.GetEngine(db.DefaultContext) - countsSlice := make([]*struct { - Count int64 - }, 0, 1) - sess := e.Select("COUNT(issue.id) AS count").Table("issue") sess.Join("INNER", "repository", "`issue`.repo_id = `repository`.id") opts.setupSessionNoLimit(sess) - if err := sess.Find(&countsSlice); err != nil { - return 0, fmt.Errorf("unable to CountIssues: %w", err) - } - if len(countsSlice) != 1 { - return 0, fmt.Errorf("unable to get one row result when CountIssues, row count=%d", len(countsSlice)) - } - return countsSlice[0].Count, nil + return sess.Count() } // GetParticipantsIDsByIssueID returns the IDs of all users who participated in comments of an issue, @@ -2354,9 +2314,9 @@ func ResolveIssueMentionsByVisibility(ctx context.Context, issue *Issue, doer *u continue } // Normal users must have read access to the referencing issue - perm, err := getUserRepoPermission(ctx, issue.Repo, user) + perm, err := GetUserRepoPermission(ctx, issue.Repo, user) if err != nil { - return nil, fmt.Errorf("getUserRepoPermission [%d]: %v", user.ID, err) + return nil, fmt.Errorf("GetUserRepoPermission [%d]: %v", user.ID, err) } if !perm.CanReadIssuesOrPulls(issue.IsPull) { continue diff --git a/models/issue_comment.go b/models/issue_comment.go index 39c2818eed04..ceea87866281 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -284,14 +284,15 @@ type PushActionContent struct { // LoadIssue loads issue from database func (c *Comment) LoadIssue() (err error) { - return c.loadIssue(db.GetEngine(db.DefaultContext)) + return c.LoadIssueCtx(db.DefaultContext) } -func (c *Comment) loadIssue(e db.Engine) (err error) { +// LoadIssueCtx loads issue from database +func (c *Comment) LoadIssueCtx(ctx context.Context) (err error) { if c.Issue != nil { return nil } - c.Issue, err = getIssueByID(e, c.IssueID) + c.Issue, err = getIssueByID(db.GetEngine(ctx), c.IssueID) return } @@ -1126,7 +1127,7 @@ func UpdateComment(c *Comment, doer *user_model.User) error { if _, err := sess.ID(c.ID).AllCols().Update(c); err != nil { return err } - if err := c.loadIssue(sess); err != nil { + if err := c.LoadIssueCtx(ctx); err != nil { return err } if err := c.addCrossReferences(ctx, doer, true); err != nil { diff --git a/models/issue_dependency.go b/models/issue_dependency.go index d2c5785b909d..b292db57e011 100644 --- a/models/issue_dependency.go +++ b/models/issue_dependency.go @@ -5,6 +5,8 @@ package models import ( + "context" + "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/timeutil" @@ -117,12 +119,8 @@ func issueDepExists(e db.Engine, issueID, depID int64) (bool, error) { } // IssueNoDependenciesLeft checks if issue can be closed -func IssueNoDependenciesLeft(issue *Issue) (bool, error) { - return issueNoDependenciesLeft(db.GetEngine(db.DefaultContext), issue) -} - -func issueNoDependenciesLeft(e db.Engine, issue *Issue) (bool, error) { - exists, err := e. +func IssueNoDependenciesLeft(ctx context.Context, issue *Issue) (bool, error) { + exists, err := db.GetEngine(ctx). Table("issue_dependency"). Select("issue.*"). Join("INNER", "issue", "issue.id = issue_dependency.dependency_id"). diff --git a/models/issue_dependency_test.go b/models/issue_dependency_test.go index 1789de5dc049..345a9077cd4f 100644 --- a/models/issue_dependency_test.go +++ b/models/issue_dependency_test.go @@ -7,6 +7,7 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -43,15 +44,15 @@ func TestCreateIssueDependency(t *testing.T) { _ = unittest.AssertExistsAndLoadBean(t, &Comment{Type: CommentTypeAddDependency, PosterID: user1.ID, IssueID: issue1.ID}) // Check if dependencies left is correct - left, err := IssueNoDependenciesLeft(issue1) + left, err := IssueNoDependenciesLeft(db.DefaultContext, issue1) assert.NoError(t, err) assert.False(t, left) // Close #2 and check again - _, err = ChangeIssueStatus(issue2, user1, true) + _, err = ChangeIssueStatus(db.DefaultContext, issue2, user1, true) assert.NoError(t, err) - left, err = IssueNoDependenciesLeft(issue1) + left, err = IssueNoDependenciesLeft(db.DefaultContext, issue1) assert.NoError(t, err) assert.True(t, left) diff --git a/models/issue_label.go b/models/issue_label.go index 016109e80f4a..d06915393908 100644 --- a/models/issue_label.go +++ b/models/issue_label.go @@ -57,12 +57,9 @@ func (label *Label) CalOpenIssues() { // CalOpenOrgIssues calculates the open issues of a label for a specific repo func (label *Label) CalOpenOrgIssues(repoID, labelID int64) { - repoIDs := []int64{repoID} - labelIDs := []int64{labelID} - counts, _ := CountIssuesByRepo(&IssuesOptions{ - RepoIDs: repoIDs, - LabelIDs: labelIDs, + RepoID: repoID, + LabelIDs: []int64{labelID}, }) for _, count := range counts { @@ -616,7 +613,6 @@ func NewIssueLabel(issue *Issue, label *Label, doer *user_model.User) (err error return err } defer committer.Close() - sess := db.GetEngine(ctx) if err = issue.LoadRepo(ctx); err != nil { return err @@ -632,7 +628,7 @@ func NewIssueLabel(issue *Issue, label *Label, doer *user_model.User) (err error } issue.Labels = nil - if err = issue.loadLabels(sess); err != nil { + if err = issue.LoadLabels(ctx); err != nil { return err } @@ -673,7 +669,7 @@ func NewIssueLabels(issue *Issue, labels []*Label, doer *user_model.User) (err e } issue.Labels = nil - if err = issue.loadLabels(db.GetEngine(ctx)); err != nil { + if err = issue.LoadLabels(ctx); err != nil { return err } @@ -710,23 +706,13 @@ func deleteIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *use } // DeleteIssueLabel deletes issue-label relation. -func DeleteIssueLabel(issue *Issue, label *Label, doer *user_model.User) (err error) { - ctx, committer, err := db.TxContext() - if err != nil { - return err - } - defer committer.Close() - - if err = deleteIssueLabel(ctx, issue, label, doer); err != nil { +func DeleteIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *user_model.User) error { + if err := deleteIssueLabel(ctx, issue, label, doer); err != nil { return err } issue.Labels = nil - if err = issue.loadLabels(db.GetEngine(ctx)); err != nil { - return err - } - - return committer.Commit() + return issue.LoadLabels(ctx) } func deleteLabelsByRepoID(sess db.Engine, repoID int64) error { diff --git a/models/issue_label_test.go b/models/issue_label_test.go index c9ff1a270f6b..2dd0cf98e085 100644 --- a/models/issue_label_test.go +++ b/models/issue_label_test.go @@ -369,7 +369,12 @@ func TestDeleteIssueLabel(t *testing.T) { } } - assert.NoError(t, DeleteIssueLabel(issue, label, doer)) + ctx, committer, err := db.TxContext() + defer committer.Close() + assert.NoError(t, err) + assert.NoError(t, DeleteIssueLabel(ctx, issue, label, doer)) + assert.NoError(t, committer.Commit()) + unittest.AssertNotExistsBean(t, &IssueLabel{IssueID: issueID, LabelID: labelID}) unittest.AssertExistsAndLoadBean(t, &Comment{ Type: CommentTypeLabel, diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go index 80dd44642eb4..81459ba4460f 100644 --- a/models/issue_stopwatch.go +++ b/models/issue_stopwatch.go @@ -66,6 +66,38 @@ func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, ex return } +// UserIDCount is a simple coalition of UserID and Count +type UserStopwatch struct { + UserID int64 + StopWatches []*Stopwatch +} + +// GetUIDsAndNotificationCounts between the two provided times +func GetUIDsAndStopwatch() ([]*UserStopwatch, error) { + sws := []*Stopwatch{} + if err := db.GetEngine(db.DefaultContext).Find(&sws); err != nil { + return nil, err + } + if len(sws) == 0 { + return []*UserStopwatch{}, nil + } + + lastUserID := int64(-1) + res := []*UserStopwatch{} + for _, sw := range sws { + if lastUserID == sw.UserID { + lastUserStopwatch := res[len(res)-1] + lastUserStopwatch.StopWatches = append(lastUserStopwatch.StopWatches, sw) + } else { + res = append(res, &UserStopwatch{ + UserID: sw.UserID, + StopWatches: []*Stopwatch{sw}, + }) + } + } + return res, nil +} + // GetUserStopwatches return list of all stopwatches of a user func GetUserStopwatches(userID int64, listOptions db.ListOptions) ([]*Stopwatch, error) { sws := make([]*Stopwatch, 0, 8) diff --git a/models/issue_test.go b/models/issue_test.go index 7893df8a7fb6..9b82fc80fb66 100644 --- a/models/issue_test.go +++ b/models/issue_test.go @@ -21,6 +21,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "github.com/stretchr/testify/assert" + "xorm.io/builder" ) func TestIssue_ReplaceLabels(t *testing.T) { @@ -157,7 +158,7 @@ func TestIssues(t *testing.T) { }, { IssuesOptions{ - RepoIDs: []int64{1, 3}, + RepoCond: builder.In("repo_id", 1, 3), SortType: "oldest", ListOptions: db.ListOptions{ Page: 1, @@ -344,7 +345,7 @@ func TestGetRepoIDsForIssuesOptions(t *testing.T) { }, { IssuesOptions{ - RepoIDs: []int64{1, 2}, + RepoCond: builder.In("repo_id", 1, 2), }, []int64{1, 2}, }, @@ -442,12 +443,12 @@ func TestIssue_DeleteIssue(t *testing.T) { assert.NoError(t, err) err = CreateIssueDependency(user, issue1, issue2) assert.NoError(t, err) - left, err := IssueNoDependenciesLeft(issue1) + left, err := IssueNoDependenciesLeft(db.DefaultContext, issue1) assert.NoError(t, err) assert.False(t, left) err = DeleteIssue(&Issue{ID: 2}) assert.NoError(t, err) - left, err = IssueNoDependenciesLeft(issue1) + left, err = IssueNoDependenciesLeft(db.DefaultContext, issue1) assert.NoError(t, err) assert.True(t, left) } @@ -589,3 +590,10 @@ func TestLoadTotalTrackedTime(t *testing.T) { assert.Equal(t, int64(3682), milestone.TotalTrackedTime) } + +func TestCountIssues(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + count, err := CountIssues(&IssuesOptions{}) + assert.NoError(t, err) + assert.EqualValues(t, 15, count) +} diff --git a/models/issue_xref.go b/models/issue_xref.go index 405f1dae2270..cd1c122252de 100644 --- a/models/issue_xref.go +++ b/models/issue_xref.go @@ -215,7 +215,7 @@ func (issue *Issue) verifyReferencedIssue(stdCtx context.Context, ctx *crossRefe // Check doer permissions; set action to None if the doer can't change the destination if refIssue.RepoID != ctx.OrigIssue.RepoID || ref.Action != references.XRefActionNone { - perm, err := getUserRepoPermission(stdCtx, refIssue.Repo, ctx.Doer) + perm, err := GetUserRepoPermission(stdCtx, refIssue.Repo, ctx.Doer) if err != nil { return nil, references.XRefActionNone, err } @@ -249,7 +249,7 @@ func (comment *Comment) addCrossReferences(stdCtx context.Context, doer *user_mo if comment.Type != CommentTypeCode && comment.Type != CommentTypeComment { return nil } - if err := comment.loadIssue(db.GetEngine(stdCtx)); err != nil { + if err := comment.LoadIssueCtx(stdCtx); err != nil { return err } ctx := &crossReferencesContext{ @@ -340,9 +340,9 @@ func (comment *Comment) RefIssueIdent() string { // \/ \/ |__| \/ \/ // ResolveCrossReferences will return the list of references to close/reopen by this PR -func (pr *PullRequest) ResolveCrossReferences() ([]*Comment, error) { +func (pr *PullRequest) ResolveCrossReferences(ctx context.Context) ([]*Comment, error) { unfiltered := make([]*Comment, 0, 5) - if err := db.GetEngine(db.DefaultContext). + if err := db.GetEngine(ctx). Where("ref_repo_id = ? AND ref_issue_id = ?", pr.Issue.RepoID, pr.Issue.ID). In("ref_action", []references.XRefAction{references.XRefActionCloses, references.XRefActionReopens}). OrderBy("id"). diff --git a/models/issue_xref_test.go b/models/issue_xref_test.go index 1ae5fb360f00..677caa48ba9b 100644 --- a/models/issue_xref_test.go +++ b/models/issue_xref_test.go @@ -98,7 +98,7 @@ func TestXRef_ResolveCrossReferences(t *testing.T) { i1 := testCreateIssue(t, 1, 2, "title1", "content1", false) i2 := testCreateIssue(t, 1, 2, "title2", "content2", false) i3 := testCreateIssue(t, 1, 2, "title3", "content3", false) - _, err := ChangeIssueStatus(i3, d, true) + _, err := ChangeIssueStatus(db.DefaultContext, i3, d, true) assert.NoError(t, err) pr := testCreatePR(t, 1, 2, "titlepr", fmt.Sprintf("closes #%d", i1.Index)) @@ -118,7 +118,7 @@ func TestXRef_ResolveCrossReferences(t *testing.T) { c4 := testCreateComment(t, 1, 2, pr.Issue.ID, fmt.Sprintf("closes #%d", i3.Index)) r4 := unittest.AssertExistsAndLoadBean(t, &Comment{IssueID: i3.ID, RefIssueID: pr.Issue.ID, RefCommentID: c4.ID}).(*Comment) - refs, err := pr.ResolveCrossReferences() + refs, err := pr.ResolveCrossReferences(db.DefaultContext) assert.NoError(t, err) assert.Len(t, refs, 3) assert.Equal(t, rp.ID, refs[0].ID, "bad ref rp: %+v", refs[0]) diff --git a/models/lfs_lock.go b/models/lfs_lock.go index a77dd24e9fa6..b5f8e4907fbf 100644 --- a/models/lfs_lock.go +++ b/models/lfs_lock.go @@ -5,6 +5,7 @@ package models import ( + "context" "fmt" "path" "strings" @@ -42,15 +43,20 @@ func cleanPath(p string) string { // CreateLFSLock creates a new lock. func CreateLFSLock(repo *repo_model.Repository, lock *LFSLock) (*LFSLock, error) { - err := CheckLFSAccessForRepo(lock.OwnerID, repo, perm.AccessModeWrite) + dbCtx, committer, err := db.TxContext() if err != nil { return nil, err } + defer committer.Close() + + if err := CheckLFSAccessForRepo(dbCtx, lock.OwnerID, repo, perm.AccessModeWrite); err != nil { + return nil, err + } lock.Path = cleanPath(lock.Path) lock.RepoID = repo.ID - l, err := GetLFSLock(repo, lock.Path) + l, err := GetLFSLock(dbCtx, repo, lock.Path) if err == nil { return l, ErrLFSLockAlreadyExist{lock.RepoID, lock.Path} } @@ -58,15 +64,18 @@ func CreateLFSLock(repo *repo_model.Repository, lock *LFSLock) (*LFSLock, error) return nil, err } - err = db.Insert(db.DefaultContext, lock) - return lock, err + if err := db.Insert(dbCtx, lock); err != nil { + return nil, err + } + + return lock, committer.Commit() } // GetLFSLock returns release by given path. -func GetLFSLock(repo *repo_model.Repository, path string) (*LFSLock, error) { +func GetLFSLock(ctx context.Context, repo *repo_model.Repository, path string) (*LFSLock, error) { path = cleanPath(path) rel := &LFSLock{RepoID: repo.ID} - has, err := db.GetEngine(db.DefaultContext).Where("lower(path) = ?", strings.ToLower(path)).Get(rel) + has, err := db.GetEngine(ctx).Where("lower(path) = ?", strings.ToLower(path)).Get(rel) if err != nil { return nil, err } @@ -77,9 +86,9 @@ func GetLFSLock(repo *repo_model.Repository, path string) (*LFSLock, error) { } // GetLFSLockByID returns release by given id. -func GetLFSLockByID(id int64) (*LFSLock, error) { +func GetLFSLockByID(ctx context.Context, id int64) (*LFSLock, error) { lock := new(LFSLock) - has, err := db.GetEngine(db.DefaultContext).ID(id).Get(lock) + has, err := db.GetEngine(ctx).ID(id).Get(lock) if err != nil { return nil, err } else if !has { @@ -127,34 +136,42 @@ func CountLFSLockByRepoID(repoID int64) (int64, error) { // DeleteLFSLockByID deletes a lock by given ID. func DeleteLFSLockByID(id int64, repo *repo_model.Repository, u *user_model.User, force bool) (*LFSLock, error) { - lock, err := GetLFSLockByID(id) + dbCtx, committer, err := db.TxContext() if err != nil { return nil, err } + defer committer.Close() - err = CheckLFSAccessForRepo(u.ID, repo, perm.AccessModeWrite) + lock, err := GetLFSLockByID(dbCtx, id) if err != nil { return nil, err } + if err := CheckLFSAccessForRepo(dbCtx, u.ID, repo, perm.AccessModeWrite); err != nil { + return nil, err + } + if !force && u.ID != lock.OwnerID { return nil, fmt.Errorf("user doesn't own lock and force flag is not set") } - _, err = db.GetEngine(db.DefaultContext).ID(id).Delete(new(LFSLock)) - return lock, err + if _, err := db.GetEngine(dbCtx).ID(id).Delete(new(LFSLock)); err != nil { + return nil, err + } + + return lock, committer.Commit() } // CheckLFSAccessForRepo check needed access mode base on action -func CheckLFSAccessForRepo(ownerID int64, repo *repo_model.Repository, mode perm.AccessMode) error { +func CheckLFSAccessForRepo(ctx context.Context, ownerID int64, repo *repo_model.Repository, mode perm.AccessMode) error { if ownerID == 0 { return ErrLFSUnauthorizedAction{repo.ID, "undefined", mode} } - u, err := user_model.GetUserByID(ownerID) + u, err := user_model.GetUserByIDCtx(ctx, ownerID) if err != nil { return err } - perm, err := GetUserRepoPermission(repo, u) + perm, err := GetUserRepoPermission(ctx, repo, u) if err != nil { return err } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 995a16641386..f07779963245 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -61,6 +61,7 @@ type Version struct { // update minDBVersion accordingly var migrations = []Migration{ // Gitea 1.5.0 ends at v69 + // v70 -> v71 NewMigration("add issue_dependencies", addIssueDependencies), // v71 -> v72 @@ -381,6 +382,8 @@ var migrations = []Migration{ // v212 -> v213 NewMigration("Add package tables", addPackageTables), // v213 -> v214 + NewMigration("Add allow edits from maintainers to PullRequest table", addAllowMaintainerEdit), + // v214 -> v215 NewMigration("Improve Action table indices", improveActionTableIndices), } @@ -462,7 +465,7 @@ Please try upgrading to a lower version first (suggested v1.6.4), then upgrade t // Downgrading Gitea's database version not supported if int(v-minDBVersion) > len(migrations) { - msg := fmt.Sprintf("Your database (migration version: %d) is for a newer Gita, you can not use the newer database for this old Gitea release (%d).", v, minDBVersion+len(migrations)) + msg := fmt.Sprintf("Your database (migration version: %d) is for a newer Gitea, you can not use the newer database for this old Gitea release (%d).", v, minDBVersion+len(migrations)) msg += "\nGitea will exit to keep your database safe and unchanged. Please use the correct Gitea release, do not change the migration version manually (incorrect manual operation may lose data)." if !setting.IsProd { msg += fmt.Sprintf("\nIf you are in development and really know what you're doing, you can force changing the migration version by executing: UPDATE version SET version=%d WHERE id=1;", minDBVersion+len(migrations)) diff --git a/models/migrations/v213.go b/models/migrations/v213.go new file mode 100644 index 000000000000..b1dbf98d1ea2 --- /dev/null +++ b/models/migrations/v213.go @@ -0,0 +1,18 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "xorm.io/xorm" +) + +func addAllowMaintainerEdit(x *xorm.Engine) error { + // PullRequest represents relation between pull request and repositories. + type PullRequest struct { + AllowMaintainerEdit bool `xorm:"NOT NULL DEFAULT false"` + } + + return x.Sync2(new(PullRequest)) +} diff --git a/models/organization/org.go b/models/organization/org.go index 9e71bbe80d4e..3761335922a4 100644 --- a/models/organization/org.go +++ b/models/organization/org.go @@ -683,7 +683,7 @@ func (org *Organization) getUserTeamIDs(ctx context.Context, userID int64) ([]in // TeamsWithAccessToRepo returns all teams that have given access level to the repository. func (org *Organization) TeamsWithAccessToRepo(repoID int64, mode perm.AccessMode) ([]*Team, error) { - return GetTeamsWithAccessToRepo(org.ID, repoID, mode) + return GetTeamsWithAccessToRepo(db.DefaultContext, org.ID, repoID, mode) } // GetUserTeamIDs returns of all team IDs of the organization that user is member of. diff --git a/models/organization/team_repo.go b/models/organization/team_repo.go index 657e83aaa56f..717d754c40b7 100644 --- a/models/organization/team_repo.go +++ b/models/organization/team_repo.go @@ -75,9 +75,9 @@ func RemoveTeamRepo(ctx context.Context, teamID, repoID int64) error { } // GetTeamsWithAccessToRepo returns all teams in an organization that have given access level to the repository. -func GetTeamsWithAccessToRepo(orgID, repoID int64, mode perm.AccessMode) ([]*Team, error) { +func GetTeamsWithAccessToRepo(ctx context.Context, orgID, repoID int64, mode perm.AccessMode) ([]*Team, error) { teams := make([]*Team, 0, 5) - return teams, db.GetEngine(db.DefaultContext).Where("team.authorize >= ?", mode). + return teams, db.GetEngine(ctx).Where("team.authorize >= ?", mode). Join("INNER", "team_repo", "team_repo.team_id = team.id"). And("team_repo.org_id = ?", orgID). And("team_repo.repo_id = ?", repoID). diff --git a/models/pull.go b/models/pull.go index ac44ebf0bea2..d056888130e6 100644 --- a/models/pull.go +++ b/models/pull.go @@ -69,15 +69,16 @@ type PullRequest struct { Issue *Issue `xorm:"-"` Index int64 - HeadRepoID int64 `xorm:"INDEX"` - HeadRepo *repo_model.Repository `xorm:"-"` - BaseRepoID int64 `xorm:"INDEX"` - BaseRepo *repo_model.Repository `xorm:"-"` - HeadBranch string - HeadCommitID string `xorm:"-"` - BaseBranch string - ProtectedBranch *ProtectedBranch `xorm:"-"` - MergeBase string `xorm:"VARCHAR(40)"` + HeadRepoID int64 `xorm:"INDEX"` + HeadRepo *repo_model.Repository `xorm:"-"` + BaseRepoID int64 `xorm:"INDEX"` + BaseRepo *repo_model.Repository `xorm:"-"` + HeadBranch string + HeadCommitID string `xorm:"-"` + BaseBranch string + ProtectedBranch *ProtectedBranch `xorm:"-"` + MergeBase string `xorm:"VARCHAR(40)"` + AllowMaintainerEdit bool `xorm:"NOT NULL DEFAULT false"` HasMerged bool `xorm:"INDEX"` MergedCommitID string `xorm:"VARCHAR(40)"` @@ -130,7 +131,8 @@ func (pr *PullRequest) LoadAttributes() error { return pr.loadAttributes(db.GetEngine(db.DefaultContext)) } -func (pr *PullRequest) loadHeadRepo(ctx context.Context) (err error) { +// LoadHeadRepoCtx loads the head repository +func (pr *PullRequest) LoadHeadRepoCtx(ctx context.Context) (err error) { if !pr.isHeadRepoLoaded && pr.HeadRepo == nil && pr.HeadRepoID > 0 { if pr.HeadRepoID == pr.BaseRepoID { if pr.BaseRepo != nil { @@ -153,15 +155,16 @@ func (pr *PullRequest) loadHeadRepo(ctx context.Context) (err error) { // LoadHeadRepo loads the head repository func (pr *PullRequest) LoadHeadRepo() error { - return pr.loadHeadRepo(db.DefaultContext) + return pr.LoadHeadRepoCtx(db.DefaultContext) } // LoadBaseRepo loads the target repository func (pr *PullRequest) LoadBaseRepo() error { - return pr.loadBaseRepo(db.DefaultContext) + return pr.LoadBaseRepoCtx(db.DefaultContext) } -func (pr *PullRequest) loadBaseRepo(ctx context.Context) (err error) { +// LoadBaseRepoCtx loads the target repository +func (pr *PullRequest) LoadBaseRepoCtx(ctx context.Context) (err error) { if pr.BaseRepo != nil { return nil } @@ -185,15 +188,16 @@ func (pr *PullRequest) loadBaseRepo(ctx context.Context) (err error) { // LoadIssue loads issue information from database func (pr *PullRequest) LoadIssue() (err error) { - return pr.loadIssue(db.GetEngine(db.DefaultContext)) + return pr.LoadIssueCtx(db.DefaultContext) } -func (pr *PullRequest) loadIssue(e db.Engine) (err error) { +// LoadIssueCtx loads issue information from database +func (pr *PullRequest) LoadIssueCtx(ctx context.Context) (err error) { if pr.Issue != nil { return nil } - pr.Issue, err = getIssueByID(e, pr.IssueID) + pr.Issue, err = getIssueByID(db.GetEngine(ctx), pr.IssueID) if err == nil { pr.Issue.PullRequest = pr } @@ -202,10 +206,11 @@ func (pr *PullRequest) loadIssue(e db.Engine) (err error) { // LoadProtectedBranch loads the protected branch of the base branch func (pr *PullRequest) LoadProtectedBranch() (err error) { - return pr.loadProtectedBranch(db.DefaultContext) + return pr.LoadProtectedBranchCtx(db.DefaultContext) } -func (pr *PullRequest) loadProtectedBranch(ctx context.Context) (err error) { +// LoadProtectedBranchCtx loads the protected branch of the base branch +func (pr *PullRequest) LoadProtectedBranchCtx(ctx context.Context) (err error) { if pr.ProtectedBranch == nil { if pr.BaseRepo == nil { if pr.BaseRepoID == 0 { @@ -222,23 +227,23 @@ func (pr *PullRequest) loadProtectedBranch(ctx context.Context) (err error) { } // GetDefaultMergeMessage returns default message used when merging pull request -func (pr *PullRequest) GetDefaultMergeMessage() (string, error) { +func (pr *PullRequest) GetDefaultMergeMessage(ctx context.Context) (string, error) { if pr.HeadRepo == nil { var err error - pr.HeadRepo, err = repo_model.GetRepositoryByID(pr.HeadRepoID) + pr.HeadRepo, err = repo_model.GetRepositoryByIDCtx(ctx, pr.HeadRepoID) if err != nil { return "", fmt.Errorf("GetRepositoryById[%d]: %v", pr.HeadRepoID, err) } } - if err := pr.LoadIssue(); err != nil { + if err := pr.LoadIssueCtx(ctx); err != nil { return "", fmt.Errorf("Cannot load issue %d for PR id %d: Error: %v", pr.IssueID, pr.ID, err) } - if err := pr.LoadBaseRepo(); err != nil { + if err := pr.LoadBaseRepoCtx(ctx); err != nil { return "", fmt.Errorf("LoadBaseRepo: %v", err) } issueReference := "#" - if pr.BaseRepo.UnitEnabled(unit.TypeExternalTracker) { + if pr.BaseRepo.UnitEnabledCtx(ctx, unit.TypeExternalTracker) { issueReference = "!" } @@ -332,14 +337,14 @@ func (pr *PullRequest) getReviewedByLines(writer io.Writer) error { } // GetDefaultSquashMessage returns default message used when squash and merging pull request -func (pr *PullRequest) GetDefaultSquashMessage() (string, error) { - if err := pr.LoadIssue(); err != nil { +func (pr *PullRequest) GetDefaultSquashMessage(ctx context.Context) (string, error) { + if err := pr.LoadIssueCtx(ctx); err != nil { return "", fmt.Errorf("LoadIssue: %v", err) } - if err := pr.LoadBaseRepo(); err != nil { + if err := pr.LoadBaseRepoCtx(ctx); err != nil { return "", fmt.Errorf("LoadBaseRepo: %v", err) } - if pr.BaseRepo.UnitEnabled(unit.TypeExternalTracker) { + if pr.BaseRepo.UnitEnabledCtx(ctx, unit.TypeExternalTracker) { return fmt.Sprintf("%s (!%d)", pr.Issue.Title, pr.Issue.Index), nil } return fmt.Sprintf("%s (#%d)", pr.Issue.Title, pr.Issue.Index), nil @@ -366,7 +371,7 @@ func (pr *PullRequest) IsEmpty() bool { } // SetMerged sets a pull request to merged and closes the corresponding issue -func (pr *PullRequest) SetMerged() (bool, error) { +func (pr *PullRequest) SetMerged(ctx context.Context) (bool, error) { if pr.HasMerged { return false, fmt.Errorf("PullRequest[%d] already merged", pr.Index) } @@ -375,12 +380,6 @@ func (pr *PullRequest) SetMerged() (bool, error) { } pr.HasMerged = true - - ctx, committer, err := db.TxContext() - if err != nil { - return false, err - } - defer committer.Close() sess := db.GetEngine(ctx) if _, err := sess.Exec("UPDATE `issue` SET `repo_id` = `repo_id` WHERE `id` = ?", pr.IssueID); err != nil { @@ -392,7 +391,7 @@ func (pr *PullRequest) SetMerged() (bool, error) { } pr.Issue = nil - if err := pr.loadIssue(sess); err != nil { + if err := pr.LoadIssueCtx(ctx); err != nil { return false, err } @@ -427,9 +426,6 @@ func (pr *PullRequest) SetMerged() (bool, error) { return false, fmt.Errorf("Failed to update pr[%d]: %v", pr.ID, err) } - if err := committer.Commit(); err != nil { - return false, fmt.Errorf("Commit: %v", err) - } return true, nil } @@ -510,6 +506,11 @@ func GetLatestPullRequestByHeadInfo(repoID int64, branch string) (*PullRequest, // GetPullRequestByIndex returns a pull request by the given index func GetPullRequestByIndex(repoID, index int64) (*PullRequest, error) { + return GetPullRequestByIndexCtx(db.DefaultContext, repoID, index) +} + +// GetPullRequestByIndexCtx returns a pull request by the given index +func GetPullRequestByIndexCtx(ctx context.Context, repoID, index int64) (*PullRequest, error) { if index < 1 { return nil, ErrPullRequestNotExist{} } @@ -518,17 +519,17 @@ func GetPullRequestByIndex(repoID, index int64) (*PullRequest, error) { Index: index, } - has, err := db.GetEngine(db.DefaultContext).Get(pr) + has, err := db.GetEngine(ctx).Get(pr) if err != nil { return nil, err } else if !has { return nil, ErrPullRequestNotExist{0, 0, 0, repoID, "", ""} } - if err = pr.LoadAttributes(); err != nil { + if err = pr.loadAttributes(db.GetEngine(ctx)); err != nil { return nil, err } - if err = pr.LoadIssue(); err != nil { + if err = pr.LoadIssueCtx(ctx); err != nil { return nil, err } @@ -547,8 +548,8 @@ func getPullRequestByID(e db.Engine, id int64) (*PullRequest, error) { } // GetPullRequestByID returns a pull request by given ID. -func GetPullRequestByID(id int64) (*PullRequest, error) { - return getPullRequestByID(db.GetEngine(db.DefaultContext), id) +func GetPullRequestByID(ctx context.Context, id int64) (*PullRequest, error) { + return getPullRequestByID(db.GetEngine(ctx), id) } // GetPullRequestByIssueIDWithNoAttributes returns pull request with no attributes loaded by given issue ID. @@ -702,6 +703,14 @@ func (pr *PullRequest) GetHeadBranchHTMLURL() string { return pr.HeadRepo.HTMLURL() + "/src/branch/" + util.PathEscapeSegments(pr.HeadBranch) } +// UpdateAllowEdits update if PR can be edited from maintainers +func UpdateAllowEdits(ctx context.Context, pr *PullRequest) error { + if _, err := db.GetEngine(ctx).ID(pr.ID).Cols("allow_maintainer_edit").Update(pr); err != nil { + return err + } + return nil +} + // Mergeable returns if the pullrequest is mergeable. func (pr *PullRequest) Mergeable() bool { // If a pull request isn't mergable if it's: diff --git a/models/pull_list.go b/models/pull_list.go index 9d4d42892883..ca09e28a93ba 100644 --- a/models/pull_list.go +++ b/models/pull_list.go @@ -5,6 +5,7 @@ package models import ( + "context" "fmt" "code.gitea.io/gitea/models/db" @@ -61,8 +62,8 @@ func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequ // HasUnmergedPullRequestsByHeadInfo checks if there are open and not merged pull request // by given head information (repo and branch) -func HasUnmergedPullRequestsByHeadInfo(repoID int64, branch string) (bool, error) { - return db.GetEngine(db.DefaultContext). +func HasUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch string) (bool, error) { + return db.GetEngine(ctx). Where("head_repo_id = ? AND head_branch = ? AND has_merged = ? AND issue.is_closed = ? AND flow = ?", repoID, branch, false, false, PullRequestFlowGithub). Join("INNER", "issue", "issue.id = pull_request.issue_id"). @@ -158,13 +159,14 @@ func (prs PullRequestList) LoadAttributes() error { return prs.loadAttributes(db.GetEngine(db.DefaultContext)) } -func (prs PullRequestList) invalidateCodeComments(e db.Engine, doer *user_model.User, repo *git.Repository, branch string) error { +// InvalidateCodeComments will lookup the prs for code comments which got invalidated by change +func (prs PullRequestList) InvalidateCodeComments(ctx context.Context, doer *user_model.User, repo *git.Repository, branch string) error { if len(prs) == 0 { return nil } issueIDs := prs.getIssueIDs() var codeComments []*Comment - if err := e. + if err := db.GetEngine(ctx). Where("type = ? and invalidated = ?", CommentTypeCode, false). In("issue_id", issueIDs). Find(&codeComments); err != nil { @@ -177,8 +179,3 @@ func (prs PullRequestList) invalidateCodeComments(e db.Engine, doer *user_model. } return nil } - -// InvalidateCodeComments will lookup the prs for code comments which got invalidated by change -func (prs PullRequestList) InvalidateCodeComments(doer *user_model.User, repo *git.Repository, branch string) error { - return prs.invalidateCodeComments(db.GetEngine(db.DefaultContext), doer, repo, branch) -} diff --git a/models/pull_test.go b/models/pull_test.go index 9098b611617a..92cf9a652417 100644 --- a/models/pull_test.go +++ b/models/pull_test.go @@ -110,11 +110,11 @@ func TestGetUnmergedPullRequest(t *testing.T) { func TestHasUnmergedPullRequestsByHeadInfo(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - exist, err := HasUnmergedPullRequestsByHeadInfo(1, "branch2") + exist, err := HasUnmergedPullRequestsByHeadInfo(db.DefaultContext, 1, "branch2") assert.NoError(t, err) assert.Equal(t, true, exist) - exist, err = HasUnmergedPullRequestsByHeadInfo(1, "not_exist_branch") + exist, err = HasUnmergedPullRequestsByHeadInfo(db.DefaultContext, 1, "not_exist_branch") assert.NoError(t, err) assert.Equal(t, false, exist) } @@ -159,12 +159,12 @@ func TestGetPullRequestByIndex(t *testing.T) { func TestGetPullRequestByID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - pr, err := GetPullRequestByID(1) + pr, err := GetPullRequestByID(db.DefaultContext, 1) assert.NoError(t, err) assert.Equal(t, int64(1), pr.ID) assert.Equal(t, int64(2), pr.IssueID) - _, err = GetPullRequestByID(9223372036854775807) + _, err = GetPullRequestByID(db.DefaultContext, 9223372036854775807) assert.Error(t, err) assert.True(t, IsErrPullRequestNotExist(err)) } @@ -261,13 +261,13 @@ func TestPullRequest_GetDefaultMergeMessage_InternalTracker(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) pr := unittest.AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest) - msg, err := pr.GetDefaultMergeMessage() + msg, err := pr.GetDefaultMergeMessage(db.DefaultContext) assert.NoError(t, err) assert.Equal(t, "Merge pull request 'issue3' (#3) from branch2 into master", msg) pr.BaseRepoID = 1 pr.HeadRepoID = 2 - msg, err = pr.GetDefaultMergeMessage() + msg, err = pr.GetDefaultMergeMessage(db.DefaultContext) assert.NoError(t, err) assert.Equal(t, "Merge pull request 'issue3' (#3) from user2/repo1:branch2 into master", msg) } @@ -287,13 +287,22 @@ func TestPullRequest_GetDefaultMergeMessage_ExternalTracker(t *testing.T) { pr := unittest.AssertExistsAndLoadBean(t, &PullRequest{ID: 2, BaseRepo: baseRepo}).(*PullRequest) - msg, err := pr.GetDefaultMergeMessage() + msg, err := pr.GetDefaultMergeMessage(db.DefaultContext) assert.NoError(t, err) assert.Equal(t, "Merge pull request 'issue3' (!3) from branch2 into master", msg) pr.BaseRepoID = 1 pr.HeadRepoID = 2 - msg, err = pr.GetDefaultMergeMessage() + msg, err = pr.GetDefaultMergeMessage(db.DefaultContext) assert.NoError(t, err) assert.Equal(t, "Merge pull request 'issue3' (!3) from user2/repo1:branch2 into master", msg) } + +func TestPullRequest_GetDefaultSquashMessage(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + pr := unittest.AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest) + + msg, err := pr.GetDefaultSquashMessage(db.DefaultContext) + assert.NoError(t, err) + assert.Equal(t, "issue3 (#3)", msg) +} diff --git a/models/repo.go b/models/repo.go index 5073d1ceb903..e934b24fb385 100644 --- a/models/repo.go +++ b/models/repo.go @@ -57,9 +57,9 @@ func checkRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *u if user.IsAdmin { return true } - perm, err := getUserRepoPermission(ctx, repo, user) + perm, err := GetUserRepoPermission(ctx, repo, user) if err != nil { - log.Error("getUserRepoPermission(): %v", err) + log.Error("GetUserRepoPermission(): %v", err) return false } @@ -198,7 +198,7 @@ func GetReviewerTeams(repo *repo_model.Repository) ([]*organization.Team, error) return nil, nil } - teams, err := organization.GetTeamsWithAccessToRepo(repo.OwnerID, repo.ID, perm.AccessModeRead) + teams, err := organization.GetTeamsWithAccessToRepo(db.DefaultContext, repo.OwnerID, repo.ID, perm.AccessModeRead) if err != nil { return nil, err } @@ -455,8 +455,8 @@ func CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_ } } - if isAdmin, err := isUserRepoAdmin(ctx, repo, doer); err != nil { - return fmt.Errorf("isUserRepoAdmin: %v", err) + if isAdmin, err := IsUserRepoAdminCtx(ctx, repo, doer); err != nil { + return fmt.Errorf("IsUserRepoAdminCtx: %v", err) } else if !isAdmin { // Make creator repo admin if it wasn't assigned automatically if err = addCollaborator(ctx, repo, doer); err != nil { @@ -1215,7 +1215,7 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error if err != nil { return fmt.Errorf("GetRepositoryByID: %v", err) } - has, err := isUserRepoAdmin(ctx, repo, doer) + has, err := IsUserRepoAdminCtx(ctx, repo, doer) if err != nil { return fmt.Errorf("GetUserRepoPermission: %v", err) } else if !has { diff --git a/models/repo/repo.go b/models/repo/repo.go index fc72d36dac8e..8af6357bf37b 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -302,8 +302,19 @@ func (repo *Repository) LoadUnits(ctx context.Context) (err error) { } // UnitEnabled if this repository has the given unit enabled -func (repo *Repository) UnitEnabled(tp unit.Type) bool { - if err := repo.LoadUnits(db.DefaultContext); err != nil { +func (repo *Repository) UnitEnabled(tp unit.Type) (result bool) { + if err := db.WithContext(func(ctx *db.Context) error { + result = repo.UnitEnabledCtx(ctx, tp) + return nil + }); err != nil { + log.Error("repo.UnitEnabled: %v", err) + } + return +} + +// UnitEnabled if this repository has the given unit enabled +func (repo *Repository) UnitEnabledCtx(ctx context.Context, tp unit.Type) bool { + if err := repo.LoadUnits(ctx); err != nil { log.Warn("Error loading repository (ID: %d) units: %s", repo.ID, err.Error()) } for _, unit := range repo.Units { diff --git a/models/repo_activity.go b/models/repo_activity.go index 7475be2b116d..06710ff1ac5d 100644 --- a/models/repo_activity.go +++ b/models/repo_activity.go @@ -127,7 +127,7 @@ func GetActivityStatsTopAuthors(ctx context.Context, repo *repo_model.Repository user.Commits += v.Commits } } - v := make([]*ActivityAuthorData, 0) + v := make([]*ActivityAuthorData, 0, len(users)) for _, u := range users { v = append(v, u) } diff --git a/models/repo_permission.go b/models/repo_permission.go index 3e9ad04482d9..8ba6b8614561 100644 --- a/models/repo_permission.go +++ b/models/repo_permission.go @@ -103,6 +103,39 @@ func (p *Permission) CanWriteIssuesOrPulls(isPull bool) bool { return p.CanWrite(unit.TypeIssues) } +// CanWriteToBranch checks if the branch is writable by the user +func (p *Permission) CanWriteToBranch(user *user_model.User, branch string) bool { + if p.CanWrite(unit.TypeCode) { + return true + } + + if len(p.Units) < 1 { + return false + } + + prs, err := GetUnmergedPullRequestsByHeadInfo(p.Units[0].RepoID, branch) + if err != nil { + return false + } + + for _, pr := range prs { + if pr.AllowMaintainerEdit { + err = pr.LoadBaseRepo() + if err != nil { + continue + } + prPerm, err := GetUserRepoPermission(db.DefaultContext, pr.BaseRepo, user) + if err != nil { + continue + } + if prPerm.CanWrite(unit.TypeCode) { + return true + } + } + } + return false +} + // ColorFormat writes a colored string for these Permissions func (p *Permission) ColorFormat(s fmt.State) { noColor := log.ColorBytes(log.Reset) @@ -145,11 +178,7 @@ func (p *Permission) ColorFormat(s fmt.State) { } // GetUserRepoPermission returns the user permissions to the repository -func GetUserRepoPermission(repo *repo_model.Repository, user *user_model.User) (Permission, error) { - return getUserRepoPermission(db.DefaultContext, repo, user) -} - -func getUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) { +func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) { if log.IsTrace() { defer func() { if user == nil { @@ -164,6 +193,7 @@ func getUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use perm) }() } + // anonymous user visit private repo. // TODO: anonymous user visit public unit of private repo??? if user == nil && repo.IsPrivate { @@ -301,10 +331,11 @@ func IsUserRealRepoAdmin(repo *repo_model.Repository, user *user_model.User) (bo // IsUserRepoAdmin return true if user has admin right of a repo func IsUserRepoAdmin(repo *repo_model.Repository, user *user_model.User) (bool, error) { - return isUserRepoAdmin(db.DefaultContext, repo, user) + return IsUserRepoAdminCtx(db.DefaultContext, repo, user) } -func isUserRepoAdmin(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (bool, error) { +// IsUserRepoAdminCtx return true if user has admin right of a repo +func IsUserRepoAdminCtx(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (bool, error) { if user == nil || repo == nil { return false, nil } @@ -347,7 +378,7 @@ func AccessLevelUnit(user *user_model.User, repo *repo_model.Repository, unitTyp } func accessLevelUnit(ctx context.Context, user *user_model.User, repo *repo_model.Repository, unitType unit.Type) (perm_model.AccessMode, error) { - perm, err := getUserRepoPermission(ctx, repo, user) + perm, err := GetUserRepoPermission(ctx, repo, user) if err != nil { return perm_model.AccessModeNone, err } @@ -375,7 +406,7 @@ func canBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model. if user.IsOrganization() { return false, fmt.Errorf("Organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID) } - perm, err := getUserRepoPermission(ctx, repo, user) + perm, err := GetUserRepoPermission(ctx, repo, user) if err != nil { return false, err } @@ -391,7 +422,7 @@ func hasAccess(ctx context.Context, userID int64, repo *repo_model.Repository) ( return false, err } } - perm, err := getUserRepoPermission(ctx, repo, user) + perm, err := GetUserRepoPermission(ctx, repo, user) if err != nil { return false, err } @@ -414,9 +445,9 @@ func GetRepoWriters(repo *repo_model.Repository) (_ []*user_model.User, err erro } // IsRepoReader returns true if user has explicit read access or higher to the repository. -func IsRepoReader(repo *repo_model.Repository, userID int64) (bool, error) { +func IsRepoReader(ctx context.Context, repo *repo_model.Repository, userID int64) (bool, error) { if repo.OwnerID == userID { return true, nil } - return db.GetEngine(db.DefaultContext).Where("repo_id = ? AND user_id = ? AND mode >= ?", repo.ID, userID, perm_model.AccessModeRead).Get(&Access{}) + return db.GetEngine(ctx).Where("repo_id = ? AND user_id = ? AND mode >= ?", repo.ID, userID, perm_model.AccessModeRead).Get(&Access{}) } diff --git a/models/repo_permission_test.go b/models/repo_permission_test.go index c103a9363f82..7e22437f997e 100644 --- a/models/repo_permission_test.go +++ b/models/repo_permission_test.go @@ -27,7 +27,7 @@ func TestRepoPermissionPublicNonOrgRepo(t *testing.T) { // plain user user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) - perm, err := GetUserRepoPermission(repo, user) + perm, err := GetUserRepoPermission(db.DefaultContext, repo, user) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) @@ -36,7 +36,7 @@ func TestRepoPermissionPublicNonOrgRepo(t *testing.T) { // change to collaborator assert.NoError(t, AddCollaborator(repo, user)) - perm, err = GetUserRepoPermission(repo, user) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, user) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) @@ -45,7 +45,7 @@ func TestRepoPermissionPublicNonOrgRepo(t *testing.T) { // collaborator collaborator := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User) - perm, err = GetUserRepoPermission(repo, collaborator) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, collaborator) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) @@ -54,7 +54,7 @@ func TestRepoPermissionPublicNonOrgRepo(t *testing.T) { // owner owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User) - perm, err = GetUserRepoPermission(repo, owner) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, owner) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) @@ -63,7 +63,7 @@ func TestRepoPermissionPublicNonOrgRepo(t *testing.T) { // admin admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User) - perm, err = GetUserRepoPermission(repo, admin) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, admin) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) @@ -80,7 +80,7 @@ func TestRepoPermissionPrivateNonOrgRepo(t *testing.T) { // plain user user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User) - perm, err := GetUserRepoPermission(repo, user) + perm, err := GetUserRepoPermission(db.DefaultContext, repo, user) assert.NoError(t, err) for _, unit := range repo.Units { assert.False(t, perm.CanRead(unit.Type)) @@ -89,7 +89,7 @@ func TestRepoPermissionPrivateNonOrgRepo(t *testing.T) { // change to collaborator to default write access assert.NoError(t, AddCollaborator(repo, user)) - perm, err = GetUserRepoPermission(repo, user) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, user) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) @@ -97,7 +97,7 @@ func TestRepoPermissionPrivateNonOrgRepo(t *testing.T) { } assert.NoError(t, ChangeCollaborationAccessMode(repo, user.ID, perm_model.AccessModeRead)) - perm, err = GetUserRepoPermission(repo, user) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, user) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) @@ -106,7 +106,7 @@ func TestRepoPermissionPrivateNonOrgRepo(t *testing.T) { // owner owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) - perm, err = GetUserRepoPermission(repo, owner) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, owner) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) @@ -115,7 +115,7 @@ func TestRepoPermissionPrivateNonOrgRepo(t *testing.T) { // admin admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User) - perm, err = GetUserRepoPermission(repo, admin) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, admin) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) @@ -132,7 +132,7 @@ func TestRepoPermissionPublicOrgRepo(t *testing.T) { // plain user user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User) - perm, err := GetUserRepoPermission(repo, user) + perm, err := GetUserRepoPermission(db.DefaultContext, repo, user) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) @@ -141,7 +141,7 @@ func TestRepoPermissionPublicOrgRepo(t *testing.T) { // change to collaborator to default write access assert.NoError(t, AddCollaborator(repo, user)) - perm, err = GetUserRepoPermission(repo, user) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, user) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) @@ -149,7 +149,7 @@ func TestRepoPermissionPublicOrgRepo(t *testing.T) { } assert.NoError(t, ChangeCollaborationAccessMode(repo, user.ID, perm_model.AccessModeRead)) - perm, err = GetUserRepoPermission(repo, user) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, user) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) @@ -158,7 +158,7 @@ func TestRepoPermissionPublicOrgRepo(t *testing.T) { // org member team owner owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) - perm, err = GetUserRepoPermission(repo, owner) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, owner) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) @@ -167,7 +167,7 @@ func TestRepoPermissionPublicOrgRepo(t *testing.T) { // org member team tester member := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}).(*user_model.User) - perm, err = GetUserRepoPermission(repo, member) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, member) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) @@ -177,7 +177,7 @@ func TestRepoPermissionPublicOrgRepo(t *testing.T) { // admin admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User) - perm, err = GetUserRepoPermission(repo, admin) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, admin) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) @@ -194,7 +194,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) { // plain user user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User) - perm, err := GetUserRepoPermission(repo, user) + perm, err := GetUserRepoPermission(db.DefaultContext, repo, user) assert.NoError(t, err) for _, unit := range repo.Units { assert.False(t, perm.CanRead(unit.Type)) @@ -203,7 +203,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) { // change to collaborator to default write access assert.NoError(t, AddCollaborator(repo, user)) - perm, err = GetUserRepoPermission(repo, user) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, user) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) @@ -211,7 +211,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) { } assert.NoError(t, ChangeCollaborationAccessMode(repo, user.ID, perm_model.AccessModeRead)) - perm, err = GetUserRepoPermission(repo, user) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, user) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) @@ -220,7 +220,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) { // org member team owner owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}).(*user_model.User) - perm, err = GetUserRepoPermission(repo, owner) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, owner) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) @@ -231,7 +231,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) { team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}).(*organization.Team) err = organization.UpdateTeamUnits(team, nil) assert.NoError(t, err) - perm, err = GetUserRepoPermission(repo, owner) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, owner) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) @@ -240,7 +240,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) { // org member team tester tester := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) - perm, err = GetUserRepoPermission(repo, tester) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, tester) assert.NoError(t, err) assert.True(t, perm.CanWrite(unit.TypeIssues)) assert.False(t, perm.CanWrite(unit.TypeCode)) @@ -248,7 +248,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) { // org member team reviewer reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 20}).(*user_model.User) - perm, err = GetUserRepoPermission(repo, reviewer) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, reviewer) assert.NoError(t, err) assert.False(t, perm.CanRead(unit.TypeIssues)) assert.False(t, perm.CanWrite(unit.TypeCode)) @@ -256,7 +256,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) { // admin admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User) - perm, err = GetUserRepoPermission(repo, admin) + perm, err = GetUserRepoPermission(db.DefaultContext, repo, admin) assert.NoError(t, err) for _, unit := range repo.Units { assert.True(t, perm.CanRead(unit.Type)) diff --git a/models/review.go b/models/review.go index 51818bc72220..a9e29a10e05c 100644 --- a/models/review.go +++ b/models/review.go @@ -238,7 +238,7 @@ func isOfficialReviewer(ctx context.Context, issue *Issue, reviewers ...*user_mo if err != nil { return false, err } - if err = pr.loadProtectedBranch(ctx); err != nil { + if err = pr.LoadProtectedBranchCtx(ctx); err != nil { return false, err } if pr.ProtectedBranch == nil { @@ -265,7 +265,7 @@ func isOfficialReviewerTeam(ctx context.Context, issue *Issue, team *organizatio if err != nil { return false, err } - if err = pr.loadProtectedBranch(ctx); err != nil { + if err = pr.LoadProtectedBranchCtx(ctx); err != nil { return false, err } if pr.ProtectedBranch == nil { @@ -891,7 +891,7 @@ func CanMarkConversation(issue *Issue, doer *user_model.User) (permResult bool, return false, err } - p, err := GetUserRepoPermission(issue.Repo, doer) + p, err := GetUserRepoPermission(db.DefaultContext, issue.Repo, doer) if err != nil { return false, err } diff --git a/models/statistic.go b/models/statistic.go index 87c1bd6d754e..d858102be8e3 100644 --- a/models/statistic.go +++ b/models/statistic.go @@ -49,7 +49,7 @@ type IssueByRepositoryCount struct { // GetStatistic returns the database statistics func GetStatistic() (stats Statistic) { e := db.GetEngine(db.DefaultContext) - stats.Counter.User = user_model.CountUsers() + stats.Counter.User = user_model.CountUsers(nil) stats.Counter.Org = organization.CountOrganizations() stats.Counter.PublicKey, _ = e.Count(new(asymkey_model.PublicKey)) stats.Counter.Repo = repo_model.CountRepositories(true) diff --git a/models/task.go b/models/task.go index bade1a639de8..5528573ca550 100644 --- a/models/task.go +++ b/models/task.go @@ -181,6 +181,14 @@ func GetMigratingTask(repoID int64) (*Task, error) { return &task, nil } +// HasFinishedMigratingTask returns if a finished migration task exists for the repo. +func HasFinishedMigratingTask(repoID int64) (bool, error) { + return db.GetEngine(db.DefaultContext). + Where("repo_id=? AND type=? AND status=?", repoID, structs.TaskTypeMigrateRepo, structs.TaskStatusFinished). + Table("task"). + Exist() +} + // GetMigratingTaskByID returns the migrating task by repo's id func GetMigratingTaskByID(id, doerID int64) (*Task, *migration.MigrateOptions, error) { task := Task{ diff --git a/models/user/list.go b/models/user/list.go index 06ec5113753f..5cdc92ba4a6b 100644 --- a/models/user/list.go +++ b/models/user/list.go @@ -17,7 +17,7 @@ type UserList []*User //revive:disable-line:exported // GetUserIDs returns a slice of user's id func (users UserList) GetUserIDs() []int64 { - userIDs := make([]int64, len(users)) + userIDs := make([]int64, 0, len(users)) for _, user := range users { userIDs = append(userIDs, user.ID) // Considering that user id are unique in the list } diff --git a/models/user/user.go b/models/user/user.go index c84889523918..6aa63a0a567d 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -621,7 +621,14 @@ func IsUsableUsername(name string) error { // CreateUserOverwriteOptions are an optional options who overwrite system defaults on user creation type CreateUserOverwriteOptions struct { - Visibility structs.VisibleType + KeepEmailPrivate util.OptionalBool + Visibility *structs.VisibleType + AllowCreateOrganization util.OptionalBool + EmailNotificationsPreference *string + MaxRepoCreation *int + Theme *string + IsRestricted util.OptionalBool + IsActive util.OptionalBool } // CreateUser creates record of a new user. @@ -637,10 +644,36 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e u.EmailNotificationsPreference = setting.Admin.DefaultEmailNotification u.MaxRepoCreation = -1 u.Theme = setting.UI.DefaultTheme + u.IsRestricted = setting.Service.DefaultUserIsRestricted + u.IsActive = !(setting.Service.RegisterEmailConfirm || setting.Service.RegisterManualConfirm) // overwrite defaults if set if len(overwriteDefault) != 0 && overwriteDefault[0] != nil { - u.Visibility = overwriteDefault[0].Visibility + overwrite := overwriteDefault[0] + if !overwrite.KeepEmailPrivate.IsNone() { + u.KeepEmailPrivate = overwrite.KeepEmailPrivate.IsTrue() + } + if overwrite.Visibility != nil { + u.Visibility = *overwrite.Visibility + } + if !overwrite.AllowCreateOrganization.IsNone() { + u.AllowCreateOrganization = overwrite.AllowCreateOrganization.IsTrue() + } + if overwrite.EmailNotificationsPreference != nil { + u.EmailNotificationsPreference = *overwrite.EmailNotificationsPreference + } + if overwrite.MaxRepoCreation != nil { + u.MaxRepoCreation = *overwrite.MaxRepoCreation + } + if overwrite.Theme != nil { + u.Theme = *overwrite.Theme + } + if !overwrite.IsRestricted.IsNone() { + u.IsRestricted = overwrite.IsRestricted.IsTrue() + } + if !overwrite.IsActive.IsNone() { + u.IsActive = overwrite.IsActive.IsTrue() + } } // validate data @@ -711,16 +744,25 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e return committer.Commit() } -func countUsers(e db.Engine) int64 { - count, _ := e. - Where("type=0"). - Count(new(User)) - return count +// CountUserFilter represent optional filters for CountUsers +type CountUserFilter struct { + LastLoginSince *int64 } // CountUsers returns number of users. -func CountUsers() int64 { - return countUsers(db.GetEngine(db.DefaultContext)) +func CountUsers(opts *CountUserFilter) int64 { + return countUsers(db.DefaultContext, opts) +} + +func countUsers(ctx context.Context, opts *CountUserFilter) int64 { + sess := db.GetEngine(ctx).Where(builder.Eq{"type": "0"}) + + if opts != nil && opts.LastLoginSince != nil { + sess = sess.Where(builder.Gte{"last_login_unix": *opts.LastLoginSince}) + } + + count, _ := sess.Count(new(User)) + return count } // GetVerifyUser get user by verify code diff --git a/modules/auth/pam/pam.go b/modules/auth/pam/pam.go index 1acd0b112c6f..39e93d716209 100644 --- a/modules/auth/pam/pam.go +++ b/modules/auth/pam/pam.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build pam -// +build pam package pam diff --git a/modules/auth/pam/pam_stub.go b/modules/auth/pam/pam_stub.go index 815ccf2b0e2d..414d7631b5c7 100644 --- a/modules/auth/pam/pam_stub.go +++ b/modules/auth/pam/pam_stub.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !pam -// +build !pam package pam diff --git a/modules/auth/pam/pam_test.go b/modules/auth/pam/pam_test.go index d6d78a748b7a..08565d2f312a 100644 --- a/modules/auth/pam/pam_test.go +++ b/modules/auth/pam/pam_test.go @@ -1,5 +1,4 @@ //go:build pam -// +build pam // Copyright 2021 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style diff --git a/modules/avatar/identicon/identicon_test.go b/modules/avatar/identicon/identicon_test.go index ee44c95139a7..44635fbb3bd5 100644 --- a/modules/avatar/identicon/identicon_test.go +++ b/modules/avatar/identicon/identicon_test.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build test_avatar_identicon -// +build test_avatar_identicon package identicon diff --git a/modules/cache/cache.go b/modules/cache/cache.go index 0198f8da7375..fd32aa153b01 100644 --- a/modules/cache/cache.go +++ b/modules/cache/cache.go @@ -5,6 +5,7 @@ package cache import ( + "errors" "fmt" "strconv" @@ -34,25 +35,37 @@ func NewContext() error { if conn, err = newCache(setting.CacheService.Cache); err != nil { return err } - const testKey = "__gitea_cache_test" - const testVal = "test-value" - if err = conn.Put(testKey, testVal, 10); err != nil { + if err = Ping(); err != nil { return err } - val := conn.Get(testKey) - if valStr, ok := val.(string); !ok || valStr != testVal { - // If the cache is full, the Get may not read the expected value stored by Put. - // Since we have checked that Put can success, so we just show a warning here, do not return an error to panic. - log.Warn("cache (adapter:%s, config:%s) doesn't seem to work correctly, set test value '%v' but get '%v'", - setting.CacheService.Cache.Adapter, setting.CacheService.Cache.Conn, - testVal, val, - ) - } } return err } +// Ping checks if the cache service works or not, it not, it returns an error +func Ping() error { + if conn == nil { + return errors.New("cache not available") + } + var err error + const testKey = "__gitea_cache_test" + const testVal = "test-value" + if err = conn.Put(testKey, testVal, 10); err != nil { + return err + } + val := conn.Get(testKey) + if valStr, ok := val.(string); !ok || valStr != testVal { + // If the cache is full, the Get may not read the expected value stored by Put. + // Since we have checked that Put can success, so we just show a warning here, do not return an error to panic. + log.Warn("cache (adapter:%s, config:%s) doesn't seem to work correctly, set test value '%v' but get '%v'", + setting.CacheService.Cache.Adapter, setting.CacheService.Cache.Conn, + testVal, val, + ) + } + return nil +} + // GetCache returns the currently configured cache func GetCache() mc.Cache { return conn diff --git a/modules/context/api.go b/modules/context/api.go index 41d559f5b1c5..20a3e05177cc 100644 --- a/modules/context/api.go +++ b/modules/context/api.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models/auth" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -247,6 +248,7 @@ func APIContexter() func(http.Handler) http.Handler { Resp: NewResponse(w), Data: map[string]interface{}{}, Locale: locale, + Cache: cache.GetCache(), Repo: &Repository{ PullRequest: &PullRequest{}, }, @@ -362,6 +364,7 @@ func RepoRefForAPI(next http.Handler) http.Handler { return } ctx.Repo.Commit = commit + ctx.Repo.TreePath = ctx.Params("*") return } diff --git a/modules/context/permission.go b/modules/context/permission.go index 142b86faeaf5..8dc3b3cd46ec 100644 --- a/modules/context/permission.go +++ b/modules/context/permission.go @@ -29,6 +29,16 @@ func RequireRepoWriter(unitType unit.Type) func(ctx *Context) { } } +// CanEnableEditor checks if the user is allowed to write to the branch of the repo +func CanEnableEditor() func(ctx *Context) { + return func(ctx *Context) { + if !ctx.Repo.Permission.CanWriteToBranch(ctx.Doer, ctx.Repo.BranchName) { + ctx.NotFound("CanWriteToBranch denies permission", nil) + return + } + } +} + // RequireRepoWriterOr returns a middleware for requiring repository write to one of the unit permission func RequireRepoWriterOr(unitTypes ...unit.Type) func(ctx *Context) { return func(ctx *Context) { diff --git a/modules/context/repo.go b/modules/context/repo.go index 4687434455a3..4a1e9aa9e818 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -78,8 +78,8 @@ type Repository struct { } // CanEnableEditor returns true if repository is editable and user has proper access level. -func (r *Repository) CanEnableEditor() bool { - return r.Permission.CanWrite(unit_model.TypeCode) && r.Repository.CanEnableEditor() && r.IsViewBranch && !r.Repository.IsArchived +func (r *Repository) CanEnableEditor(user *user_model.User) bool { + return r.IsViewBranch && r.Permission.CanWriteToBranch(user, r.BranchName) && r.Repository.CanEnableEditor() && !r.Repository.IsArchived } // CanCreateBranch returns true if repository is editable and user has proper access level. @@ -123,7 +123,7 @@ func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.Use sign, keyID, _, err := asymkey_service.SignCRUDAction(ctx, r.Repository.RepoPath(), doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName) - canCommit := r.CanEnableEditor() && userCanPush + canCommit := r.CanEnableEditor(doer) && userCanPush if requireSigned { canCommit = canCommit && sign } @@ -139,7 +139,7 @@ func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.Use return CanCommitToBranchResults{ CanCommitToBranch: canCommit, - EditorEnabled: r.CanEnableEditor(), + EditorEnabled: r.CanEnableEditor(doer), UserCanPush: userCanPush, RequireSigned: requireSigned, WillSign: sign, @@ -285,7 +285,7 @@ func RetrieveTemplateRepo(ctx *Context, repo *repo_model.Repository) { return } - perm, err := models.GetUserRepoPermission(templateRepo, ctx.Doer) + perm, err := models.GetUserRepoPermission(ctx, templateRepo, ctx.Doer) if err != nil { ctx.ServerError("GetUserRepoPermission", err) return @@ -351,7 +351,7 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) { return } - ctx.Repo.Permission, err = models.GetUserRepoPermission(repo, ctx.Doer) + ctx.Repo.Permission, err = models.GetUserRepoPermission(ctx, repo, ctx.Doer) if err != nil { ctx.ServerError("GetUserRepoPermission", err) return @@ -370,15 +370,24 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) { ctx.Data["Permission"] = &ctx.Repo.Permission if repo.IsMirror { - var err error - ctx.Repo.Mirror, err = repo_model.GetMirrorByRepoID(repo.ID) + + // Check if the mirror has finsihed migrationg, only then we can + // lookup the mirror informtation the database. + finishedMigrating, err := models.HasFinishedMigratingTask(repo.ID) if err != nil { - ctx.ServerError("GetMirrorByRepoID", err) + ctx.ServerError("HasFinishedMigratingTask", err) return } - ctx.Data["MirrorEnablePrune"] = ctx.Repo.Mirror.EnablePrune - ctx.Data["MirrorInterval"] = ctx.Repo.Mirror.Interval - ctx.Data["Mirror"] = ctx.Repo.Mirror + if finishedMigrating { + ctx.Repo.Mirror, err = repo_model.GetMirrorByRepoID(repo.ID) + if err != nil { + ctx.ServerError("GetMirrorByRepoID", err) + return + } + ctx.Data["MirrorEnablePrune"] = ctx.Repo.Mirror.EnablePrune + ctx.Data["MirrorInterval"] = ctx.Repo.Mirror.Interval + ctx.Data["Mirror"] = ctx.Repo.Mirror + } } pushMirrors, err := repo_model.GetPushMirrorsByRepoID(repo.ID) diff --git a/modules/convert/convert.go b/modules/convert/convert.go index 43300f603e55..3a12ed8f1f4f 100644 --- a/modules/convert/convert.go +++ b/modules/convert/convert.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" @@ -40,12 +41,19 @@ func ToEmail(email *user_model.EmailAddress) *api.Email { func ToBranch(repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *models.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) { if bp == nil { var hasPerm bool + var canPush bool var err error if user != nil { hasPerm, err = models.HasAccessUnit(user, repo, unit.TypeCode, perm.AccessModeWrite) if err != nil { return nil, err } + + perms, err := models.GetUserRepoPermission(db.DefaultContext, repo, user) + if err != nil { + return nil, err + } + canPush = perms.CanWriteToBranch(user, b.Name) } return &api.Branch{ @@ -55,7 +63,7 @@ func ToBranch(repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *mod RequiredApprovals: 0, EnableStatusCheck: false, StatusCheckContexts: []string{}, - UserCanPush: hasPerm, + UserCanPush: canPush, UserCanMerge: hasPerm, }, nil } @@ -74,12 +82,12 @@ func ToBranch(repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *mod } if user != nil { - permission, err := models.GetUserRepoPermission(repo, user) + permission, err := models.GetUserRepoPermission(db.DefaultContext, repo, user) if err != nil { return nil, err } branch.UserCanPush = bp.CanUserPush(user.ID) - branch.UserCanMerge = models.IsUserMergeWhitelisted(bp, user.ID, permission) + branch.UserCanMerge = models.IsUserMergeWhitelisted(db.DefaultContext, bp, user.ID, permission) } return branch, nil diff --git a/modules/convert/issue.go b/modules/convert/issue.go index 6cdb10f7daa6..bf116e2283cf 100644 --- a/modules/convert/issue.go +++ b/modules/convert/issue.go @@ -24,7 +24,7 @@ import ( // Required - Poster, Labels, // Optional - Milestone, Assignee, PullRequest func ToAPIIssue(issue *models.Issue) *api.Issue { - if err := issue.LoadLabels(); err != nil { + if err := issue.LoadLabels(db.DefaultContext); err != nil { return &api.Issue{} } if err := issue.LoadPoster(); err != nil { diff --git a/modules/convert/pull.go b/modules/convert/pull.go index 3b39e3d2c126..a2f54270e4ff 100644 --- a/modules/convert/pull.go +++ b/modules/convert/pull.go @@ -33,17 +33,17 @@ func ToAPIPullRequest(ctx context.Context, pr *models.PullRequest, doer *user_mo } apiIssue := ToAPIIssue(pr.Issue) - if err := pr.LoadBaseRepo(); err != nil { + if err := pr.LoadBaseRepoCtx(ctx); err != nil { log.Error("GetRepositoryById[%d]: %v", pr.ID, err) return nil } - if err := pr.LoadHeadRepo(); err != nil { + if err := pr.LoadHeadRepoCtx(ctx); err != nil { log.Error("GetRepositoryById[%d]: %v", pr.ID, err) return nil } - p, err := models.GetUserRepoPermission(pr.BaseRepo, doer) + p, err := models.GetUserRepoPermission(ctx, pr.BaseRepo, doer) if err != nil { log.Error("GetUserRepoPermission[%d]: %v", pr.BaseRepoID, err) p.AccessMode = perm.AccessModeNone @@ -73,6 +73,8 @@ func ToAPIPullRequest(ctx context.Context, pr *models.PullRequest, doer *user_mo Created: pr.Issue.CreatedUnix.AsTimePtr(), Updated: pr.Issue.UpdatedUnix.AsTimePtr(), + AllowMaintainerEdit: pr.AllowMaintainerEdit, + Base: &api.PRBranchInfo{ Name: pr.BaseBranch, Ref: pr.BaseBranch, @@ -130,7 +132,7 @@ func ToAPIPullRequest(ctx context.Context, pr *models.PullRequest, doer *user_mo } if pr.HeadRepo != nil && pr.Flow == models.PullRequestFlowGithub { - p, err := models.GetUserRepoPermission(pr.HeadRepo, doer) + p, err := models.GetUserRepoPermission(ctx, pr.HeadRepo, doer) if err != nil { log.Error("GetUserRepoPermission[%d]: %v", pr.HeadRepoID, err) p.AccessMode = perm.AccessModeNone diff --git a/modules/convert/user.go b/modules/convert/user.go index dc4a8c49c776..2b07d21838d7 100644 --- a/modules/convert/user.go +++ b/modules/convert/user.go @@ -95,3 +95,12 @@ func User2UserSettings(user *user_model.User) api.UserSettings { DiffViewStyle: user.DiffViewStyle, } } + +// ToUserAndPermission return User and its collaboration permission for a repository +func ToUserAndPermission(user, doer *user_model.User, accessMode perm.AccessMode) api.RepoCollaboratorPermission { + return api.RepoCollaboratorPermission{ + User: ToUser(user, doer), + Permission: accessMode.String(), + RoleName: accessMode.String(), + } +} diff --git a/modules/doctor/authorizedkeys.go b/modules/doctor/authorizedkeys.go index 3eb931e6f696..18e7a3cbf476 100644 --- a/modules/doctor/authorizedkeys.go +++ b/modules/doctor/authorizedkeys.go @@ -72,8 +72,8 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e "authorized_keys file %q is out of date.\nRegenerate it with:\n\t\"%s\"\nor\n\t\"%s\"", fPath, "gitea admin regenerate keys", - "gitea doctor --run authorized_keys --fix") - return fmt.Errorf(`authorized_keys is out of date and should be regenerated with "gitea admin regenerate keys" or "gitea doctor --run authorized_keys --fix"`) + "gitea doctor --run authorized-keys --fix") + return fmt.Errorf(`authorized_keys is out of date and should be regenerated with "gitea admin regenerate keys" or "gitea doctor --run authorized-keys --fix"`) } logger.Warn("authorized_keys is out of date. Attempting rewrite...") err = asymkey_model.RewriteAllPublicKeys() diff --git a/modules/eventsource/manager_run.go b/modules/eventsource/manager_run.go index 9af5c9e78ac1..127979ad6345 100644 --- a/modules/eventsource/manager_run.go +++ b/modules/eventsource/manager_run.go @@ -9,7 +9,9 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/graceful" + "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" @@ -80,6 +82,31 @@ loop: }) } then = now + + if setting.Service.EnableTimetracking { + usersStopwatches, err := models.GetUIDsAndStopwatch() + if err != nil { + log.Error("Unable to get GetUIDsAndStopwatch: %v", err) + return + } + + for _, userStopwatches := range usersStopwatches { + apiSWs, err := convert.ToStopWatches(userStopwatches.StopWatches) + if err != nil { + log.Error("Unable to APIFormat stopwatches: %v", err) + continue + } + dataBs, err := json.Marshal(apiSWs) + if err != nil { + log.Error("Unable to marshal stopwatches: %v", err) + continue + } + m.SendMessage(userStopwatches.UserID, &Event{ + Name: "stopwatches", + Data: string(dataBs), + }) + } + } } } m.UnregisterAll() diff --git a/modules/git/blob_gogit.go b/modules/git/blob_gogit.go index ef7a90c3f40b..2a2b51e422f0 100644 --- a/modules/git/blob_gogit.go +++ b/modules/git/blob_gogit.go @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/blob_nogogit.go b/modules/git/blob_nogogit.go index aabf1b34aded..211c18855972 100644 --- a/modules/git/blob_nogogit.go +++ b/modules/git/blob_nogogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/git/command_race_test.go b/modules/git/command_race_test.go index 9eb29fcfab5a..ae2acc3a5add 100644 --- a/modules/git/command_race_test.go +++ b/modules/git/command_race_test.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build race -// +build race package git diff --git a/modules/git/commit_convert_gogit.go b/modules/git/commit_convert_gogit.go index b328b3c0edfb..bb9d3bf8cef9 100644 --- a/modules/git/commit_convert_gogit.go +++ b/modules/git/commit_convert_gogit.go @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/commit_info_gogit.go b/modules/git/commit_info_gogit.go index ab6e73810367..91a1804db5cf 100644 --- a/modules/git/commit_info_gogit.go +++ b/modules/git/commit_info_gogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/commit_info_nogogit.go b/modules/git/commit_info_nogogit.go index 347ad7d05900..f430c672f884 100644 --- a/modules/git/commit_info_nogogit.go +++ b/modules/git/commit_info_nogogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/git/commit_test.go b/modules/git/commit_test.go index aac7de5d46c9..fb8c22dfd357 100644 --- a/modules/git/commit_test.go +++ b/modules/git/commit_test.go @@ -67,6 +67,7 @@ empty commit` gitRepo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare")) assert.NoError(t, err) assert.NotNil(t, gitRepo) + defer gitRepo.Close() commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString)) assert.NoError(t, err) @@ -111,6 +112,7 @@ func TestHasPreviousCommit(t *testing.T) { repo, err := openRepositoryWithDefaultContext(bareRepo1Path) assert.NoError(t, err) + defer repo.Close() commit, err := repo.GetCommit("8006ff9adbf0cb94da7dad9e537e53817f9fa5c0") assert.NoError(t, err) diff --git a/modules/git/diff.go b/modules/git/diff.go index e11f63cabd6c..c9d68bb130fe 100644 --- a/modules/git/diff.go +++ b/modules/git/diff.go @@ -28,8 +28,8 @@ const ( ) // GetRawDiff dumps diff results of repository in given commit ID to io.Writer. -func GetRawDiff(ctx context.Context, repoPath, commitID string, diffType RawDiffType, writer io.Writer) error { - return GetRawDiffForFile(ctx, repoPath, "", commitID, diffType, "", writer) +func GetRawDiff(repo *Repository, commitID string, diffType RawDiffType, writer io.Writer) error { + return GetRepoRawDiffForFile(repo, "", commitID, diffType, "", writer) } // GetReverseRawDiff dumps the reverse diff results of repository in given commit ID to io.Writer. @@ -46,17 +46,6 @@ func GetReverseRawDiff(ctx context.Context, repoPath, commitID string, writer io return nil } -// GetRawDiffForFile dumps diff results of file in given commit ID to io.Writer. -func GetRawDiffForFile(ctx context.Context, repoPath, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error { - repo, closer, err := RepositoryFromContextOrOpen(ctx, repoPath) - if err != nil { - return fmt.Errorf("RepositoryFromContextOrOpen: %v", err) - } - defer closer.Close() - - return GetRepoRawDiffForFile(repo, startCommit, endCommit, diffType, file, writer) -} - // GetRepoRawDiffForFile dumps diff results of file in given commit ID to io.Writer according given repository func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error { commit, err := repo.GetCommit(endCommit) diff --git a/modules/git/git.go b/modules/git/git.go index 259759ca81c7..8fad07033006 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -8,6 +8,7 @@ package git import ( "context" "fmt" + "os" "os/exec" "runtime" "strings" @@ -20,10 +21,11 @@ import ( ) var ( - // Prefix the log prefix - Prefix = "[git-module] " // GitVersionRequired is the minimum Git version required - GitVersionRequired = "1.7.2" + // At the moment, all code for git 1.x are not changed, if some users want to test with old git client + // or bypass the check, they still have a chance to edit this variable manually. + // If everything works fine, the code for git 1.x could be removed in a separate PR before 1.17 frozen. + GitVersionRequired = "2.0.0" // GitExecutable is the command name of git // Could be updated to an absolute path while initialization @@ -87,13 +89,13 @@ func SetExecutablePath(path string) error { } absPath, err := exec.LookPath(GitExecutable) if err != nil { - return fmt.Errorf("Git not found: %v", err) + return fmt.Errorf("git not found: %w", err) } GitExecutable = absPath err = LoadGitVersion() if err != nil { - return fmt.Errorf("Git version missing: %v", err) + return fmt.Errorf("unable to load git version: %w", err) } versionRequired, err := version.NewVersion(GitVersionRequired) @@ -102,7 +104,15 @@ func SetExecutablePath(path string) error { } if gitVersion.LessThan(versionRequired) { - return fmt.Errorf("Git version not supported. Requires version > %v", GitVersionRequired) + moreHint := "get git: https://git-scm.com/download/" + if runtime.GOOS == "linux" { + // there are a lot of CentOS/RHEL users using old git, so we add a special hint for them + if _, err = os.Stat("/etc/redhat-release"); err == nil { + // ius.io is the recommended official(git-scm.com) method to install git + moreHint = "get git: https://git-scm.com/download/linux and https://ius.io" + } + } + return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", gitVersion.Original(), GitVersionRequired, moreHint) } return nil diff --git a/modules/git/last_commit_cache_gogit.go b/modules/git/last_commit_cache_gogit.go index 06e85a6db2a6..8897000350db 100644 --- a/modules/git/last_commit_cache_gogit.go +++ b/modules/git/last_commit_cache_gogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/last_commit_cache_nogogit.go b/modules/git/last_commit_cache_nogogit.go index 5315c0a152d4..030d5486b630 100644 --- a/modules/git/last_commit_cache_nogogit.go +++ b/modules/git/last_commit_cache_nogogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/git/notes_gogit.go b/modules/git/notes_gogit.go index b1e5e453e4fb..76bc828957b3 100644 --- a/modules/git/notes_gogit.go +++ b/modules/git/notes_gogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/notes_nogogit.go b/modules/git/notes_nogogit.go index bbc8ee1371da..e3f0a3fee9ec 100644 --- a/modules/git/notes_nogogit.go +++ b/modules/git/notes_nogogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/git/parse_gogit.go b/modules/git/parse_gogit.go index c42e32929e45..409432c5d6a8 100644 --- a/modules/git/parse_gogit.go +++ b/modules/git/parse_gogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/parse_gogit_test.go b/modules/git/parse_gogit_test.go index c27f5172d5e4..075de6d25def 100644 --- a/modules/git/parse_gogit_test.go +++ b/modules/git/parse_gogit_test.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/parse_nogogit.go b/modules/git/parse_nogogit.go index dd5554b5dd74..6dc490099286 100644 --- a/modules/git/parse_nogogit.go +++ b/modules/git/parse_nogogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/git/parse_nogogit_test.go b/modules/git/parse_nogogit_test.go index d6d6f3868c06..483f96e9a7fd 100644 --- a/modules/git/parse_nogogit_test.go +++ b/modules/git/parse_nogogit_test.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/git/pipeline/lfs.go b/modules/git/pipeline/lfs.go index 1b64b672e458..18cce342895b 100644 --- a/modules/git/pipeline/lfs.go +++ b/modules/git/pipeline/lfs.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package pipeline diff --git a/modules/git/pipeline/lfs_nogogit.go b/modules/git/pipeline/lfs_nogogit.go index 31c10c6002f6..a2b5dd0c9698 100644 --- a/modules/git/pipeline/lfs_nogogit.go +++ b/modules/git/pipeline/lfs_nogogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package pipeline diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go index 6481474a9640..a18c80c3f12f 100644 --- a/modules/git/repo_attribute.go +++ b/modules/git/repo_attribute.go @@ -124,12 +124,10 @@ type CheckAttributeReader struct { env []string ctx context.Context cancel context.CancelFunc - running chan struct{} } // Init initializes the cmd func (c *CheckAttributeReader) Init(ctx context.Context) error { - c.running = make(chan struct{}) cmdArgs := []string{"check-attr", "--stdin", "-z"} if len(c.IndexFile) > 0 && CheckGitVersionAtLeast("1.7.8") == nil { @@ -194,14 +192,6 @@ func (c *CheckAttributeReader) Run() error { Stdin: c.stdinReader, Stdout: c.stdOut, Stderr: stdErr, - PipelineFunc: func(_ context.Context, _ context.CancelFunc) error { - select { - case <-c.running: - default: - close(c.running) - } - return nil - }, }) if err != nil && // If there is an error we need to return but: c.ctx.Err() != err && // 1. Ignore the context error if the context is cancelled or exceeds the deadline (RunWithContext could return c.ctx.Err() which is Canceled or DeadlineExceeded) @@ -222,7 +212,7 @@ func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err select { case <-c.ctx.Done(): return nil, c.ctx.Err() - case <-c.running: + default: } if _, err = c.stdinWriter.Write([]byte(path + "\x00")); err != nil { @@ -249,11 +239,6 @@ func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err func (c *CheckAttributeReader) Close() error { c.cancel() err := c.stdinWriter.Close() - select { - case <-c.running: - default: - close(c.running) - } return err } diff --git a/modules/git/repo_base_gogit.go b/modules/git/repo_base_gogit.go index 059f75fb3c0e..cd2ca25dfbad 100644 --- a/modules/git/repo_base_gogit.go +++ b/modules/git/repo_base_gogit.go @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/repo_base_nogogit.go b/modules/git/repo_base_nogogit.go index c4a0e82c89df..df24d952a8f7 100644 --- a/modules/git/repo_base_nogogit.go +++ b/modules/git/repo_base_nogogit.go @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/git/repo_blob_gogit.go b/modules/git/repo_blob_gogit.go index b11e9f58fe9b..5640011f4aec 100644 --- a/modules/git/repo_blob_gogit.go +++ b/modules/git/repo_blob_gogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/repo_blob_nogogit.go b/modules/git/repo_blob_nogogit.go index 775b3835dd5d..44ba0a36b1b1 100644 --- a/modules/git/repo_blob_nogogit.go +++ b/modules/git/repo_blob_nogogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/git/repo_branch_gogit.go b/modules/git/repo_branch_gogit.go index bf3eb857b95f..ecedb56686d9 100644 --- a/modules/git/repo_branch_gogit.go +++ b/modules/git/repo_branch_gogit.go @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/repo_branch_nogogit.go b/modules/git/repo_branch_nogogit.go index 4393db10f950..3aed4abdf35b 100644 --- a/modules/git/repo_branch_nogogit.go +++ b/modules/git/repo_branch_nogogit.go @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/git/repo_commit_gogit.go b/modules/git/repo_commit_gogit.go index f3504f25d870..9333b0d7b756 100644 --- a/modules/git/repo_commit_gogit.go +++ b/modules/git/repo_commit_gogit.go @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/repo_commit_nogogit.go b/modules/git/repo_commit_nogogit.go index c9afe35b1a2e..e528af0ffb88 100644 --- a/modules/git/repo_commit_nogogit.go +++ b/modules/git/repo_commit_nogogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/git/repo_commitgraph_gogit.go b/modules/git/repo_commitgraph_gogit.go index 84a2edb664bb..6b00a4fdc43a 100644 --- a/modules/git/repo_commitgraph_gogit.go +++ b/modules/git/repo_commitgraph_gogit.go @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/repo_language_stats_gogit.go b/modules/git/repo_language_stats_gogit.go index 037ec41ec6c7..3c9f026b7ace 100644 --- a/modules/git/repo_language_stats_gogit.go +++ b/modules/git/repo_language_stats_gogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/repo_language_stats_nogogit.go b/modules/git/repo_language_stats_nogogit.go index adb11dd8fa6a..41b176f81691 100644 --- a/modules/git/repo_language_stats_nogogit.go +++ b/modules/git/repo_language_stats_nogogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/git/repo_language_stats_test.go b/modules/git/repo_language_stats_test.go index 6a4e23f99b0f..0234c77c0bea 100644 --- a/modules/git/repo_language_stats_test.go +++ b/modules/git/repo_language_stats_test.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/git/repo_ref_gogit.go b/modules/git/repo_ref_gogit.go index 9f0e11366f67..d11c58e005eb 100644 --- a/modules/git/repo_ref_gogit.go +++ b/modules/git/repo_ref_gogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/repo_ref_nogogit.go b/modules/git/repo_ref_nogogit.go index 40e8a247c748..d766a8cac316 100644 --- a/modules/git/repo_ref_nogogit.go +++ b/modules/git/repo_ref_nogogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/git/repo_tag_gogit.go b/modules/git/repo_tag_gogit.go index 5c87e914c063..c6dec2898784 100644 --- a/modules/git/repo_tag_gogit.go +++ b/modules/git/repo_tag_gogit.go @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/repo_tag_nogogit.go b/modules/git/repo_tag_nogogit.go index f2da7d88572b..8d44db0a2e11 100644 --- a/modules/git/repo_tag_nogogit.go +++ b/modules/git/repo_tag_nogogit.go @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/git/repo_tree_gogit.go b/modules/git/repo_tree_gogit.go index cc156ea916c3..eef09cddd6a5 100644 --- a/modules/git/repo_tree_gogit.go +++ b/modules/git/repo_tree_gogit.go @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/repo_tree_nogogit.go b/modules/git/repo_tree_nogogit.go index 00009c997dfe..dc4a5becb9df 100644 --- a/modules/git/repo_tree_nogogit.go +++ b/modules/git/repo_tree_nogogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/git/sha1_gogit.go b/modules/git/sha1_gogit.go index 30290f14b738..16501efb43fc 100644 --- a/modules/git/sha1_gogit.go +++ b/modules/git/sha1_gogit.go @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/sha1_nogogit.go b/modules/git/sha1_nogogit.go index 53665fc9217d..1835c68f5a13 100644 --- a/modules/git/sha1_nogogit.go +++ b/modules/git/sha1_nogogit.go @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/git/signature_gogit.go b/modules/git/signature_gogit.go index 903a48133f80..fe81cd97df3b 100644 --- a/modules/git/signature_gogit.go +++ b/modules/git/signature_gogit.go @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/signature_nogogit.go b/modules/git/signature_nogogit.go index c6fe8e6d1a99..81da739a5b4d 100644 --- a/modules/git/signature_nogogit.go +++ b/modules/git/signature_nogogit.go @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/git/tree_blob_gogit.go b/modules/git/tree_blob_gogit.go index be7cb33d353a..bb010b58834d 100644 --- a/modules/git/tree_blob_gogit.go +++ b/modules/git/tree_blob_gogit.go @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/tree_blob_nogogit.go b/modules/git/tree_blob_nogogit.go index df23ff01b474..3770004d6d71 100644 --- a/modules/git/tree_blob_nogogit.go +++ b/modules/git/tree_blob_nogogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/git/tree_entry_gogit.go b/modules/git/tree_entry_gogit.go index 20e767eea1f7..2b2992c32a51 100644 --- a/modules/git/tree_entry_gogit.go +++ b/modules/git/tree_entry_gogit.go @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/tree_entry_nogogit.go b/modules/git/tree_entry_nogogit.go index 076062e1d678..aff67a3b22f5 100644 --- a/modules/git/tree_entry_nogogit.go +++ b/modules/git/tree_entry_nogogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/git/tree_entry_test.go b/modules/git/tree_entry_test.go index 9347e10b1b41..c512c7348e8d 100644 --- a/modules/git/tree_entry_test.go +++ b/modules/git/tree_entry_test.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/tree_gogit.go b/modules/git/tree_gogit.go index bc020883660d..54f8e140fbd0 100644 --- a/modules/git/tree_gogit.go +++ b/modules/git/tree_gogit.go @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package git diff --git a/modules/git/tree_nogogit.go b/modules/git/tree_nogogit.go index f852c5a51e65..7defb064a47d 100644 --- a/modules/git/tree_nogogit.go +++ b/modules/git/tree_nogogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package git diff --git a/modules/graceful/manager_unix.go b/modules/graceful/manager_unix.go index b22b7b5860ae..9d3816e9c2a7 100644 --- a/modules/graceful/manager_unix.go +++ b/modules/graceful/manager_unix.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !windows -// +build !windows package graceful diff --git a/modules/graceful/manager_windows.go b/modules/graceful/manager_windows.go index 66baddfa38ed..e7e619f53f61 100644 --- a/modules/graceful/manager_windows.go +++ b/modules/graceful/manager_windows.go @@ -4,7 +4,6 @@ // This code is heavily inspired by the archived gofacebook/gracenet/net.go handler //go:build windows -// +build windows package graceful diff --git a/modules/graceful/net_unix.go b/modules/graceful/net_unix.go index 6ffa8150cc1a..680ff529af88 100644 --- a/modules/graceful/net_unix.go +++ b/modules/graceful/net_unix.go @@ -4,7 +4,6 @@ // This code is heavily inspired by the archived gofacebook/gracenet/net.go handler //go:build !windows -// +build !windows package graceful diff --git a/modules/graceful/net_windows.go b/modules/graceful/net_windows.go index 35b7a9d1feae..07ae51b8dd38 100644 --- a/modules/graceful/net_windows.go +++ b/modules/graceful/net_windows.go @@ -4,7 +4,6 @@ // This code is heavily inspired by the archived gofacebook/gracenet/net.go handler //go:build windows -// +build windows package graceful diff --git a/modules/graceful/restart_unix.go b/modules/graceful/restart_unix.go index 9969e007c300..2654ddfb94d8 100644 --- a/modules/graceful/restart_unix.go +++ b/modules/graceful/restart_unix.go @@ -4,7 +4,6 @@ // This code is heavily inspired by the archived gofacebook/gracenet/net.go handler //go:build !windows -// +build !windows package graceful diff --git a/modules/hostmatcher/hostmatcher.go b/modules/hostmatcher/hostmatcher.go index 6c5c2a74d98c..00bbc6cb0a83 100644 --- a/modules/hostmatcher/hostmatcher.go +++ b/modules/hostmatcher/hostmatcher.go @@ -125,13 +125,18 @@ func (hl *HostMatchList) checkIP(ip net.IP) bool { // MatchHostName checks if the host matches an allow/deny(block) list func (hl *HostMatchList) MatchHostName(host string) bool { + hostname, _, err := net.SplitHostPort(host) + if err != nil { + hostname = host + } + if hl == nil { return false } - if hl.checkPattern(host) { + if hl.checkPattern(hostname) { return true } - if ip := net.ParseIP(host); ip != nil { + if ip := net.ParseIP(hostname); ip != nil { return hl.checkIP(ip) } return false diff --git a/modules/hostmatcher/hostmatcher_test.go b/modules/hostmatcher/hostmatcher_test.go index 66030a32f1ed..b93976df6a61 100644 --- a/modules/hostmatcher/hostmatcher_test.go +++ b/modules/hostmatcher/hostmatcher_test.go @@ -38,6 +38,7 @@ func TestHostOrIPMatchesList(t *testing.T) { {"", net.ParseIP("10.0.1.1"), true}, {"10.0.1.1", nil, true}, + {"10.0.1.1:8080", nil, true}, {"", net.ParseIP("192.168.1.1"), true}, {"192.168.1.1", nil, true}, {"", net.ParseIP("fd00::1"), true}, @@ -48,6 +49,7 @@ func TestHostOrIPMatchesList(t *testing.T) { {"mydomain.com", net.IPv4zero, false}, {"sub.mydomain.com", net.IPv4zero, true}, + {"sub.mydomain.com:8080", net.IPv4zero, true}, {"", net.ParseIP("169.254.1.1"), true}, {"169.254.1.1", nil, true}, diff --git a/modules/indexer/code/indexer.go b/modules/indexer/code/indexer.go index 3ead3261e9c0..f15b8d8651c1 100644 --- a/modules/indexer/code/indexer.go +++ b/modules/indexer/code/indexer.go @@ -133,7 +133,7 @@ func Init() { finished() }) - waitChannel := make(chan time.Duration) + waitChannel := make(chan time.Duration, 1) // Create the Queue switch setting.Indexer.RepoType { diff --git a/modules/indexer/issues/indexer.go b/modules/indexer/issues/indexer.go index 1343b0bddd37..d4df4f8a4f8e 100644 --- a/modules/indexer/issues/indexer.go +++ b/modules/indexer/issues/indexer.go @@ -104,7 +104,7 @@ var ( func InitIssueIndexer(syncReindex bool) { ctx, _, finished := process.GetManager().AddTypedContext(context.Background(), "Service: IssueIndexer", process.SystemProcessType, false) - waitChannel := make(chan time.Duration) + waitChannel := make(chan time.Duration, 1) // Create the Queue switch setting.Indexer.IssueType { @@ -321,7 +321,7 @@ func populateIssueIndexer(ctx context.Context) { // UpdateRepoIndexer add/update all issues of the repositories func UpdateRepoIndexer(repo *repo_model.Repository) { is, err := models.Issues(&models.IssuesOptions{ - RepoIDs: []int64{repo.ID}, + RepoID: repo.ID, IsClosed: util.OptionalBoolNone, IsPull: util.OptionalBoolNone, }) diff --git a/modules/lfs/pointer_scanner_gogit.go b/modules/lfs/pointer_scanner_gogit.go index b4ba6fc1335f..ed27cb1f55b7 100644 --- a/modules/lfs/pointer_scanner_gogit.go +++ b/modules/lfs/pointer_scanner_gogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build gogit -// +build gogit package lfs diff --git a/modules/lfs/pointer_scanner_nogogit.go b/modules/lfs/pointer_scanner_nogogit.go index cdf88c51b009..d17f1f7b98e6 100644 --- a/modules/lfs/pointer_scanner_nogogit.go +++ b/modules/lfs/pointer_scanner_nogogit.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !gogit -// +build !gogit package lfs diff --git a/modules/log/console_other.go b/modules/log/console_other.go new file mode 100644 index 000000000000..b5cac55b52a4 --- /dev/null +++ b/modules/log/console_other.go @@ -0,0 +1,21 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +//go:build !windows + +package log + +import ( + "os" + + "github.com/mattn/go-isatty" +) + +func init() { + // when running gitea as a systemd unit with logging set to console, the output can not be colorized, + // otherwise it spams the journal / syslog with escape sequences like "#033[0m#033[32mcmd/web.go:102:#033[32m" + // this file covers non-windows platforms. + CanColorStdout = isatty.IsTerminal(os.Stdout.Fd()) + CanColorStderr = isatty.IsTerminal(os.Stderr.Fd()) +} diff --git a/modules/migration/schemas_bindata.go b/modules/migration/schemas_bindata.go index d0fef698b425..febe0f75c0eb 100644 --- a/modules/migration/schemas_bindata.go +++ b/modules/migration/schemas_bindata.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build bindata -// +build bindata package migration diff --git a/modules/migration/schemas_dynamic.go b/modules/migration/schemas_dynamic.go index c883fafe9870..1b767b2e725c 100644 --- a/modules/migration/schemas_dynamic.go +++ b/modules/migration/schemas_dynamic.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !bindata -// +build !bindata package migration diff --git a/modules/migration/schemas_static.go b/modules/migration/schemas_static.go index 10c83b313a26..02957fc4edb1 100644 --- a/modules/migration/schemas_static.go +++ b/modules/migration/schemas_static.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build bindata -// +build bindata package migration diff --git a/modules/notification/mail/mail.go b/modules/notification/mail/mail.go index 6636d18b99c0..138e438751dd 100644 --- a/modules/notification/mail/mail.go +++ b/modules/notification/mail/mail.go @@ -169,7 +169,7 @@ func (m *mailNotifier) NotifyPullRequestPushCommits(doer *user_model.User, pr *m log.Error("comment.Issue.LoadPullRequest: %v", err) return } - if err = comment.Issue.PullRequest.LoadBaseRepo(); err != nil { + if err = comment.Issue.PullRequest.LoadBaseRepoCtx(ctx); err != nil { log.Error("comment.Issue.PullRequest.LoadBaseRepo: %v", err) return } diff --git a/modules/options/dynamic.go b/modules/options/dynamic.go index e1b9353c3357..5fea337e4203 100644 --- a/modules/options/dynamic.go +++ b/modules/options/dynamic.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !bindata -// +build !bindata package options diff --git a/modules/options/options_bindata.go b/modules/options/options_bindata.go index 921e15ab382a..77b7a7ef419b 100644 --- a/modules/options/options_bindata.go +++ b/modules/options/options_bindata.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build bindata -// +build bindata package options diff --git a/modules/options/static.go b/modules/options/static.go index 5b61e58f8fc9..6cad88cb61bb 100644 --- a/modules/options/static.go +++ b/modules/options/static.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build bindata -// +build bindata package options diff --git a/modules/public/public_bindata.go b/modules/public/public_bindata.go index 25c3c0d2a1a8..fe250c645430 100644 --- a/modules/public/public_bindata.go +++ b/modules/public/public_bindata.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build bindata -// +build bindata package public diff --git a/modules/public/serve_dynamic.go b/modules/public/serve_dynamic.go index 955c01e51031..672924a63658 100644 --- a/modules/public/serve_dynamic.go +++ b/modules/public/serve_dynamic.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !bindata -// +build !bindata package public diff --git a/modules/public/serve_static.go b/modules/public/serve_static.go index 8e82175e39ca..9666880adfe1 100644 --- a/modules/public/serve_static.go +++ b/modules/public/serve_static.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build bindata -// +build bindata package public diff --git a/modules/queue/workerpool.go b/modules/queue/workerpool.go index 2d8504598a1a..bdf04a363b79 100644 --- a/modules/queue/workerpool.go +++ b/modules/queue/workerpool.go @@ -22,6 +22,10 @@ import ( // they use to detect if there is a block and will grow and shrink in // response to demand as per configuration. type WorkerPool struct { + // This field requires to be the first one in the struct. + // This is to allow 64 bit atomic operations on 32-bit machines. + // See: https://pkg.go.dev/sync/atomic#pkg-note-BUG & Gitea issue 19518 + numInQueue int64 lock sync.Mutex baseCtx context.Context baseCtxCancel context.CancelFunc @@ -38,7 +42,6 @@ type WorkerPool struct { blockTimeout time.Duration boostTimeout time.Duration boostWorkers int - numInQueue int64 } var ( diff --git a/modules/repository/repo.go b/modules/repository/repo.go index 0dffa322d056..30ca6fdff84e 100644 --- a/modules/repository/repo.go +++ b/modules/repository/repo.go @@ -93,7 +93,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, return repo, fmt.Errorf("Failed to remove %s: %v", wikiPath, err) } - if err = git.Clone(ctx, wikiRemotePath, wikiPath, git.CloneRepoOptions{ + if err := git.Clone(ctx, wikiRemotePath, wikiPath, git.CloneRepoOptions{ Mirror: true, Quiet: true, Timeout: migrateTimeout, @@ -104,11 +104,12 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, if err := util.RemoveAll(wikiPath); err != nil { return repo, fmt.Errorf("Failed to remove %s: %v", wikiPath, err) } + } else { + if err := git.WriteCommitGraph(ctx, wikiPath); err != nil { + return repo, err + } } } - if err := git.WriteCommitGraph(ctx, wikiPath); err != nil { - return repo, err - } } if repo.OwnerID == u.ID { diff --git a/modules/setting/database_sqlite.go b/modules/setting/database_sqlite.go index 12c60cc86cc9..1f18868d8ea4 100644 --- a/modules/setting/database_sqlite.go +++ b/modules/setting/database_sqlite.go @@ -1,5 +1,4 @@ //go:build sqlite -// +build sqlite // Copyright 2014 The Gogs Authors. All rights reserved. // Use of this source code is governed by a MIT-style diff --git a/modules/setting/federation.go b/modules/setting/federation.go index c3000607894e..fd39e5c7c2af 100644 --- a/modules/setting/federation.go +++ b/modules/setting/federation.go @@ -9,9 +9,11 @@ import "code.gitea.io/gitea/modules/log" // Federation settings var ( Federation = struct { - Enabled bool + Enabled bool + ShareUserStatistics bool }{ - Enabled: true, + Enabled: true, + ShareUserStatistics: true, } ) diff --git a/modules/setting/log.go b/modules/setting/log.go index e666e2a02746..008a419b09d4 100644 --- a/modules/setting/log.go +++ b/modules/setting/log.go @@ -32,9 +32,8 @@ func GetLogDescriptions() map[string]*LogDescription { descs := make(map[string]*LogDescription, len(logDescriptions)) for k, v := range logDescriptions { subLogDescriptions := make([]SubLogDescription, len(v.SubLogDescriptions)) - for i, s := range v.SubLogDescriptions { - subLogDescriptions[i] = s - } + copy(subLogDescriptions, v.SubLogDescriptions) + descs[k] = &LogDescription{ Name: v.Name, SubLogDescriptions: subLogDescriptions, diff --git a/modules/structs/admin_user.go b/modules/structs/admin_user.go index facf16a39552..eccbf29a46f0 100644 --- a/modules/structs/admin_user.go +++ b/modules/structs/admin_user.go @@ -19,6 +19,7 @@ type CreateUserOption struct { Password string `json:"password" binding:"Required;MaxSize(255)"` MustChangePassword *bool `json:"must_change_password"` SendNotify bool `json:"send_notify"` + Restricted *bool `json:"restricted"` Visibility string `json:"visibility" binding:"In(,public,limited,private)"` } diff --git a/modules/structs/pull.go b/modules/structs/pull.go index 653091b2f432..b63b3edfd30a 100644 --- a/modules/structs/pull.go +++ b/modules/structs/pull.go @@ -31,9 +31,10 @@ type PullRequest struct { Mergeable bool `json:"mergeable"` HasMerged bool `json:"merged"` // swagger:strfmt date-time - Merged *time.Time `json:"merged_at"` - MergedCommitID *string `json:"merge_commit_sha"` - MergedBy *User `json:"merged_by"` + Merged *time.Time `json:"merged_at"` + MergedCommitID *string `json:"merge_commit_sha"` + MergedBy *User `json:"merged_by"` + AllowMaintainerEdit bool `json:"allow_maintainer_edit"` Base *PRBranchInfo `json:"base"` Head *PRBranchInfo `json:"head"` @@ -90,6 +91,7 @@ type EditPullRequestOption struct { Labels []int64 `json:"labels"` State *string `json:"state"` // swagger:strfmt date-time - Deadline *time.Time `json:"due_date"` - RemoveDeadline *bool `json:"unset_due_date"` + Deadline *time.Time `json:"due_date"` + RemoveDeadline *bool `json:"unset_due_date"` + AllowMaintainerEdit *bool `json:"allow_maintainer_edit"` } diff --git a/modules/structs/repo_collaborator.go b/modules/structs/repo_collaborator.go index 2b4fa390d2b2..2f9c8992a168 100644 --- a/modules/structs/repo_collaborator.go +++ b/modules/structs/repo_collaborator.go @@ -8,3 +8,10 @@ package structs type AddCollaboratorOption struct { Permission *string `json:"permission"` } + +// RepoCollaboratorPermission to get repository permission for a collaborator +type RepoCollaboratorPermission struct { + Permission string `json:"permission"` + RoleName string `json:"role_name"` + User *User `json:"user"` +} diff --git a/modules/structs/repo_file.go b/modules/structs/repo_file.go index e2947bf7ac7b..135e6484cd69 100644 --- a/modules/structs/repo_file.go +++ b/modules/structs/repo_file.go @@ -30,6 +30,11 @@ type CreateFileOptions struct { Content string `json:"content"` } +// Branch returns branch name +func (o *CreateFileOptions) Branch() string { + return o.FileOptions.BranchName +} + // DeleteFileOptions options for deleting files (used for other File structs below) // Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) type DeleteFileOptions struct { @@ -39,6 +44,11 @@ type DeleteFileOptions struct { SHA string `json:"sha" binding:"Required"` } +// Branch returns branch name +func (o *DeleteFileOptions) Branch() string { + return o.FileOptions.BranchName +} + // UpdateFileOptions options for updating files // Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) type UpdateFileOptions struct { @@ -50,6 +60,16 @@ type UpdateFileOptions struct { FromPath string `json:"from_path" binding:"MaxSize(500)"` } +// Branch returns branch name +func (o *UpdateFileOptions) Branch() string { + return o.FileOptions.BranchName +} + +// FileOptionInterface provides a unified interface for the different file options +type FileOptionInterface interface { + Branch() string +} + // ApplyDiffPatchFileOptions options for applying a diff patch // Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) type ApplyDiffPatchFileOptions struct { diff --git a/modules/svg/discover_bindata.go b/modules/svg/discover_bindata.go index e11951ff7ec9..cca1de76a7d9 100644 --- a/modules/svg/discover_bindata.go +++ b/modules/svg/discover_bindata.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build bindata -// +build bindata package svg diff --git a/modules/svg/discover_nobindata.go b/modules/svg/discover_nobindata.go index e3f13ddf6c53..ef01fbcc3e85 100644 --- a/modules/svg/discover_nobindata.go +++ b/modules/svg/discover_nobindata.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !bindata -// +build !bindata package svg diff --git a/modules/sync/unique_queue.go b/modules/sync/unique_queue.go deleted file mode 100644 index df115d7c96cc..000000000000 --- a/modules/sync/unique_queue.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Copyright 2019 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package sync - -// UniqueQueue is a queue which guarantees only one instance of same -// identity is in the line. Instances with same identity will be -// discarded if there is already one in the line. -// -// This queue is particularly useful for preventing duplicated task -// of same purpose. -type UniqueQueue struct { - table *StatusTable - queue chan string - closed chan struct{} -} - -// NewUniqueQueue initializes and returns a new UniqueQueue object. -func NewUniqueQueue(queueLength int) *UniqueQueue { - if queueLength <= 0 { - queueLength = 100 - } - - return &UniqueQueue{ - table: NewStatusTable(), - queue: make(chan string, queueLength), - closed: make(chan struct{}), - } -} - -// Close closes this queue -func (q *UniqueQueue) Close() { - select { - case <-q.closed: - default: - q.table.lock.Lock() - select { - case <-q.closed: - default: - close(q.closed) - } - q.table.lock.Unlock() - } -} - -// IsClosed returns a channel that is closed when this Queue is closed -func (q *UniqueQueue) IsClosed() <-chan struct{} { - return q.closed -} - -// IDs returns the current ids in the pool -func (q *UniqueQueue) IDs() []string { - q.table.lock.Lock() - defer q.table.lock.Unlock() - ids := make([]string, 0, len(q.table.pool)) - for id := range q.table.pool { - ids = append(ids, id) - } - return ids -} - -// Queue returns channel of queue for retrieving instances. -func (q *UniqueQueue) Queue() <-chan string { - return q.queue -} - -// Exist returns true if there is an instance with given identity -// exists in the queue. -func (q *UniqueQueue) Exist(id string) bool { - return q.table.IsRunning(id) -} - -// AddFunc adds new instance to the queue with a custom runnable function, -// the queue is blocked until the function exits. -func (q *UniqueQueue) AddFunc(id string, fn func()) { - q.table.lock.Lock() - if _, ok := q.table.pool[id]; ok { - q.table.lock.Unlock() - return - } - q.table.pool[id] = struct{}{} - if fn != nil { - fn() - } - q.table.lock.Unlock() - select { - case <-q.closed: - return - case q.queue <- id: - return - } -} - -// Add adds new instance to the queue. -func (q *UniqueQueue) Add(id string) { - q.AddFunc(id, nil) -} - -// Remove removes instance from the queue. -func (q *UniqueQueue) Remove(id string) { - q.table.Stop(id) -} diff --git a/modules/templates/dynamic.go b/modules/templates/dynamic.go index c6c47a6c8801..de6968c314a0 100644 --- a/modules/templates/dynamic.go +++ b/modules/templates/dynamic.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !bindata -// +build !bindata package templates diff --git a/modules/templates/static.go b/modules/templates/static.go index cb2978c2efcb..351e48b4daa9 100644 --- a/modules/templates/static.go +++ b/modules/templates/static.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build bindata -// +build bindata package templates diff --git a/modules/templates/templates_bindata.go b/modules/templates/templates_bindata.go index 5b59e4447e54..bcb2cbaf3fe2 100644 --- a/modules/templates/templates_bindata.go +++ b/modules/templates/templates_bindata.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build bindata -// +build bindata package templates diff --git a/modules/test/context_tests.go b/modules/test/context_tests.go index 2c6ae2f853ac..a05b221af810 100644 --- a/modules/test/context_tests.go +++ b/modules/test/context_tests.go @@ -60,7 +60,7 @@ func LoadRepo(t *testing.T, ctx *context.Context, repoID int64) { ctx.Repo.Owner, err = user_model.GetUserByID(ctx.Repo.Repository.OwnerID) assert.NoError(t, err) ctx.Repo.RepoLink = ctx.Repo.Repository.Link() - ctx.Repo.Permission, err = models.GetUserRepoPermission(ctx.Repo.Repository, ctx.Doer) + ctx.Repo.Permission, err = models.GetUserRepoPermission(ctx, ctx.Repo.Repository, ctx.Doer) assert.NoError(t, err) } diff --git a/modules/validation/helpers.go b/modules/validation/helpers.go index 617ec3578c8f..484b12b2a23c 100644 --- a/modules/validation/helpers.go +++ b/modules/validation/helpers.go @@ -13,32 +13,10 @@ import ( "code.gitea.io/gitea/modules/setting" ) -var loopbackIPBlocks []*net.IPNet - var externalTrackerRegex = regexp.MustCompile(`({?)(?:user|repo|index)+?(}?)`) -func init() { - for _, cidr := range []string{ - "127.0.0.0/8", // IPv4 loopback - "::1/128", // IPv6 loopback - } { - if _, block, err := net.ParseCIDR(cidr); err == nil { - loopbackIPBlocks = append(loopbackIPBlocks, block) - } - } -} - func isLoopbackIP(ip string) bool { - pip := net.ParseIP(ip) - if pip == nil { - return false - } - for _, block := range loopbackIPBlocks { - if block.Contains(pip) { - return true - } - } - return false + return net.ParseIP(ip).IsLoopback() } // IsValidURL checks if URL is valid diff --git a/options/license/LGPL-3.0-only b/options/license/LGPL-3.0-only index c9287dd363f6..513d1c01fe5b 100644 --- a/options/license/LGPL-3.0-only +++ b/options/license/LGPL-3.0-only @@ -69,3 +69,236 @@ Each version is given a distinguishing version number. If the Library as you rec If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. + +GNU GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The GNU General Public License is a free, copyleft license for software and other kinds of works. + +The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS + +0. Definitions. + +“This License” refers to version 3 of the GNU General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based on the Program. + +To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. + +1. Source Code. +The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work. + +A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. + +The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +2. Basic Permissions. +All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. +No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. + +4. Conveying Verbatim Copies. +You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. +You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”. + + c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. + +6. Conveying Non-Source Forms. +You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: + + a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. + + d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. + +A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. + +7. Additional Terms. +“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or authors of the material; or + + e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. + +All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. + +8. Termination. +You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. + +9. Acceptance Not Required for Having Copies. +You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. +Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. + +An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. + +11. Patents. +A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. + +In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. + +A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. +If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. + +13. Use with the GNU Affero General Public License. +Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. + +14. Revised Versions of this License. +The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. + +15. Disclaimer of Warranty. +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. +If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”. + +You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . + +The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . diff --git a/options/license/LGPL-3.0-or-later b/options/license/LGPL-3.0-or-later index c9287dd363f6..513d1c01fe5b 100644 --- a/options/license/LGPL-3.0-or-later +++ b/options/license/LGPL-3.0-or-later @@ -69,3 +69,236 @@ Each version is given a distinguishing version number. If the Library as you rec If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. + +GNU GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The GNU General Public License is a free, copyleft license for software and other kinds of works. + +The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS + +0. Definitions. + +“This License” refers to version 3 of the GNU General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based on the Program. + +To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. + +1. Source Code. +The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work. + +A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. + +The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +2. Basic Permissions. +All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. +No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. + +4. Conveying Verbatim Copies. +You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. +You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”. + + c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. + +6. Conveying Non-Source Forms. +You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: + + a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. + + d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. + +A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. + +7. Additional Terms. +“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or authors of the material; or + + e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. + +All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. + +8. Termination. +You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. + +9. Acceptance Not Required for Having Copies. +You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. +Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. + +An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. + +11. Patents. +A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. + +In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. + +A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. +If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. + +13. Use with the GNU Affero General Public License. +Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. + +14. Revised Versions of this License. +The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. + +15. Disclaimer of Warranty. +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. +If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”. + +You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . + +The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . diff --git a/options/locale/locale_bg-BG.ini b/options/locale/locale_bg-BG.ini index 2887a9b7f263..22351c04e20e 100644 --- a/options/locale/locale_bg-BG.ini +++ b/options/locale/locale_bg-BG.ini @@ -72,6 +72,7 @@ loading=Зареждане… error404=Страницата, която се опитвате да достъпите, не съществува или не сте оторизирани да я достъпите. + [error] [startpage] diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 778326b205a1..f08947a40a15 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -86,6 +86,7 @@ error404=Stránka, kterou se snažíte zobrazit, buď neexistujeexistiert entwed never=Niemals + [error] occurred=Ein Fehler ist aufgetreten report_message=Wenn du dir sicher bist, dass dies ein Fehler von Gitea ist, suche bitte auf GitHub nach diesem Fehler und erstelle gegebenenfalls ein neues Issue. diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index 14d7c9fd1d06..0119148cd52d 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -105,6 +105,7 @@ error404=Η σελίδα που προσπαθείτε να φτάσετε εί never=Ποτέ + [error] occurred=Παρουσιάστηκε ένα σφάλμα report_message=Αν είστε σίγουροι ότι πρόκειται για ένα πρόβλημα στο Gitea, παρακαλώ αναζητήστε στα ζητήματα στο GitHub ή ανοίξτε ένα νέο ζήτημα εάν είναι απαραίτητο. diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 21bf0c49ea19..0175c8bfc8b0 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -105,6 +105,8 @@ error404 = The page you are trying to reach either does not existGitHub or open a new issue if necessary. @@ -1039,6 +1041,7 @@ line_unicode = `This line has hidden unicode characters` escape_control_characters = Escape unescape_control_characters = Unescape file_copy_permalink = Copy Permalink +view_git_blame = View Git Blame video_not_supported_in_browser = Your browser does not support the HTML5 'video' tag. audio_not_supported_in_browser = Your browser does not support the HTML5 'audio' tag. stored_lfs = Stored with Git LFS @@ -1485,6 +1488,9 @@ pulls.desc = Enable pull requests and code reviews. pulls.new = New Pull Request pulls.view = View Pull Request pulls.compare_changes = New Pull Request +pulls.allow_edits_from_maintainers = Allow edits from maintainers +pulls.allow_edits_from_maintainers_desc = Users with write access to the base branch can also push to this branch +pulls.allow_edits_from_maintainers_err = Updating failed pulls.compare_changes_desc = Select the branch to merge into and the branch to pull from. pulls.compare_base = merge into pulls.compare_compare = pull from @@ -3086,7 +3092,7 @@ settings.link = Link this package to a repository settings.link.description = If you link a package with a repository, the package is listed in the repository's package list. settings.link.select = Select Repository settings.link.button = Update Repository Link -settings.link.success = Repository link was successfully updated. +settings.link.success = Repository link was successfully updated. settings.link.error = Failed to update repository link. settings.delete = Delete package settings.delete.description = Deleting a package is permanent and cannot be undone. diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index d187c43a3d37..5311470969dc 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -105,6 +105,7 @@ error404=La página a la que está intentando acceder o no existeGitHub y abre un nuevo problema si es necesario. diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index 38f24b1b23ea..0216356d135f 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -91,6 +91,7 @@ error404=صفحه موردنظر شما یا وجود نداردei löydy tai et ole oikeutettu katsomaan sitä. + [error] [startpage] diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index b83924ae7be8..2f9ef3c1bc84 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -91,6 +91,7 @@ error404=La page que vous essayez d'atteindre n'existe pas ou < never=Jamais + [error] missing_csrf=Requête incorrecte: aucun jeton CSRF présent diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini index 61c21c688d6a..b830e8baf873 100644 --- a/options/locale/locale_hu-HU.ini +++ b/options/locale/locale_hu-HU.ini @@ -80,6 +80,7 @@ step2=2. lépés: error404=Az elérni kívánt oldal vagy nem létezik, vagy nincs jogosultsága a megtekintéséhez. + [error] [startpage] diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini index b9f1e46e397c..e4869c02c520 100644 --- a/options/locale/locale_id-ID.ini +++ b/options/locale/locale_id-ID.ini @@ -73,6 +73,7 @@ loading=Memuat… + [error] [startpage] diff --git a/options/locale/locale_is-IS.ini b/options/locale/locale_is-IS.ini index 03589d3ae4f3..de89ad638582 100644 --- a/options/locale/locale_is-IS.ini +++ b/options/locale/locale_is-IS.ini @@ -105,6 +105,7 @@ error404=Síðan sem þú ert að reyna að fá annað hvort er ekki til never=Aldrei + [error] occurred=Villa kom upp report_message=Ef þú ert viss um að þetta sé villa í Gitea þá skaltu leita að vandamálum á GitHub eða opna nýtt vandamál ef þörf krefst. diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index 0550290fe34b..7a547142bf1c 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -83,6 +83,7 @@ step2=Passo 2: error404=La pagina che stai cercando di raggiungere non esiste oppure non sei autorizzato a visualizzarla. + [error] [startpage] diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index db05158ca1be..a9cb8b70ad4f 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -105,6 +105,8 @@ error404=アクセスしようとしたページは存在しないGitHubでIssueを検索して、見つからなければ新しいIssueを作成してください。 @@ -1039,6 +1041,7 @@ line_unicode=`この行には不可視Unicode文字があります` escape_control_characters=エスケープ unescape_control_characters=エスケープ解除 file_copy_permalink=パーマリンクをコピー +view_git_blame=Git Blameを表示 video_not_supported_in_browser=このブラウザはHTML5のvideoタグをサポートしていません。 audio_not_supported_in_browser=このブラウザーはHTML5のaudioタグをサポートしていません。 stored_lfs=Git LFSで保管されています @@ -1485,6 +1488,9 @@ pulls.desc=プルリクエストとコードレビューの有効化。 pulls.new=新しいプルリクエスト pulls.view=プルリクエストを表示 pulls.compare_changes=新規プルリクエスト +pulls.allow_edits_from_maintainers=メンテナーからの編集を許可する +pulls.allow_edits_from_maintainers_desc=ベースブランチへの書き込みアクセス権を持つユーザーは、このブランチにプッシュすることもできます +pulls.allow_edits_from_maintainers_err=更新に失敗しました pulls.compare_changes_desc=マージ先ブランチとプル元ブランチを選択。 pulls.compare_base=マージ先 pulls.compare_compare=プル元 @@ -3051,6 +3057,9 @@ container.labels.key=キー container.labels.value=値 generic.download=コマンドラインでパッケージをダウンロードします: generic.documentation=汎用 レジストリの詳細については、ドキュメント を参照してください。 +helm.registry=このレジストリをコマンドラインからセットアップします: +helm.install=パッケージをインストールするには、次のコマンドを実行します: +helm.documentation=Helm レジストリの詳細については、 ドキュメント を参照してください。 maven.registry=あなたのプロジェクトの pom.xml ファイルに、このレジストリをセットアップします: maven.install=パッケージを使用するため pom.xml ファイル内の dependencies ブロックに以下を含めます: maven.install2=コマンドラインで実行します: diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini index b9d878904fa8..c55de5a8f9d3 100644 --- a/options/locale/locale_ko-KR.ini +++ b/options/locale/locale_ko-KR.ini @@ -74,6 +74,7 @@ loading=불러오는 중... + [error] [startpage] diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 16eb1866a5f1..40cb93c80a67 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -105,6 +105,7 @@ error404=Lapa, ko vēlaties atvērt, neeksistē vai arī GitHub vai ziņojiet par jaunu kļūdu, ja nepieciešams. diff --git a/options/locale/locale_ml-IN.ini b/options/locale/locale_ml-IN.ini index 6c42aff44895..31b9ca23ebb3 100644 --- a/options/locale/locale_ml-IN.ini +++ b/options/locale/locale_ml-IN.ini @@ -65,6 +65,7 @@ loading=ലഭ്യമാക്കുന്നു… + [error] [startpage] diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index 3553b11e090f..dfc7fe13babb 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -90,6 +90,7 @@ error404=De pagina die u probeert te bereiken bestaat niet of < never=Nooit + [error] missing_csrf=Foutief verzoek: geen CSRF-token aanwezig diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index cfdb177765b9..90c4d28027f8 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -105,6 +105,7 @@ error404=Strona, do której próbujesz dotrzeć nie istnieje lu never=Nigdy + [error] occurred=Wystąpił błąd report_message=Jeśli jesteś pewien, że jest to błąd Gitea, poszukaj już istniejącego zgłoszenia na GitHub lub w razie potrzeby otwórz nowy problem. diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 6cadc3f68857..73d7251f604d 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -105,6 +105,7 @@ error404=A página que você está tentando acessar não existe never=Nunca + [error] occurred=Ocorreu um erro report_message=Se você tem certeza de que é um bug do Gitea, procure por issues no GitHub ou abra uma nova issue, se necessário. @@ -267,6 +268,7 @@ organizations=Organizações search=Pesquisar code=Código search.fuzzy=Similar +search.match=Correspondência code_search_unavailable=A pesquisa por código não está disponível no momento. Entre em contato com o administrador do site. repo_no_results=Nenhum repositório correspondente foi encontrado. user_no_results=Nenhum usuário correspondente foi encontrado. @@ -566,6 +568,7 @@ comment_type_group_lock=Status de Bloqueio comment_type_group_review_request=Revisar solicitação comment_type_group_pull_request_push=Commits adicionados comment_type_group_project=Projeto +comment_type_group_issue_ref=Referência do issue saved_successfully=Suas configurações foram salvas com sucesso. privacy=Privacidade keep_activity_private=Ocultar a atividade da página de perfil @@ -1037,6 +1040,7 @@ line_unicode=`Esta linha possui caracteres unicode ocultos` escape_control_characters=Escapar unescape_control_characters=Desescapar file_copy_permalink=Copiar Link Permanente +view_git_blame=Ver Git Blame video_not_supported_in_browser=Seu navegador não suporta a tag 'video' do HTML5. audio_not_supported_in_browser=Seu navegador não suporta a tag 'audio' do HTML5. stored_lfs=Armazenado com Git LFS @@ -1130,7 +1134,7 @@ commits.message=Mensagem commits.date=Data commits.older=Mais Antigo commits.newer=Mais recente -commits.signed_by=Acessado por +commits.signed_by=Assinado por commits.signed_by_untrusted_user=Assinado por usuário não confiável commits.signed_by_untrusted_user_unmatched=Assinado por usuário não confiável que não corresponde ao autor da submissão commits.gpg_key_id=ID da chave GPG @@ -1725,6 +1729,7 @@ activity.git_stats_deletion_n=%d exclusões search=Pesquisar search.search_repo=Pesquisar no repositório... search.fuzzy=Aproximada +search.match=Corresponde search.results=Resultados da pesquisa para "%s" em %s search.code_no_results=Nenhum código-fonte correspondente ao seu termo de pesquisa foi encontrado. search.code_search_unavailable=A pesquisa por código não está disponível no momento. Entre em contato com o administrador do site. @@ -1789,6 +1794,7 @@ settings.pulls.allow_rebase_merge_commit=Habilitar Rebasing com commits explíci settings.pulls.allow_squash_commits=Habilitar Squashing em commits via merge settings.pulls.allow_manual_merge=Habilitar Marcar PR como aplicado manualmente settings.pulls.enable_autodetect_manual_merge=Habilitar a detecção automática de merge manual (Nota: Em alguns casos especiais, podem ocorrer julgamentos errados) +settings.pulls.allow_rebase_update=Ativar atualização do branch do pull request por rebase settings.pulls.default_delete_branch_after_merge=Excluir o branch de pull request após o merge por padrão settings.packages_desc=Habilitar Registro de Pacotes de Repositório settings.projects_desc=Habilitar Projetos do Repositório @@ -2159,6 +2165,7 @@ diff.review.placeholder=Comentário da revisão diff.review.comment=Comentar diff.review.approve=Aprovar diff.review.reject=Solicitar alterações +diff.committed_by=commit de diff.protected=Protegido diff.image.side_by_side=Lado a Lado diff.image.swipe=Deslizar @@ -2597,6 +2604,7 @@ auths.use_paged_search=Use a pesquisa paginada auths.search_page_size=Tamanho da página auths.filter=Filtro de usuário auths.admin_filter=Filtro de administrador +auths.group_attribute_list_users=Atributo do Grupo que Contém a Lista de Usuários auths.enable_ldap_groups=Habilitar grupos do LDAP auths.ms_ad_sa=Atributos de pesquisa do MS AD auths.smtp_auth=Tipo de autenticação SMTP @@ -2624,6 +2632,8 @@ auths.oauth2_profileURL=URL do perfil auths.oauth2_emailURL=URL de e-mail auths.skip_local_two_fa=Pular 2FA local auths.skip_local_two_fa_helper=Deixar desligado significa que os usuários locais com 2FA ligada ainda terão que fazer login com 2FA +auths.oauth2_tenant=Tenant +auths.oauth2_scopes=Escopos Adicionais auths.enable_auto_register=Habilitar cadastro automático auths.sspi_auto_create_users=Criar usuários automaticamente auths.sspi_auto_create_users_helper=Permitir que o método de autenticação SSPI crie automaticamente novas contas para usuários que fazem o login pela primeira vez @@ -2987,6 +2997,8 @@ empty.documentation=Para obter mais informações sobre o registro de pacote, co filter.type=Tipo filter.type.all=Todos filter.no_result=Seu filtro não produziu resultados. +filter.container.tagged=Marcado +filter.container.untagged=Desmarcado published_by=Publicado %[1]s por %[3]s published_by_in=Publicado %[1]s por %[3]s em %[5]s installation=Instalação @@ -2998,6 +3010,7 @@ details=Detalhes details.author=Autor details.project_site=Site do Projeto details.license=Licença +assets=Recursos versions=Versões versions.on=em versions.view_all=Ver todas @@ -3019,6 +3032,7 @@ container.details.documentation_site=Site da Documentação container.pull=Puxe a imagem pela linha de comando: container.documentation=Para obter mais informações sobre o registro de Container, consulte a documentação. container.multi_arch=S.O. / Arquitetura +container.layers=Camadas da Imagem container.labels=Rótulos container.labels.key=Chave container.labels.value=Valor @@ -3035,12 +3049,14 @@ maven.documentation=Para obter mais informações sobre o registro Maven, consul nuget.registry=Configurar este registro pela linha de comando: nuget.install=Para instalar o pacote usando NuGet, execute o seguinte comando: nuget.documentation=Para obter mais informações sobre o registro Nuget, consulte a documentação. +nuget.dependency.framework=Estrutura Alvo npm.registry=Configure este registro no arquivo .npmrc do seu projeto: npm.install=Para instalar o pacote usando o npm, execute o seguinte comando: npm.install2=ou adicione-o ao arquivo package.json: npm.documentation=Para obter mais informações sobre o registro npm, consulte a documentação. npm.dependencies=Dependências npm.dependencies.development=Dependências de Desenvolvimento +npm.dependencies.peer=Dependências Peer npm.dependencies.optional=Dependências Opcionais npm.details.tag=Tag pypi.requires=Requer Python diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 05f102d9e35a..d8801f7517b9 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -105,6 +105,8 @@ error404=A página que pretende aceder não existe ou n never=Nunca +rss_feed=Fonte RSS + [error] occurred=Ocorreu um erro report_message=Se tiver certeza de que se trata de um erro do Gitea, procure, por favor, questões relacionadas no GitHub ou abra uma nova questão, se necessário. @@ -1484,6 +1486,8 @@ pulls.desc=Habilitar pedidos de integração e revisão de código. pulls.new=Novo pedido de integração pulls.view=Ver pedido de integração pulls.compare_changes=Novo pedido de integração +pulls.allow_edits_from_maintainers_desc=Utilizadores com acesso de escrita no ramo base também podem fazer envios para este ramo +pulls.allow_edits_from_maintainers_err=Não foi possível fazer a modificação pulls.compare_changes_desc=Escolha o ramo de destino e o ramo de origem. pulls.compare_base=integrar em pulls.compare_compare=puxar de diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 47d85b5edf86..20664861aac1 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -45,6 +45,8 @@ webauthn_error_insecure=WebAuthn поддерживает только безо webauthn_error_unable_to_process=Сервер не смог обработать ваш запрос. webauthn_error_duplicated=Представленный ключ не подходит для этого запроса. Если вы пытаетесь зарегистрировать его, убедитесь, что ключ ещё не зарегистрирован. webauthn_error_empty=Вы должны указать имя для этого ключа. +webauthn_error_timeout=Тайм-аут достигнут до того, как ваш ключ был прочитан. Перезагрузите эту страницу и повторите попытку. +webauthn_reload=Обновить repository=Репозиторий organization=Организация @@ -102,8 +104,13 @@ error404=Страница, которую вы пытаетесь открыть never=Никогда +rss_feed=RSS-лента + [error] +occurred=Произошла ошибка missing_csrf=Некорректный запрос: CSRF токен отсутствует +invalid_csrf=Некорректный запрос: неверный CSRF токен +network_error=Ошибка сети [startpage] app_desc=Удобный сервис собственного хостинга репозиториев Git @@ -260,6 +267,7 @@ search=Поиск code=Код search.fuzzy=Неточный search.match=Соответствие +code_search_unavailable=В настоящее время поиск по коду недоступен. Обратитесь к администратору сайта. repo_no_results=Подходящие репозитории не найдены. user_no_results=Подходящие пользователи не найдены. org_no_results=Подходящие организации не найдены. @@ -273,6 +281,7 @@ register_helper_msg=Уже есть аккаунт? Авторизуйтесь! social_register_helper_msg=Уже есть аккаунт? Свяжите его сейчас! disable_register_prompt=Извините, возможность регистрации отключена. Пожалуйста, свяжитесь с администратором сайта. disable_register_mail=Подтверждение регистрации по электронной почте отключено. +manual_activation_only=Обратитесь к администратору сайта для завершения активации. remember_me=Запомнить это устройство forgot_password_title=Восстановить пароль forgot_password=Забыли пароль? @@ -311,6 +320,9 @@ oauth_signup_submit=Полная учётная запись oauth_signin_tab=Ссылка на существующую учётную запись oauth_signin_title=Войдите, чтобы авторизовать связанную учётную запись oauth_signin_submit=Привязать учётную запись +oauth.signin.error=Произошла ошибка при обработке запроса авторизации. Если эта ошибка повторяется, обратитесь к администратору сайта. +oauth.signin.error.access_denied=Запрос на авторизацию был отклонен. +oauth.signin.error.temporarily_unavailable=Ошибка авторизации, так как сервер временно недоступен. Пожалуйста, повторите попытку позже. openid_connect_submit=Подключить openid_connect_title=Подключение к существующей учетной записи openid_connect_desc=Выбранный OpenID URI неизвестен. Свяжите с новой учетной записью здесь. @@ -475,7 +487,9 @@ auth_failed=Ошибка аутентификации: %v still_own_repo=Ваша учётная запись владеет одним или несколькими репозиториями; сначала удалите или перенесите их. still_has_org=Ваша учётная запись является членом одной или нескольких организаций; сначала выйдите из них. +still_own_packages=Ваша учётная запись владеет одним или несколькими пакетами, сначала удалите их. org_still_own_repo=Эта организация по-прежнему владеет одним или несколькими репозиториями; сначала удалите или перенесите их. +org_still_own_packages=Эта организация всё ещё имеет пакеты, сначала удалите их. target_branch_not_exist=Целевая ветка не существует. @@ -516,6 +530,7 @@ twofa=Двухфакторная аутентификация account_link=Привязанные аккаунты organization=Организации uid=UID +webauthn=Ключи безопасности public_profile=Открытый профиль biography_placeholder=Расскажите немного о себе @@ -537,6 +552,19 @@ continue=Далее cancel=Отмена language=Язык ui=Тема +hidden_comment_types=Скрытые типы комментариев +comment_type_group_reference=Упоминания +comment_type_group_label=Операции с метками +comment_type_group_milestone=Этап +comment_type_group_assignee=Назначения +comment_type_group_title=Правки заголовков +comment_type_group_branch=Операции с ветками +comment_type_group_time_tracking=Отслеживание времени +comment_type_group_deadline=Модификации сроков выполнения +comment_type_group_dependency=Модификации зависимостей +comment_type_group_lock=Смена статуса ограничения на обсуждение +comment_type_group_review_request=Запросы на рецензию +saved_successfully=Ваши настройки успешно сохранены. privacy=Приватность keep_activity_private=Скрыть активность со страницы профиля keep_activity_private_popup=Делает активность видимой только для вас и администраторов @@ -736,6 +764,10 @@ twofa_enrolled=Для вашего аккаунта была включена д twofa_failed_get_secret=Не удалось получить ключ. webauthn_desc=Ключи безопасности - это аппаратные устройства, содержащие криптографические ключи. Они могут использоваться для двухфакторной аутентификации. Ключи безопасности должны поддерживать стандарт WebAuthn Authenticator. +webauthn_register_key=Добавить ключ безопасности +webauthn_nickname=Имя пользователя +webauthn_delete_key=Удалить ключ безопасности +webauthn_delete_key_desc=Если вы удалите ключ безопасности, вы больше не сможете войти с его помощью. Продолжить? manage_account_links=Управление привязанными аккаунтами manage_account_links_desc=Эти внешние аккаунты привязаны к вашему аккаунту Gitea. @@ -786,6 +818,7 @@ visibility_fork_helper=(Изменение этого повлияет на вс clone_helper=Нужна помощь в клонировании? Посетите страницу помощи. fork_repo=Форкнуть репозиторий fork_from=Форк от +already_forked=Вы уже форкнули %s fork_visibility_helper=Видимость форкнутого репозитория изменить нельзя. use_template=Использовать этот шаблон clone_in_vsc=Клонировать в VS Code @@ -920,6 +953,7 @@ migrate.migrating=Перенос из %s... migrate.migrating_failed=Перенос из %s не удался. migrate.migrating_failed.error=Ошибка: %s migrate.migrating_failed_no_addr=Миграция не удалась. +migrate.github.description=Переносите данные с github.com или других серверов GitHub. migrate.git.description=Перенести только репозиторий из любого Git сервиса. migrate.gitlab.description=Перенести данные с gitlab.com или других экземпляров GitLab. migrate.gitea.description=Перенести данные с gitea.com или других экземпляров Gitea. @@ -929,6 +963,7 @@ migrate.codebase.description=Перенос данных с codebasehq.com. migrate.gitbucket.description=Перенести данные из экземпляров GitBucket. migrate.migrating_git=Перенос Git данных migrate.migrating_topics=Миграция тем +migrate.migrating_milestones=Перенос этапов migrate.migrating_labels=Миграция меток migrate.migrating_issues=Миграция задач migrate.migrating_pulls=Миграция запросов на слияние @@ -1025,6 +1060,8 @@ editor.add_tmpl=Добавить '' editor.add=Создал(а) '%s' editor.update=Изменил(а) на '%s' editor.delete=Удалить '%s' +editor.patch=Применить патч +editor.new_patch=Новый патч editor.commit_message_desc=Добавьте необязательное расширенное описание… editor.signoff_desc=Добавить Signed-off-by коммитом в конце сообщения журнала коммитов. editor.commit_directly_to_this_branch=Сделайте коммит прямо в ветку %s. @@ -1077,6 +1114,12 @@ commits.signed_by_untrusted_user=Подписано ненадежным пол commits.signed_by_untrusted_user_unmatched=Подписан ненадежным пользователем, который не соответствует коммиту commits.gpg_key_id=Идентификатор GPG ключа +commit.revert=Откатить +commit.revert-header=Откат: %s +commit.revert-content=Выбрать ветку для отката: +commit.cherry-pick=Cherry-pick +commit.cherry-pick-header=Cherry-pick: %s +commit.cherry-pick-content=Выбрать ветку для cherry-pick: ext_issues.desc=Ссылка на внешнюю систему отслеживания ошибок. @@ -1302,6 +1345,9 @@ issues.lock.reason=Причина для ограничения issues.lock.title=Ограничить обсуждение данной задачи. issues.unlock.title=Снять ограничение обсуждения данной задачи. issues.comment_on_locked=Вы не можете оставить комментарий по задаче, ограниченной для обсуждения. +issues.delete=Удалить +issues.delete.title=Удалить эту задачу? +issues.delete.text=Вы действительно хотите удалить эту задачу? Это навсегда удалит всё содержимое. Возможно лучше закрыть её в архивных целях. issues.tracker=Отслеживание времени issues.start_tracking_short=Запустить таймер issues.start_tracking=Начать отслеживание времени @@ -1342,6 +1388,8 @@ issues.due_date_remove=удалён срок выполнения %s %s issues.due_date_overdue=Просроченные issues.due_date_invalid=Срок действия недействителен или находится за пределами допустимого диапазона. Пожалуйста, используйте формат 'гггг-мм-дд'. issues.dependency.title=Зависимости +issues.dependency.issue_no_dependencies=Зависимостей нет. +issues.dependency.pr_no_dependencies=Зависимостей нет. issues.dependency.add=Добавить зависимость… issues.dependency.cancel=Отменить issues.dependency.remove=Удалить @@ -1407,7 +1455,7 @@ pulls.new=Новый запрос на слияние pulls.view=Просмотр запроса на слияние pulls.compare_changes=Новый запрос на слияние pulls.compare_changes_desc=Сравнить две ветки и создать запрос на слияние для изменений. -pulls.compare_base=родительская ветка +pulls.compare_base=базовая ветка pulls.compare_compare=взять из pulls.switch_comparison_type=Переключить тип сравнения pulls.switch_head_and_base=Поменять исходную и целевую ветки местами @@ -1482,7 +1530,9 @@ pulls.rebase_conflict_summary=Сообщение об ошибке ; %[2]s
%[3]s
pulls.unrelated_histories=Слияние не удалось: У источника и цели слияния нет общей истории. Совет: попробуйте другую стратегию pulls.merge_out_of_date=Ошибка слияния: при создании слияния база данных была обновлена. Подсказка: попробуйте ещё раз. +pulls.push_rejected=Слияние не удалось: push был отклонён. Проверьте Git-хуки для этого репозитория. pulls.push_rejected_summary=Полная ошибка отклонения +pulls.push_rejected_no_message=Слияние не удалось: push был отклонён, но сервер не указал причину.
Проверьте Git-хуки для этого репозитория pulls.open_unmerged_pull_exists=`Вы не можете снова открыть, поскольку уже существует запрос на слияние (#%d) из того же репозитория с той же информацией о слиянии и ожидающий слияния.` pulls.status_checking=Выполняются некоторые проверки pulls.status_checks_success=Все проверки выполнены успешно @@ -1646,6 +1696,8 @@ search.search_repo=Поиск по репозиторию search.fuzzy=Неточный search.match=Соответствие search.results=Результаты поиска "%s" в %s +search.code_no_results=Не найдено исходного кода, соответствующего поисковому запросу. +search.code_search_unavailable=В настоящее время поиск по коду недоступен. Обратитесь к администратору сайта. settings=Настройки settings.desc=В настройках вы можете менять различные параметры этого репозитория @@ -1805,6 +1857,7 @@ settings.webhook.response=Ответ settings.webhook.headers=Заголовки settings.webhook.payload=Содержимое settings.webhook.body=Тело ответа +settings.webhook.replay.description=Повторить этот веб-хук. settings.githook_edit_desc=Если хук не активен, будет подставлен пример содержимого. Пустое значение в этом поле приведёт к отключению хука. settings.githook_name=Название Hook'a settings.githook_content=Содержание hook'а @@ -1862,6 +1915,8 @@ settings.event_pull_request_review=Запрос на слияние рассмо settings.event_pull_request_review_desc=Запрос на слияние утвержден, отклонён или оставлен комментарий. settings.event_pull_request_sync=Синхронизация запроса на слияние settings.event_pull_request_sync_desc=Запрос на слияние синхронизирован. +settings.event_package=Пакеты +settings.event_package_desc=Пакет создан или удален в репозитории. settings.branch_filter=Фильтр веток settings.branch_filter_desc=Белый список ветвей для событий Push, создания ветвей и удаления ветвей, указанных в виде глоб-шаблона. Если пустой или *, то все событий для всех ветвей будут зарегистрированы. Перейдите по ссылке github.com/gobwas/glob на документацию по синтаксису. Примеры: master, {master,release*}. settings.active=Активный @@ -1875,6 +1930,13 @@ settings.hook_type=Тип hook'а settings.slack_token=Slack токен settings.slack_domain=Домен settings.slack_channel=Канал +settings.web_hook_name_gitea=Gitea +settings.web_hook_name_gogs=Gogs +settings.web_hook_name_slack=Slack +settings.web_hook_name_discord=Discord +settings.web_hook_name_dingtalk=DingTalk +settings.web_hook_name_msteams=Microsoft Teams +settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite settings.deploy_keys=Ключи развертывания settings.add_deploy_key=Добавить ключ развертывания settings.deploy_key_desc=Ключи развёртывания доступны только для чтения. Это не то же самое что и SSH-ключи аккаунта. @@ -2129,11 +2191,14 @@ branch.included_desc=Эта ветка является частью ветки branch.included=Включено branch.create_new_branch=Создать ветку из ветви: branch.confirm_create_branch=Создать ветку +branch.create_branch_operation=Создать ветку branch.new_branch=Создать новую ветку branch.new_branch_from=Создать новую ветку из '%s' branch.renamed=Ветка %s была переименована в %s. tag.create_tag=Создать тег %s +tag.create_tag_operation=Создать тег +tag.confirm_create_tag=Создать тег tag.create_success=Тег '%s' был создан. @@ -2221,7 +2286,9 @@ teams.leave=Выйти teams.leave.detail=Покинуть %s? teams.can_create_org_repo=Создать репозитории teams.can_create_org_repo_helper=Участники могут создавать новые репозитории в организации. Создатель получит администраторский доступ к новому репозиторию. +teams.read_access=Чтение teams.read_access_helper=Участники могут просматривать и клонировать командные репозитории. +teams.write_access=Запись teams.write_access_helper=Участники могут читать и выполнять push в командные репозитории. teams.admin_access=Доступ администратора teams.admin_access_helper=Участники могут выполнять pull, push в командные репозитории и добавлять соавторов в команду. @@ -2345,6 +2412,7 @@ dashboard.last_gc_pause=Последняя пауза сборщика мусо dashboard.gc_times=Количество сборок мусора dashboard.delete_old_actions=Удалите все старые действия из базы данных dashboard.delete_old_actions.started=Удалите все старые действия из запущенной базы данных. +dashboard.update_checker=Проверка обновлений users.user_manage_panel=Панель управления пользователями users.new_account=Создать новый аккаунт @@ -2427,6 +2495,14 @@ repos.forks=Форки repos.issues=Задачи repos.size=Размер +packages.owner=Владелец +packages.creator=Автор +packages.name=Наименование +packages.version=Версия +packages.type=Тип +packages.repository=Репозиторий +packages.size=Размер +packages.published=Опубликовано defaulthooks=Стандартные Веб-хуки defaulthooks.desc=Вебхуки автоматически делают HTTP-POST запросы на сервер, когда вызываются определенные события Gitea. Вебхуки, определённые здесь, по умолчанию и будут скопированы во все новые репозитории. Подробнее читайте в руководстве по вебхукам. @@ -2683,9 +2759,11 @@ monitor.next=Следующий раз monitor.previous=Предыдущий раз monitor.execute_times=Количество выполнений monitor.process=Запущенные процессы +monitor.goroutines=%d горутин monitor.desc=Описание monitor.start=Время начала monitor.execute_time=Время выполнения +monitor.last_execution_result=Результат monitor.process.cancel=Отменить процесс monitor.process.cancel_desc=Отмена процесса может привести к потере данных monitor.process.cancel_notices=Отменить: %s? @@ -2716,6 +2794,11 @@ monitor.queue.pool.flush.title=Очистить очередь monitor.queue.pool.flush.desc=При сбросе будет добавлен работник, который будет закрыт, когда очередь будет пустой, или истечет время время. monitor.queue.pool.flush.submit=Добавить чистящего работника monitor.queue.pool.flush.added=Добавлен чистящий рабочий на %[1]s +monitor.queue.pool.pause.desc=Приостановка очереди приостановит обработку данных +monitor.queue.pool.pause.submit=Приостановить очередь +monitor.queue.pool.resume.title=Возобновить очередь +monitor.queue.pool.resume.desc=Эта очередь возобновит работу +monitor.queue.pool.resume.submit=Возобновить очередь monitor.queue.settings.title=Настройки пула monitor.queue.settings.desc=Пулы динамично растут с ускорением в ответ на блокировку их рабочих очередей. Эти изменения не повлияют на текущие рабочие группы. @@ -2845,4 +2928,18 @@ error.no_unit_allowed_repo=У вас нет доступа ни к одному error.unit_not_allowed=У вас нет доступа к этому разделу репозитория. [packages] +title=Пакеты +desc=Управление пакетами репозитория. +empty=Пока нет пакетов. +empty.documentation=Дополнительную информацию о реестре пакетов можно найти в документации. +filter.type=Тип +filter.type.all=Все +filter.no_result=Фильтр не дал результатов. +installation=Установка +about=Об этом пакете +requirements=Требования +dependencies=Зависимости +container.multi_arch=ОС / архитектура +container.labels.key=Ключ +container.labels.value=Значение diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini index 115b09fb3c3e..f64c40d2286e 100644 --- a/options/locale/locale_si-LK.ini +++ b/options/locale/locale_si-LK.ini @@ -91,6 +91,7 @@ error404=ඔබ ළඟා වීමට උත්සාහ කරන පිටු never=කිසි විටෙකත් + [error] missing_csrf=නරක ඉල්ලීම: CSRF ටෝකන් නොමැත diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index fdca7da18d03..88d9b8bc5361 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -79,6 +79,7 @@ loading=Laddar… error404=Sidan du försöker nå finns inte eller så har du inte behörighet att se den. + [error] [startpage] diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 9eb3022d2736..b0cea0cc08b2 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -91,6 +91,7 @@ error404=Ulaşmaya çalıştığınız sayfa mevcut değil veya never=Asla + [error] missing_csrf=Hatalı İstek: CSRF anahtarı yok diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 7c9a3b0ce620..f9e099baa24a 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -92,6 +92,7 @@ error404=Сторінка, до якої ви намагаєтеся зверн never=Ніколи + [error] occurred=Сталася помилка missing_csrf=Некоректний запит: токен CSRF не задано diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 0c395fec9ac8..51b8263691cf 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -105,6 +105,7 @@ error404=您正尝试访问的页面 不存在您 never=从不 + [error] occurred=发生了一个错误 report_message=如果您确定这是一个 Gitea bug,请在 GitHub 上搜索问题,或在必要时打开一个新问题。 diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini index c2e9cbe7c4f2..89fc772ecbca 100644 --- a/options/locale/locale_zh-HK.ini +++ b/options/locale/locale_zh-HK.ini @@ -46,6 +46,7 @@ cancel=取消 + [error] [startpage] diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 719342930909..440bf18135ba 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -105,6 +105,7 @@ error404=您正嘗試訪問的頁面 不存在您 never=從來沒有 + [error] occurred=發生錯誤 report_message=如果您確定這是一個 Gitea 的 bug,請到 GitHub 搜尋相關的問題,如果有需要您也可以建立新問題。 diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index bf176f95710b..775802449abb 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -22,6 +22,7 @@ import ( "code.gitea.io/gitea/modules/password" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/api/v1/user" "code.gitea.io/gitea/routers/api/v1/utils" @@ -82,7 +83,6 @@ func CreateUser(ctx *context.APIContext) { Email: form.Email, Passwd: form.Password, MustChangePassword: true, - IsActive: true, LoginType: auth.Plain, } if form.MustChangePassword != nil { @@ -108,11 +108,17 @@ func CreateUser(ctx *context.APIContext) { return } - var overwriteDefault *user_model.CreateUserOverwriteOptions + overwriteDefault := &user_model.CreateUserOverwriteOptions{ + IsActive: util.OptionalBoolTrue, + } + + if form.Restricted != nil { + overwriteDefault.IsRestricted = util.OptionalBoolOf(*form.Restricted) + } + if form.Visibility != "" { - overwriteDefault = &user_model.CreateUserOverwriteOptions{ - Visibility: api.VisibilityModes[form.Visibility], - } + visibility := api.VisibilityModes[form.Visibility] + overwriteDefault.Visibility = &visibility } if err := user_model.CreateUser(u, overwriteDefault); err != nil { diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index aec2a6d7b2c4..6587037ea3f8 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -183,7 +183,7 @@ func repoAssignment() func(ctx *context.APIContext) { repo.Owner = owner ctx.Repo.Repository = repo - ctx.Repo.Permission, err = models.GetUserRepoPermission(repo, ctx.Doer) + ctx.Repo.Permission, err = models.GetUserRepoPermission(ctx, repo, ctx.Doer) if err != nil { ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err) return @@ -283,6 +283,15 @@ func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) { } } +// reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin +func reqRepoBranchWriter(ctx *context.APIContext) { + options, ok := web.GetForm(ctx).(api.FileOptionInterface) + if !ok || (!ctx.Repo.CanWriteToBranch(ctx.Doer, options.Branch()) && !ctx.IsUserSiteAdmin()) { + ctx.Error(http.StatusForbidden, "reqRepoBranchWriter", "user should have a permission to write to this branch") + return + } +} + // reqRepoReader user should have specific read permission or be a repo admin or a site admin func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) { return func(ctx *context.APIContext) { @@ -801,9 +810,12 @@ func Routes() *web.Route { }, reqToken(), reqAdmin(), reqWebhooksEnabled()) m.Group("/collaborators", func() { m.Get("", reqAnyRepoReader(), repo.ListCollaborators) - m.Combo("/{collaborator}").Get(reqAnyRepoReader(), repo.IsCollaborator). - Put(reqAdmin(), bind(api.AddCollaboratorOption{}), repo.AddCollaborator). - Delete(reqAdmin(), repo.DeleteCollaborator) + m.Group("/{collaborator}", func() { + m.Combo("").Get(reqAnyRepoReader(), repo.IsCollaborator). + Put(reqAdmin(), bind(api.AddCollaboratorOption{}), repo.AddCollaborator). + Delete(reqAdmin(), repo.DeleteCollaborator) + m.Get("/permission", repo.GetRepoPermissions) + }, reqToken()) }, reqToken()) m.Get("/assignees", reqToken(), reqAnyRepoReader(), repo.GetAssignees) m.Get("/reviewers", reqToken(), reqAnyRepoReader(), repo.GetReviewers) @@ -1002,7 +1014,7 @@ func Routes() *web.Route { m.Group("/{ref}", func() { m.Get("/status", repo.GetCombinedCommitStatusByRef) m.Get("/statuses", repo.GetCommitStatusesByRef) - }) + }, context.ReferencesGitRepo()) }, reqRepoReader(unit.TypeCode)) m.Group("/git", func() { m.Group("/commits", func() { @@ -1021,10 +1033,10 @@ func Routes() *web.Route { m.Get("", repo.GetContentsList) m.Get("/*", repo.GetContents) m.Group("/*", func() { - m.Post("", bind(api.CreateFileOptions{}), repo.CreateFile) - m.Put("", bind(api.UpdateFileOptions{}), repo.UpdateFile) - m.Delete("", bind(api.DeleteFileOptions{}), repo.DeleteFile) - }, reqRepoWriter(unit.TypeCode), reqToken()) + m.Post("", bind(api.CreateFileOptions{}), reqRepoBranchWriter, repo.CreateFile) + m.Put("", bind(api.UpdateFileOptions{}), reqRepoBranchWriter, repo.UpdateFile) + m.Delete("", bind(api.DeleteFileOptions{}), reqRepoBranchWriter, repo.DeleteFile) + }, reqToken()) }, reqRepoReader(unit.TypeCode)) m.Get("/signing-key.gpg", misc.SigningKey) m.Group("/topics", func() { @@ -1109,7 +1121,8 @@ func Routes() *web.Route { m.Get("", org.GetTeamRepos) m.Combo("/{org}/{reponame}"). Put(org.AddTeamRepository). - Delete(org.RemoveTeamRepository) + Delete(org.RemoveTeamRepository). + Get(org.GetTeamRepo) }) }, orgAssignment(false, true), reqToken(), reqTeamMembership()) diff --git a/routers/api/v1/auth.go b/routers/api/v1/auth.go index 359c9ec56bcc..becf45f6433f 100644 --- a/routers/api/v1/auth.go +++ b/routers/api/v1/auth.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !windows -// +build !windows package v1 diff --git a/routers/api/v1/misc/nodeinfo.go b/routers/api/v1/misc/nodeinfo.go index bc36fa1be128..ce1f9ec0f76e 100644 --- a/routers/api/v1/misc/nodeinfo.go +++ b/routers/api/v1/misc/nodeinfo.go @@ -6,12 +6,17 @@ package misc import ( "net/http" + "time" + "code.gitea.io/gitea/models" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" ) +const cacheKeyNodeInfoUsage = "API_NodeInfoUsage" + // NodeInfo returns the NodeInfo for the Gitea instance to allow for federation func NodeInfo(ctx *context.APIContext) { // swagger:operation GET /nodeinfo miscellaneous getNodeInfo @@ -23,6 +28,37 @@ func NodeInfo(ctx *context.APIContext) { // "200": // "$ref": "#/responses/NodeInfo" + nodeInfoUsage := structs.NodeInfoUsage{} + if setting.Federation.ShareUserStatistics { + info, ok := ctx.Cache.Get(cacheKeyNodeInfoUsage).(structs.NodeInfoUsage) + if !ok { + usersTotal := int(user_model.CountUsers(nil)) + now := time.Now() + timeOneMonthAgo := now.AddDate(0, -1, 0).Unix() + timeHaveYearAgo := now.AddDate(0, -6, 0).Unix() + usersActiveMonth := int(user_model.CountUsers(&user_model.CountUserFilter{LastLoginSince: &timeOneMonthAgo})) + usersActiveHalfyear := int(user_model.CountUsers(&user_model.CountUserFilter{LastLoginSince: &timeHaveYearAgo})) + + allIssues, _ := models.CountIssues(&models.IssuesOptions{}) + allComments, _ := models.CountComments(&models.FindCommentsOptions{}) + + info = structs.NodeInfoUsage{ + Users: structs.NodeInfoUsageUsers{ + Total: usersTotal, + ActiveMonth: usersActiveMonth, + ActiveHalfyear: usersActiveHalfyear, + }, + LocalPosts: int(allIssues), + LocalComments: int(allComments), + } + if err := ctx.Cache.Put(cacheKeyNodeInfoUsage, nodeInfoUsage, 180); err != nil { + ctx.InternalServerError(err) + return + } + } + nodeInfoUsage = info + } + nodeInfo := &structs.NodeInfo{ Version: "2.1", Software: structs.NodeInfoSoftware{ @@ -34,12 +70,10 @@ func NodeInfo(ctx *context.APIContext) { Protocols: []string{"activitypub"}, Services: structs.NodeInfoServices{ Inbound: []string{}, - Outbound: []string{}, + Outbound: []string{"rss2.0"}, }, OpenRegistrations: setting.Service.ShowRegistrationButton, - Usage: structs.NodeInfoUsage{ - Users: structs.NodeInfoUsageUsers{}, - }, + Usage: nodeInfoUsage, } ctx.JSON(http.StatusOK, nodeInfo) } diff --git a/routers/api/v1/notify/repo.go b/routers/api/v1/notify/repo.go index a36bbc6b428c..0f6b90b05d02 100644 --- a/routers/api/v1/notify/repo.go +++ b/routers/api/v1/notify/repo.go @@ -214,7 +214,7 @@ func ReadRepoNotifications(ctx *context.APIContext) { targetStatus = models.NotificationStatusRead } - changed := make([]*structs.NotificationThread, len(nl)) + changed := make([]*structs.NotificationThread, 0, len(nl)) for _, n := range nl { notif, err := models.SetNotificationStatus(n.ID, ctx.Doer, targetStatus) diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index 322196b8197f..b24c8a623598 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -558,6 +558,55 @@ func GetTeamRepos(ctx *context.APIContext) { ctx.JSON(http.StatusOK, repos) } +// GetTeamRepo api for get a particular repo of team +func GetTeamRepo(ctx *context.APIContext) { + // swagger:operation GET /teams/{id}/repos/{org}/{repo} organization orgListTeamRepo + // --- + // summary: List a particular repo of team + // produces: + // - application/json + // parameters: + // - name: id + // in: path + // description: id of the team + // type: integer + // format: int64 + // required: true + // - name: org + // in: path + // description: organization that owns the repo to list + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo to list + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/Repository" + // "404": + // "$ref": "#/responses/notFound" + + repo := getRepositoryByParams(ctx) + if ctx.Written() { + return + } + + if !organization.HasTeamRepo(ctx, ctx.Org.Team.OrgID, ctx.Org.Team.ID, repo.ID) { + ctx.NotFound() + return + } + + access, err := models.AccessLevel(ctx.Doer, repo) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err) + return + } + + ctx.JSON(http.StatusOK, convert.ToRepo(repo, access)) +} + // getRepositoryByParams get repository by a team's organization ID and repo name func getRepositoryByParams(ctx *context.APIContext) *repo_model.Repository { repo, err := repo_model.GetRepositoryByName(ctx.Org.Team.OrgID, ctx.Params(":reponame")) diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index f22bed813e15..c030a896a792 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -259,10 +259,15 @@ func ListBranches(ctx *context.APIContext) { return } - apiBranches := make([]*api.Branch, len(branches)) + apiBranches := make([]*api.Branch, 0, len(branches)) for i := range branches { c, err := branches[i].GetCommit() if err != nil { + // Skip if this branch doesn't exist anymore. + if git.IsErrNotExist(err) { + totalNumOfBranches-- + continue + } ctx.Error(http.StatusInternalServerError, "GetCommit", err) return } @@ -271,11 +276,12 @@ func ListBranches(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err) return } - apiBranches[i], err = convert.ToBranch(ctx.Repo.Repository, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) + apiBranch, err := convert.ToBranch(ctx.Repo.Repository, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) if err != nil { ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err) return } + apiBranches = append(apiBranches, apiBranch) } ctx.SetLinkHeader(totalNumOfBranches, listOptions.PageSize) @@ -498,7 +504,7 @@ func CreateBranchProtection(ctx *context.APIContext) { BlockOnOutdatedBranch: form.BlockOnOutdatedBranch, } - err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{ + err = models.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, models.WhitelistOptions{ UserIDs: whitelistUsers, TeamIDs: whitelistTeams, MergeUserIDs: mergeWhitelistUsers, @@ -733,7 +739,7 @@ func EditBranchProtection(ctx *context.APIContext) { } } - err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{ + err = models.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, models.WhitelistOptions{ UserIDs: whitelistUsers, TeamIDs: whitelistTeams, MergeUserIDs: mergeWhitelistUsers, diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go index 3bb6113d772a..2db1724b2a9f 100644 --- a/routers/api/v1/repo/collaborators.go +++ b/routers/api/v1/repo/collaborators.go @@ -233,6 +233,61 @@ func DeleteCollaborator(ctx *context.APIContext) { ctx.Status(http.StatusNoContent) } +// GetRepoPermissions gets repository permissions for a user +func GetRepoPermissions(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/collaborators/{collaborator}/permission repository repoGetRepoPermissions + // --- + // summary: Get repository permissions for a user + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // - name: collaborator + // in: path + // description: username of the collaborator + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/RepoCollaboratorPermission" + // "404": + // "$ref": "#/responses/notFound" + // "403": + // "$ref": "#/responses/forbidden" + + if !ctx.Doer.IsAdmin && ctx.Doer.LoginName != ctx.Params(":collaborator") && !ctx.IsUserRepoAdmin() { + ctx.Error(http.StatusForbidden, "User", "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.Params(":collaborator")) + if err != nil { + if user_model.IsErrUserNotExist(err) { + ctx.Error(http.StatusNotFound, "GetUserByName", err) + } else { + ctx.Error(http.StatusInternalServerError, "GetUserByName", err) + } + return + } + + permission, err := models.GetUserRepoPermission(ctx, ctx.Repo.Repository, collaborator) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err) + return + } + + ctx.JSON(http.StatusOK, convert.ToUserAndPermission(collaborator, ctx.ContextUser, permission.AccessMode)) +} + // GetReviewers return all users that can be requested to review in this repo func GetReviewers(ctx *context.APIContext) { // swagger:operation GET /repos/{owner}/{repo}/reviewers repository repoGetReviewers diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go index c79c34ec4296..b196ce97740d 100644 --- a/routers/api/v1/repo/commits.go +++ b/routers/api/v1/repo/commits.go @@ -11,7 +11,6 @@ import ( "net/http" "strconv" - repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" @@ -268,17 +267,12 @@ func DownloadCommitDiffOrPatch(ctx *context.APIContext) { // "$ref": "#/responses/string" // "404": // "$ref": "#/responses/notFound" - repoPath := repo_model.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) - // TODO: use gitRepo from context - if err := git.GetRawDiff( - ctx, - repoPath, - ctx.Params(":sha"), - git.RawDiffType(ctx.Params(":diffType")), - ctx.Resp, - ); err != nil { + sha := ctx.Params(":sha") + diffType := git.RawDiffType(ctx.Params(":diffType")) + + if err := git.GetRawDiff(ctx.Repo.GitRepo, sha, diffType, ctx.Resp); err != nil { if git.IsErrNotExist(err) { - ctx.NotFound(ctx.Params(":sha")) + ctx.NotFound(sha) return } ctx.Error(http.StatusInternalServerError, "DownloadCommitDiffOrPatch", err) diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index ed51f6f4df2a..b8b72f95ebab 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -53,7 +53,7 @@ func GetRawFile(ctx *context.APIContext) { // required: false // responses: // 200: - // description: success + // description: Returns raw file content. // "404": // "$ref": "#/responses/notFound" @@ -173,8 +173,10 @@ func GetEditorconfig(ctx *context.APIContext) { } // canWriteFiles returns true if repository is editable and user has proper access level. -func canWriteFiles(r *context.Repository) bool { - return r.Permission.CanWrite(unit.TypeCode) && !r.Repository.IsMirror && !r.Repository.IsArchived +func canWriteFiles(ctx *context.APIContext, branch string) bool { + return ctx.Repo.Permission.CanWriteToBranch(ctx.Doer, branch) && + !ctx.Repo.Repository.IsMirror && + !ctx.Repo.Repository.IsArchived } // canReadFiles returns true if repository is readable and user has proper access level. @@ -376,7 +378,7 @@ func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) { // Called from both CreateFile or UpdateFile to handle both func createOrUpdateFile(ctx *context.APIContext, opts *files_service.UpdateRepoFileOptions) (*api.FileResponse, error) { - if !canWriteFiles(ctx.Repo) { + if !canWriteFiles(ctx, opts.OldBranch) { return nil, models.ErrUserDoesNotHaveAccessToRepo{ UserID: ctx.Doer.ID, RepoName: ctx.Repo.Repository.LowerName, @@ -433,7 +435,7 @@ func DeleteFile(ctx *context.APIContext) { // "$ref": "#/responses/error" apiOpts := web.GetForm(ctx).(*api.DeleteFileOptions) - if !canWriteFiles(ctx.Repo) { + if !canWriteFiles(ctx, apiOpts.BranchName) { ctx.Error(http.StatusForbidden, "DeleteFile", models.ErrUserDoesNotHaveAccessToRepo{ UserID: ctx.Doer.ID, RepoName: ctx.Repo.Repository.LowerName, diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 083fe8f0b947..9654b270c019 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -175,6 +175,7 @@ func SearchIssues(ctx *context.APIContext) { opts.TeamID = team.ID } + repoCond := models.SearchRepositoryCondition(opts) repoIDs, _, err := models.SearchRepositoryIDs(opts) if err != nil { ctx.Error(http.StatusInternalServerError, "SearchRepositoryByName", err) @@ -235,7 +236,7 @@ func SearchIssues(ctx *context.APIContext) { Page: ctx.FormInt("page"), PageSize: limit, }, - RepoIDs: repoIDs, + RepoCond: repoCond, IsClosed: isClosed, IssueIDs: issueIDs, IncludedLabelNames: includedLabelNames, @@ -462,7 +463,7 @@ func ListIssues(ctx *context.APIContext) { if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 { issuesOpt := &models.IssuesOptions{ ListOptions: listOptions, - RepoIDs: []int64{ctx.Repo.Repository.ID}, + RepoID: ctx.Repo.Repository.ID, IsClosed: isClosed, IssueIDs: issueIDs, LabelIDs: labelIDs, diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go index ef91a2481c27..bc68cb396b97 100644 --- a/routers/api/v1/repo/issue_comment.go +++ b/routers/api/v1/repo/issue_comment.go @@ -6,6 +6,7 @@ package repo import ( + stdCtx "context" "errors" "net/http" @@ -183,7 +184,7 @@ func ListIssueCommentsAndTimeline(ctx *context.APIContext) { var apiComments []*api.TimelineComment for _, comment := range comments { - if comment.Type != models.CommentTypeCode && isXRefCommentAccessible(ctx.Doer, comment, issue.RepoID) { + if comment.Type != models.CommentTypeCode && isXRefCommentAccessible(ctx, ctx.Doer, comment, issue.RepoID) { comment.Issue = issue apiComments = append(apiComments, convert.ToTimelineComment(comment, ctx.Doer)) } @@ -193,16 +194,16 @@ func ListIssueCommentsAndTimeline(ctx *context.APIContext) { ctx.JSON(http.StatusOK, &apiComments) } -func isXRefCommentAccessible(user *user_model.User, c *models.Comment, issueRepoID int64) bool { +func isXRefCommentAccessible(ctx stdCtx.Context, user *user_model.User, c *models.Comment, issueRepoID int64) bool { // Remove comments that the user has no permissions to see if models.CommentTypeIsRef(c.Type) && c.RefRepoID != issueRepoID && c.RefRepoID != 0 { var err error // Set RefRepo for description in template - c.RefRepo, err = repo_model.GetRepositoryByID(c.RefRepoID) + c.RefRepo, err = repo_model.GetRepositoryByIDCtx(ctx, c.RefRepoID) if err != nil { return false } - perm, err := models.GetUserRepoPermission(c.RefRepo, user) + perm, err := models.GetUserRepoPermission(ctx, c.RefRepo, user) if err != nil { return false } diff --git a/routers/api/v1/repo/language.go b/routers/api/v1/repo/language.go index 427a8fd6b544..f47b0a0e7883 100644 --- a/routers/api/v1/repo/language.go +++ b/routers/api/v1/repo/language.go @@ -76,9 +76,7 @@ func GetLanguages(ctx *context.APIContext) { } resp := make(languageResponse, len(langs)) - for i, v := range langs { - resp[i] = v - } + copy(resp, langs) ctx.JSON(http.StatusOK, resp) } diff --git a/routers/api/v1/repo/main_test.go b/routers/api/v1/repo/main_test.go index 19e524d014f7..1f91a2493718 100644 --- a/routers/api/v1/repo/main_test.go +++ b/routers/api/v1/repo/main_test.go @@ -9,10 +9,15 @@ import ( "testing" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/setting" + webhook_service "code.gitea.io/gitea/services/webhook" ) func TestMain(m *testing.M) { + setting.LoadForTest() + setting.NewQueueService() unittest.MainTest(m, &unittest.TestOptions{ GiteaRootPath: filepath.Join("..", "..", "..", ".."), + SetUp: webhook_service.Init, }) } diff --git a/routers/api/v1/repo/patch.go b/routers/api/v1/repo/patch.go index ae64c6efe3aa..6dbf979701fc 100644 --- a/routers/api/v1/repo/patch.go +++ b/routers/api/v1/repo/patch.go @@ -77,7 +77,7 @@ func ApplyDiffPatch(ctx *context.APIContext) { opts.Message = "apply-patch" } - if !canWriteFiles(ctx.Repo) { + if !canWriteFiles(ctx, apiOpts.BranchName) { ctx.Error(http.StatusInternalServerError, "ApplyPatch", models.ErrUserDoesNotHaveAccessToRepo{ UserID: ctx.Doer.ID, RepoName: ctx.Repo.Repository.LowerName, diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 94262f81d187..d6f349e332c1 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -111,11 +111,11 @@ func ListPullRequests(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) return } - if err = prs[i].LoadBaseRepo(); err != nil { + if err = prs[i].LoadBaseRepoCtx(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err) return } - if err = prs[i].LoadHeadRepo(); err != nil { + if err = prs[i].LoadHeadRepoCtx(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err) return } @@ -167,11 +167,11 @@ func GetPullRequest(ctx *context.APIContext) { return } - if err = pr.LoadBaseRepo(); err != nil { + if err = pr.LoadBaseRepoCtx(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err) return } - if err = pr.LoadHeadRepo(); err != nil { + if err = pr.LoadHeadRepoCtx(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err) return } @@ -616,6 +616,18 @@ func EditPullRequest(ctx *context.APIContext) { notification.NotifyPullRequestChangeTargetBranch(ctx.Doer, pr, form.Base) } + // update allow edits + if form.AllowMaintainerEdit != nil { + if err := pull_service.SetAllowEdits(ctx, ctx.Doer, pr, *form.AllowMaintainerEdit); err != nil { + if errors.Is(pull_service.ErrUserHasNoPermissionForAction, err) { + ctx.Error(http.StatusForbidden, "SetAllowEdits", fmt.Sprintf("SetAllowEdits: %s", err)) + return + } + ctx.ServerError("SetAllowEdits", err) + return + } + } + // Refetch from database pr, err = models.GetPullRequestByIndex(ctx.Repo.Repository.ID, pr.Index) if err != nil { @@ -714,7 +726,8 @@ func MergePullRequest(ctx *context.APIContext) { // "$ref": "#/responses/error" form := web.GetForm(ctx).(*forms.MergePullRequestForm) - pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) + + pr, err := models.GetPullRequestByIndexCtx(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrPullRequestNotExist(err) { ctx.NotFound("GetPullRequestByIndex", err) @@ -724,12 +737,12 @@ func MergePullRequest(ctx *context.APIContext) { return } - if err := pr.LoadHeadRepo(); err != nil { + if err := pr.LoadHeadRepoCtx(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err) return } - if err := pr.LoadIssue(); err != nil { + if err := pr.LoadIssueCtx(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "LoadIssue", err) return } @@ -737,7 +750,7 @@ func MergePullRequest(ctx *context.APIContext) { if ctx.IsSigned { // Update issue-user. - if err = pr.Issue.ReadBy(ctx.Doer.ID); err != nil { + if err = pr.Issue.ReadBy(ctx, ctx.Doer.ID); err != nil { ctx.Error(http.StatusInternalServerError, "ReadBy", err) return } @@ -746,6 +759,7 @@ func MergePullRequest(ctx *context.APIContext) { manuallMerge := repo_model.MergeStyle(form.Do) == repo_model.MergeStyleManuallyMerged force := form.ForceMerge != nil && *form.ForceMerge + // start with merging by checking if err := pull_service.CheckPullMergable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, manuallMerge, force); err != nil { if errors.Is(err, pull_service.ErrIsClosed) { ctx.NotFound() @@ -786,15 +800,14 @@ func MergePullRequest(ctx *context.APIContext) { } // set defaults to propagate needed fields - if err := form.SetDefaults(pr); err != nil { + if err := form.SetDefaults(ctx, pr); err != nil { ctx.ServerError("SetDefaults", fmt.Errorf("SetDefaults: %v", err)) return } - if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, form.MergeTitleField); err != nil { + if err := pull_service.Merge(pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, form.MergeTitleField); err != nil { if models.IsErrInvalidMergeStyle(err) { ctx.Error(http.StatusMethodNotAllowed, "Invalid merge style", fmt.Errorf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do))) - return } else if models.IsErrMergeConflicts(err) { conflictError := err.(models.ErrMergeConflicts) ctx.JSON(http.StatusConflict, conflictError) @@ -806,28 +819,25 @@ func MergePullRequest(ctx *context.APIContext) { ctx.JSON(http.StatusConflict, conflictError) } else if git.IsErrPushOutOfDate(err) { ctx.Error(http.StatusConflict, "Merge", "merge push out of date") - return } else if models.IsErrSHADoesNotMatch(err) { ctx.Error(http.StatusConflict, "Merge", "head out of date") - return } else if git.IsErrPushRejected(err) { errPushRej := err.(*git.ErrPushRejected) if len(errPushRej.Message) == 0 { ctx.Error(http.StatusConflict, "Merge", "PushRejected without remote error message") - return + } else { + ctx.Error(http.StatusConflict, "Merge", "PushRejected with remote message: "+errPushRej.Message) } - ctx.Error(http.StatusConflict, "Merge", "PushRejected with remote message: "+errPushRej.Message) - return + } else { + ctx.Error(http.StatusInternalServerError, "Merge", err) } - ctx.Error(http.StatusInternalServerError, "Merge", err) return } - log.Trace("Pull request merged: %d", pr.ID) if form.DeleteBranchAfterMerge { // Don't cleanup when there are other PR's that use this branch as head branch. - exist, err := models.HasUnmergedPullRequestsByHeadInfo(pr.HeadRepoID, pr.HeadBranch) + exist, err := models.HasUnmergedPullRequestsByHeadInfo(ctx, pr.HeadRepoID, pr.HeadBranch) if err != nil { ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err) return @@ -861,7 +871,7 @@ func MergePullRequest(ctx *context.APIContext) { } return } - if err := models.AddDeletePRBranchComment(ctx.Doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil { + if err := models.AddDeletePRBranchComment(ctx, ctx.Doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil { // Do not fail here as branch has already been deleted log.Error("DeleteBranch: %v", err) } @@ -943,7 +953,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) } // user should have permission to read baseRepo's codes and pulls, NOT headRepo's - permBase, err := models.GetUserRepoPermission(baseRepo, ctx.Doer) + permBase, err := models.GetUserRepoPermission(ctx, baseRepo, ctx.Doer) if err != nil { headGitRepo.Close() ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err) @@ -962,7 +972,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) } // user should have permission to read headrepo's codes - permHead, err := models.GetUserRepoPermission(headRepo, ctx.Doer) + permHead, err := models.GetUserRepoPermission(ctx, headRepo, ctx.Doer) if err != nil { headGitRepo.Close() ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err) @@ -1063,18 +1073,18 @@ func UpdatePullRequest(ctx *context.APIContext) { return } - if err = pr.LoadBaseRepo(); err != nil { + if err = pr.LoadBaseRepoCtx(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err) return } - if err = pr.LoadHeadRepo(); err != nil { + if err = pr.LoadHeadRepoCtx(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err) return } rebase := ctx.FormString("style") == "rebase" - allowedUpdateByMerge, allowedUpdateByRebase, err := pull_service.IsUserAllowedToUpdate(pr, ctx.Doer) + allowedUpdateByMerge, allowedUpdateByRebase, err := pull_service.IsUserAllowedToUpdate(ctx, pr, ctx.Doer) if err != nil { ctx.Error(http.StatusInternalServerError, "IsUserAllowedToMerge", err) return @@ -1151,7 +1161,7 @@ func GetPullRequestCommits(ctx *context.APIContext) { return } - if err := pr.LoadBaseRepo(); err != nil { + if err := pr.LoadBaseRepoCtx(ctx); err != nil { ctx.InternalServerError(err) return } diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go index 3b36f28326f5..b3ebe49bf512 100644 --- a/routers/api/v1/repo/pull_review.go +++ b/routers/api/v1/repo/pull_review.go @@ -664,7 +664,7 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions reviewers := make([]*user_model.User, 0, len(opts.Reviewers)) - permDoer, err := models.GetUserRepoPermission(pr.Issue.Repo, ctx.Doer) + permDoer, err := models.GetUserRepoPermission(ctx, pr.Issue.Repo, ctx.Doer) if err != nil { ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err) return @@ -687,7 +687,7 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions return } - err = issue_service.IsValidReviewRequest(reviewer, ctx.Doer, isAdd, pr.Issue, &permDoer) + err = issue_service.IsValidReviewRequest(ctx, reviewer, ctx.Doer, isAdd, pr.Issue, &permDoer) if err != nil { if models.IsErrNotValidReviewRequest(err) { ctx.Error(http.StatusUnprocessableEntity, "NotValidReviewRequest", err) @@ -736,7 +736,7 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions return } - err = issue_service.IsValidTeamReviewRequest(teamReviewer, ctx.Doer, isAdd, pr.Issue) + err = issue_service.IsValidTeamReviewRequest(ctx, teamReviewer, ctx.Doer, isAdd, pr.Issue) if err != nil { if models.IsErrNotValidReviewRequest(err) { ctx.Error(http.StatusUnprocessableEntity, "NotValidReviewRequest", err) diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 16942dd3bae8..29e83521420f 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -218,7 +218,6 @@ func Search(ctx *context.APIContext) { } results[i] = convert.ToRepo(repo, accessMode) } - ctx.SetLinkHeader(int(count), opts.PageSize) ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, api.SearchResults{ @@ -556,7 +555,7 @@ func GetByID(ctx *context.APIContext) { return } - perm, err := models.GetUserRepoPermission(repo, ctx.Doer) + perm, err := models.GetUserRepoPermission(ctx, repo, ctx.Doer) if err != nil { ctx.Error(http.StatusInternalServerError, "AccessLevel", err) return diff --git a/routers/api/v1/swagger/repo.go b/routers/api/v1/swagger/repo.go index 40aeca677de8..ab802db7812f 100644 --- a/routers/api/v1/swagger/repo.go +++ b/routers/api/v1/swagger/repo.go @@ -344,3 +344,10 @@ type swaggerWikiCommitList struct { // in:body Body api.WikiCommitList `json:"body"` } + +// RepoCollaboratorPermission +// swagger:response RepoCollaboratorPermission +type swaggerRepoCollaboratorPermission struct { + // in:body + Body api.RepoCollaboratorPermission `json:"body"` +} diff --git a/routers/api/v1/utils/git.go b/routers/api/v1/utils/git.go index 9f02bc8083f9..ac64d5b87bfe 100644 --- a/routers/api/v1/utils/git.go +++ b/routers/api/v1/utils/git.go @@ -5,6 +5,7 @@ package utils import ( + "fmt" "net/http" "code.gitea.io/gitea/modules/context" @@ -35,12 +36,7 @@ func ResolveRefOrSha(ctx *context.APIContext, ref string) string { // GetGitRefs return git references based on filter func GetGitRefs(ctx *context.APIContext, filter string) ([]*git.Reference, string, error) { if ctx.Repo.GitRepo == nil { - var err error - ctx.Repo.GitRepo, err = git.OpenRepository(ctx, ctx.Repo.Repository.RepoPath()) - if err != nil { - return nil, "OpenRepository", err - } - defer ctx.Repo.GitRepo.Close() + return nil, "", fmt.Errorf("no open git repo found in context") } if len(filter) > 0 { filter = "refs/" + filter diff --git a/routers/init.go b/routers/init.go index 88c393736ef4..403fab00cd3b 100644 --- a/routers/init.go +++ b/routers/init.go @@ -145,7 +145,7 @@ func GlobalInitInstalled(ctx context.Context) { mustInit(stats_indexer.Init) mirror_service.InitSyncMirrors() - webhook.InitDeliverHooks() + mustInit(webhook.Init) mustInit(pull_service.Init) mustInit(task.Init) mustInit(repo_migrations.Init) diff --git a/routers/install/install.go b/routers/install/install.go index ec1719439f53..459f534b8974 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -134,6 +134,7 @@ func Install(ctx *context.Context) { form.SMTPHost = setting.MailService.Host form.SMTPFrom = setting.MailService.From form.SMTPUser = setting.MailService.User + form.SMTPPasswd = setting.MailService.Passwd } form.RegisterConfirm = setting.Service.RegisterEmailConfirm form.MailNotify = setting.Service.EnableNotifyMail @@ -499,13 +500,17 @@ func SubmitInstall(ctx *context.Context) { // Create admin account if len(form.AdminName) > 0 { u := &user_model.User{ - Name: form.AdminName, - Email: form.AdminEmail, - Passwd: form.AdminPasswd, - IsAdmin: true, - IsActive: true, + Name: form.AdminName, + Email: form.AdminEmail, + Passwd: form.AdminPasswd, + IsAdmin: true, } - if err = user_model.CreateUser(u); err != nil { + overwriteDefault := &user_model.CreateUserOverwriteOptions{ + IsRestricted: util.OptionalBoolFalse, + IsActive: util.OptionalBoolTrue, + } + + if err = user_model.CreateUser(u, overwriteDefault); err != nil { if !user_model.IsErrUserAlreadyExist(err) { setting.InstallLock = false ctx.Data["Err_AdminName"] = true diff --git a/routers/install/routes.go b/routers/install/routes.go index ef96e99628ef..e77081afe0a8 100644 --- a/routers/install/routes.go +++ b/routers/install/routes.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/routers/common" + "code.gitea.io/gitea/routers/web/healthcheck" "code.gitea.io/gitea/services/forms" "gitea.com/go-chi/session" @@ -106,6 +107,7 @@ func Routes() *web.Route { r.Use(Init) r.Get("/", Install) r.Post("/", web.Bind(forms.InstallForm{}), SubmitInstall) + r.Get("/api/healthz", healthcheck.Check) r.NotFound(web.Wrap(installNotFound)) return r diff --git a/routers/install/routes_test.go b/routers/install/routes_test.go index 35a66c1c4742..29003c3841be 100644 --- a/routers/install/routes_test.go +++ b/routers/install/routes_test.go @@ -13,7 +13,6 @@ import ( func TestRoutes(t *testing.T) { routes := Routes() assert.NotNil(t, routes) - assert.Len(t, routes.R.Routes(), 1) assert.EqualValues(t, "/", routes.R.Routes()[0].Pattern) assert.Nil(t, routes.R.Routes()[0].SubRoutes) assert.Len(t, routes.R.Routes()[0].Handlers, 2) diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index 763fe1cf1c55..d2203a1f99a2 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -45,6 +45,8 @@ type preReceiveContext struct { env []string opts *private.HookOptions + + branchName string } // CanWriteCode returns true if pusher can write code @@ -53,7 +55,7 @@ func (ctx *preReceiveContext) CanWriteCode() bool { if !ctx.loadPusherAndPermission() { return false } - ctx.canWriteCode = ctx.userPerm.CanWrite(unit.TypeCode) || ctx.deployKeyAccessMode >= perm_model.AccessModeWrite + ctx.canWriteCode = ctx.userPerm.CanWriteToBranch(ctx.user, ctx.branchName) || ctx.deployKeyAccessMode >= perm_model.AccessModeWrite ctx.checkedCanWriteCode = true } return ctx.canWriteCode @@ -134,13 +136,15 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) { } func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID, refFullName string) { + branchName := strings.TrimPrefix(refFullName, git.BranchPrefix) + ctx.branchName = branchName + if !ctx.AssertCanWriteCode() { return } repo := ctx.Repo.Repository gitRepo := ctx.Repo.GitRepo - branchName := strings.TrimPrefix(refFullName, git.BranchPrefix) if branchName == repo.DefaultBranch && newCommitID == git.EmptySHA { log.Warn("Forbidden: Branch: %s is the default branch in %-v and cannot be deleted", branchName, repo) @@ -290,7 +294,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID, refFullN // 6b. Merge (from UI or API) // Get the PR, user and permissions for the user in the repository - pr, err := models.GetPullRequestByID(ctx.opts.PullRequestID) + pr, err := models.GetPullRequestByID(ctx, ctx.opts.PullRequestID) if err != nil { log.Error("Unable to get PullRequest %d Error: %v", ctx.opts.PullRequestID, err) ctx.JSON(http.StatusInternalServerError, private.Response{ @@ -307,7 +311,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID, refFullN // Now check if the user is allowed to merge PRs for this repository // Note: we can use ctx.perm and ctx.user directly as they will have been loaded above - allowedMerge, err := pull_service.IsUserAllowedToMerge(pr, ctx.userPerm, ctx.user) + allowedMerge, err := pull_service.IsUserAllowedToMerge(ctx, pr, ctx.userPerm, ctx.user) if err != nil { log.Error("Error calculating if allowed to merge: %v", err) ctx.JSON(http.StatusInternalServerError, private.Response{ @@ -339,7 +343,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID, refFullN } // Check all status checks and reviews are ok - if err := pull_service.CheckPRReadyToMerge(ctx, pr, true); err != nil { + if err := pull_service.CheckPullBranchProtections(ctx, pr, true); err != nil { if models.IsErrDisallowedToMerge(err) { log.Warn("Forbidden: User %d is not allowed push to protected branch %s in %-v and pr #%d is not ready to be merged: %s", ctx.opts.UserID, branchName, repo, pr.Index, err.Error()) ctx.JSON(http.StatusForbidden, private.Response{ @@ -468,7 +472,7 @@ func (ctx *preReceiveContext) loadPusherAndPermission() bool { } ctx.user = user - userPerm, err := models.GetUserRepoPermission(ctx.Repo.Repository, user) + userPerm, err := models.GetUserRepoPermission(ctx, ctx.Repo.Repository, user) if err != nil { log.Error("Unable to get Repo permission of repo %s/%s of User %s", ctx.Repo.Repository.OwnerName, ctx.Repo.Repository.Name, user.Name, err) ctx.JSON(http.StatusInternalServerError, private.Response{ diff --git a/routers/private/manager_unix.go b/routers/private/manager_unix.go index 402bade5d412..43cbdec01c02 100644 --- a/routers/private/manager_unix.go +++ b/routers/private/manager_unix.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !windows -// +build !windows package private diff --git a/routers/private/manager_windows.go b/routers/private/manager_windows.go index 014018a5395a..2b72ee952d4e 100644 --- a/routers/private/manager_windows.go +++ b/routers/private/manager_windows.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build windows -// +build windows package private diff --git a/routers/private/serv.go b/routers/private/serv.go index b0451df5d85e..6ef0079a2be1 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -320,7 +320,7 @@ func ServCommand(ctx *context.PrivateContext) { mode = perm.AccessModeRead } - perm, err := models.GetUserRepoPermission(repo, user) + perm, err := models.GetUserRepoPermission(ctx, repo, user) if err != nil { log.Error("Unable to get permissions for %-v with key %d in %-v Error: %v", user, key.ID, repo, err) ctx.JSON(http.StatusInternalServerError, private.ErrServCommand{ diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index fcfea5380128..57da319d794b 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -125,10 +125,14 @@ func NewUserPost(ctx *context.Context) { Name: form.UserName, Email: form.Email, Passwd: form.Password, - IsActive: true, LoginType: auth.Plain, } + overwriteDefault := &user_model.CreateUserOverwriteOptions{ + IsActive: util.OptionalBoolTrue, + Visibility: &form.Visibility, + } + if len(form.LoginType) > 0 { fields := strings.Split(form.LoginType, "-") if len(fields) == 2 { @@ -163,7 +167,7 @@ func NewUserPost(ctx *context.Context) { u.MustChangePassword = form.MustChangePassword } - if err := user_model.CreateUser(u, &user_model.CreateUserOverwriteOptions{Visibility: form.Visibility}); err != nil { + if err := user_model.CreateUser(u, overwriteDefault); err != nil { switch { case user_model.IsErrUserAlreadyExist(err): ctx.Data["Err_UserName"] = true diff --git a/routers/web/auth.go b/routers/web/auth.go index 4a7fb856be2e..a771643b663d 100644 --- a/routers/web/auth.go +++ b/routers/web/auth.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !windows -// +build !windows package web diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index c82fde49eb2f..be936d2230f5 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -507,14 +507,12 @@ func SignUpPost(ctx *context.Context) { } u := &user_model.User{ - Name: form.UserName, - Email: form.Email, - Passwd: form.Password, - IsActive: !(setting.Service.RegisterEmailConfirm || setting.Service.RegisterManualConfirm), - IsRestricted: setting.Service.DefaultUserIsRestricted, + Name: form.UserName, + Email: form.Email, + Passwd: form.Password, } - if !createAndHandleCreatedUser(ctx, tplSignUp, form, u, nil, false) { + if !createAndHandleCreatedUser(ctx, tplSignUp, form, u, nil, nil, false) { // error already handled return } @@ -525,8 +523,8 @@ func SignUpPost(ctx *context.Context) { // createAndHandleCreatedUser calls createUserInContext and // then handleUserCreated. -func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form interface{}, u *user_model.User, gothUser *goth.User, allowLink bool) bool { - if !createUserInContext(ctx, tpl, form, u, gothUser, allowLink) { +func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form interface{}, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) bool { + if !createUserInContext(ctx, tpl, form, u, overwrites, gothUser, allowLink) { return false } return handleUserCreated(ctx, u, gothUser) @@ -534,8 +532,8 @@ func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form int // createUserInContext creates a user and handles errors within a given context. // Optionally a template can be specified. -func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{}, u *user_model.User, gothUser *goth.User, allowLink bool) (ok bool) { - if err := user_model.CreateUser(u); err != nil { +func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{}, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) (ok bool) { + if err := user_model.CreateUser(u, overwrites); err != nil { if allowLink && (user_model.IsErrUserAlreadyExist(err) || user_model.IsErrEmailAlreadyUsed(err)) { if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingAuto { var user *user_model.User @@ -602,7 +600,7 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{ // sends a confirmation email if required. func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User) (ok bool) { // Auto-set admin for the only user. - if user_model.CountUsers() == 1 { + if user_model.CountUsers(nil) == 1 { u.IsAdmin = true u.IsActive = true u.SetLastLogin() diff --git a/routers/web/auth/linkaccount.go b/routers/web/auth/linkaccount.go index bf5fb83265ba..c3e96f077a87 100644 --- a/routers/web/auth/linkaccount.go +++ b/routers/web/auth/linkaccount.go @@ -283,13 +283,12 @@ func LinkAccountPostRegister(ctx *context.Context) { Name: form.UserName, Email: form.Email, Passwd: form.Password, - IsActive: !(setting.Service.RegisterEmailConfirm || setting.Service.RegisterManualConfirm), LoginType: auth.OAuth2, LoginSource: authSource.ID, LoginName: gothUser.UserID, } - if !createAndHandleCreatedUser(ctx, tplLinkAccount, form, u, &gothUser, false) { + if !createAndHandleCreatedUser(ctx, tplLinkAccount, form, u, nil, &gothUser, false) { // error already handled return } diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 12de208ad7bc..4c3e3c3ace39 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -24,6 +24,7 @@ import ( "code.gitea.io/gitea/modules/session" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" auth_service "code.gitea.io/gitea/services/auth" @@ -867,19 +868,21 @@ func SignInOAuthCallback(ctx *context.Context) { return } u = &user_model.User{ - Name: getUserName(&gothUser), - FullName: gothUser.Name, - Email: gothUser.Email, - IsActive: !setting.OAuth2Client.RegisterEmailConfirm, - LoginType: auth.OAuth2, - LoginSource: authSource.ID, - LoginName: gothUser.UserID, - IsRestricted: setting.Service.DefaultUserIsRestricted, + Name: getUserName(&gothUser), + FullName: gothUser.Name, + Email: gothUser.Email, + LoginType: auth.OAuth2, + LoginSource: authSource.ID, + LoginName: gothUser.UserID, + } + + overwriteDefault := &user_model.CreateUserOverwriteOptions{ + IsActive: util.OptionalBoolOf(!setting.OAuth2Client.RegisterEmailConfirm), } setUserGroupClaims(authSource, u, &gothUser) - if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) { + if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) { // error already handled return } diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go index f3189887a530..3012d8c5a55e 100644 --- a/routers/web/auth/openid.go +++ b/routers/web/auth/openid.go @@ -423,12 +423,11 @@ func RegisterOpenIDPost(ctx *context.Context) { } u := &user_model.User{ - Name: form.UserName, - Email: form.Email, - Passwd: password, - IsActive: !(setting.Service.RegisterEmailConfirm || setting.Service.RegisterManualConfirm), + Name: form.UserName, + Email: form.Email, + Passwd: password, } - if !createUserInContext(ctx, tplSignUpOID, form, u, nil, false) { + if !createUserInContext(ctx, tplSignUpOID, form, u, nil, nil, false) { // error already handled return } diff --git a/routers/web/events/events.go b/routers/web/events/events.go index 02d20550afcd..d8c6f38d02ae 100644 --- a/routers/web/events/events.go +++ b/routers/web/events/events.go @@ -8,15 +8,10 @@ import ( "net/http" "time" - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" - "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/eventsource" "code.gitea.io/gitea/modules/graceful" - "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/routers/web/auth" ) @@ -71,8 +66,6 @@ func Events(ctx *context.Context) { timer := time.NewTicker(30 * time.Second) - stopwatchTimer := time.NewTicker(setting.UI.Notification.MinTimeout) - loop: for { select { @@ -93,32 +86,6 @@ loop: case <-shutdownCtx.Done(): go unregister() break loop - case <-stopwatchTimer.C: - sws, err := models.GetUserStopwatches(ctx.Doer.ID, db.ListOptions{}) - if err != nil { - log.Error("Unable to GetUserStopwatches: %v", err) - continue - } - apiSWs, err := convert.ToStopWatches(sws) - if err != nil { - log.Error("Unable to APIFormat stopwatches: %v", err) - continue - } - dataBs, err := json.Marshal(apiSWs) - if err != nil { - log.Error("Unable to marshal stopwatches: %v", err) - continue - } - _, err = (&eventsource.Event{ - Name: "stopwatches", - Data: string(dataBs), - }).WriteTo(ctx.Resp) - if err != nil { - log.Error("Unable to write to EventStream for user %s: %v", ctx.Doer.Name, err) - go unregister() - break loop - } - ctx.Resp.Flush() case event, ok := <-messageChan: if !ok { break loop diff --git a/routers/web/healthcheck/check.go b/routers/web/healthcheck/check.go new file mode 100644 index 000000000000..481f05c0da7e --- /dev/null +++ b/routers/web/healthcheck/check.go @@ -0,0 +1,143 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package healthcheck + +import ( + "net/http" + "os" + "time" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/cache" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" +) + +type status string + +const ( + // pass healthy (acceptable aliases: "ok" to support Node's Terminus and "up" for Java's SpringBoot) + // fail unhealthy (acceptable aliases: "error" to support Node's Terminus and "down" for Java's SpringBoot), and + // warn healthy, with some concerns. + // + // ref https://datatracker.ietf.org/doc/html/draft-inadarei-api-health-check#section-3.1 + // status: (required) indicates whether the service status is acceptable + // or not. API publishers SHOULD use following values for the field: + // The value of the status field is case-insensitive and is tightly + // related with the HTTP response code returned by the health endpoint. + // For "pass" status, HTTP response code in the 2xx-3xx range MUST be + // used. For "fail" status, HTTP response code in the 4xx-5xx range + // MUST be used. In case of the "warn" status, endpoints MUST return + // HTTP status in the 2xx-3xx range, and additional information SHOULD + // be provided, utilizing optional fields of the response. + pass status = "pass" + fail status = "fail" + warn status = "warn" +) + +func (s status) ToHTTPStatus() int { + if s == pass || s == warn { + return http.StatusOK + } + return http.StatusFailedDependency +} + +type checks map[string][]componentStatus + +// response is the data returned by the health endpoint, which will be marshaled to JSON format +type response struct { + Status status `json:"status"` + Description string `json:"description"` // a human-friendly description of the service + Checks checks `json:"checks,omitempty"` // The Checks Object, should be omitted on installation route +} + +// componentStatus presents one status of a single check object +// an object that provides detailed health statuses of additional downstream systems and endpoints +// which can affect the overall health of the main API. +type componentStatus struct { + Status status `json:"status"` + Time string `json:"time"` // the date-time, in ISO8601 format + Output string `json:"output,omitempty"` // this field SHOULD be omitted for "pass" state. +} + +// Check is the health check API handler +func Check(w http.ResponseWriter, r *http.Request) { + rsp := response{ + Status: pass, + Description: setting.AppName, + Checks: make(checks), + } + + statuses := make([]status, 0) + if setting.InstallLock { + statuses = append(statuses, checkDatabase(rsp.Checks)) + statuses = append(statuses, checkCache(rsp.Checks)) + } + for _, s := range statuses { + if s != pass { + rsp.Status = fail + break + } + } + + data, _ := json.MarshalIndent(rsp, "", " ") + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(rsp.Status.ToHTTPStatus()) + _, _ = w.Write(data) +} + +// database checks gitea database status +func checkDatabase(checks checks) status { + st := componentStatus{} + if err := db.GetEngine(db.DefaultContext).Ping(); err != nil { + st.Status = fail + st.Time = getCheckTime() + log.Error("database ping failed with error: %v", err) + } else { + st.Status = pass + st.Time = getCheckTime() + } + + if setting.Database.UseSQLite3 && st.Status == pass { + if !setting.EnableSQLite3 { + st.Status = fail + st.Time = getCheckTime() + log.Error("SQLite3 health check failed with error: %v", "this Gitea binary is built without SQLite3 enabled") + } else { + if _, err := os.Stat(setting.Database.Path); err != nil { + st.Status = fail + st.Time = getCheckTime() + log.Error("SQLite3 file exists check failed with error: %v", err) + } + } + } + + checks["database:ping"] = []componentStatus{st} + return st.Status +} + +// cache checks gitea cache status +func checkCache(checks checks) status { + if !setting.CacheService.Enabled { + return pass + } + + st := componentStatus{} + if err := cache.Ping(); err != nil { + st.Status = fail + st.Time = getCheckTime() + log.Error("cache ping failed with error: %v", err) + } else { + st.Status = pass + st.Time = getCheckTime() + } + checks["cache:ping"] = []componentStatus{st} + return st.Status +} + +func getCheckTime() string { + return time.Now().UTC().Format(time.RFC3339) +} diff --git a/routers/web/repo/attachment.go b/routers/web/repo/attachment.go index be5b5812d380..c930311f70f2 100644 --- a/routers/web/repo/attachment.go +++ b/routers/web/repo/attachment.go @@ -106,7 +106,7 @@ func GetAttachment(ctx *context.Context) { return } } else { // If we have the repository we check access - perm, err := models.GetUserRepoPermission(repository, ctx.Doer) + perm, err := models.GetUserRepoPermission(ctx, repository, ctx.Doer) if err != nil { ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err.Error()) return diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index 0d139ec79c48..732b9c9d5473 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -279,7 +279,7 @@ func loadOneBranch(ctx *context.Context, rawBranch, defaultBranch *git.Branch, p } if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok { pr.BaseRepo = repo - } else if err := pr.LoadBaseRepo(); err != nil { + } else if err := pr.LoadBaseRepoCtx(ctx); err != nil { ctx.ServerError("pr.LoadBaseRepo", err) return nil } else { diff --git a/routers/web/repo/cherry_pick.go b/routers/web/repo/cherry_pick.go index 926361ccd768..6667d2741087 100644 --- a/routers/web/repo/cherry_pick.go +++ b/routers/web/repo/cherry_pick.go @@ -151,7 +151,7 @@ func CherryPickPost(ctx *context.Context) { return } } else { - if err := git.GetRawDiff(ctx, ctx.Repo.Repository.RepoPath(), sha, git.RawDiffType("patch"), buf); err != nil { + if err := git.GetRawDiff(ctx.Repo.GitRepo, sha, git.RawDiffType("patch"), buf); err != nil { if git.IsErrNotExist(err) { ctx.NotFound("GetRawDiff", errors.New("commit "+ctx.Params(":sha")+" does not exist.")) return diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 2b1b5440d04a..59d818672a8e 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -7,13 +7,13 @@ package repo import ( "errors" + "fmt" "net/http" "strings" "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" - repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/charset" @@ -381,15 +381,24 @@ func Diff(ctx *context.Context) { // RawDiff dumps diff results of repository in given commit ID to io.Writer func RawDiff(ctx *context.Context) { - var repoPath string + var gitRepo *git.Repository if ctx.Data["PageIsWiki"] != nil { - repoPath = ctx.Repo.Repository.WikiPath() + wikiRepo, err := git.OpenRepository(ctx, ctx.Repo.Repository.WikiPath()) + if err != nil { + ctx.ServerError("OpenRepository", err) + return + } + defer wikiRepo.Close() + gitRepo = wikiRepo } else { - repoPath = repo_model.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) + gitRepo = ctx.Repo.GitRepo + if gitRepo == nil { + ctx.ServerError("GitRepo not open", fmt.Errorf("no open git repo for '%s'", ctx.Repo.Repository.FullName())) + return + } } if err := git.GetRawDiff( - ctx, - repoPath, + gitRepo, ctx.Params(":sha"), git.RawDiffType(ctx.Params(":ext")), ctx.Resp, diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 60c6ae0298c2..d483227ebf29 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -143,6 +143,11 @@ func setCsvCompareContext(ctx *context.Context) { if err == errTooLarge { return CsvDiffResult{nil, err.Error()} } + if err != nil { + log.Error("CreateCsvDiff error whilst creating baseReader from file %s in commit %s in %s: %v", diffFile.Name, baseCommit.ID.String(), ctx.Repo.Repository.Name, err) + return CsvDiffResult{nil, "unable to load file from base commit"} + } + headReader, headBlobCloser, err := csvReaderFromCommit(&markup.RenderContext{Ctx: ctx, Filename: diffFile.Name}, headCommit) if headBlobCloser != nil { defer headBlobCloser.Close() @@ -150,13 +155,17 @@ func setCsvCompareContext(ctx *context.Context) { if err == errTooLarge { return CsvDiffResult{nil, err.Error()} } + if err != nil { + log.Error("CreateCsvDiff error whilst creating headReader from file %s in commit %s in %s: %v", diffFile.Name, headCommit.ID.String(), ctx.Repo.Repository.Name, err) + return CsvDiffResult{nil, "unable to load file from head commit"} + } sections, err := gitdiff.CreateCsvDiff(diffFile, baseReader, headReader) if err != nil { errMessage, err := csv_module.FormatError(err, ctx.Locale) if err != nil { - log.Error("RenderCsvDiff failed: %v", err) - return CsvDiffResult{nil, ""} + log.Error("CreateCsvDiff FormatError failed: %v", err) + return CsvDiffResult{nil, "unknown csv diff error"} } return CsvDiffResult{nil, errMessage} } @@ -398,11 +407,12 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { } ctx.Data["HeadRepo"] = ci.HeadRepo + ctx.Data["BaseCompareRepo"] = ctx.Repo.Repository // Now we need to assert that the ctx.Doer has permission to read // the baseRepo's code and pulls // (NOT headRepo's) - permBase, err := models.GetUserRepoPermission(baseRepo, ctx.Doer) + permBase, err := models.GetUserRepoPermission(ctx, baseRepo, ctx.Doer) if err != nil { ctx.ServerError("GetUserRepoPermission", err) return nil @@ -421,7 +431,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { // If we're not merging from the same repo: if !isSameRepo { // Assert ctx.Doer has permission to read headRepo's codes - permHead, err := models.GetUserRepoPermission(ci.HeadRepo, ctx.Doer) + permHead, err := models.GetUserRepoPermission(ctx, ci.HeadRepo, ctx.Doer) if err != nil { ctx.ServerError("GetUserRepoPermission", err) return nil @@ -436,6 +446,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { ctx.NotFound("ParseCompareInfo", nil) return nil } + ctx.Data["CanWriteToHeadRepo"] = permHead.CanWrite(unit.TypeCode) } // If we have a rootRepo and it's different from: diff --git a/routers/web/repo/http.go b/routers/web/repo/http.go index 1306a5436945..cc44c8e7e4fb 100644 --- a/routers/web/repo/http.go +++ b/routers/web/repo/http.go @@ -181,7 +181,7 @@ func httpBase(ctx *context.Context) (h *serviceHandler) { } if repoExist { - p, err := models.GetUserRepoPermission(repo, ctx.Doer) + p, err := models.GetUserRepoPermission(ctx, repo, ctx.Doer) if err != nil { ctx.ServerError("GetUserRepoPermission", err) return diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 3ca193a15e73..cf919e0c32e3 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -7,6 +7,7 @@ package repo import ( "bytes" + stdCtx "context" "errors" "fmt" "io" @@ -232,7 +233,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti Page: pager.Paginater.Current(), PageSize: setting.UI.IssuePagingNum, }, - RepoIDs: []int64{repo.ID}, + RepoID: repo.ID, AssigneeID: assigneeID, PosterID: posterID, MentionedID: mentionedID, @@ -269,14 +270,15 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti } } - commitStatus, err := pull_service.GetIssuesLastCommitStatus(ctx, issues) + commitStatuses, lastStatus, err := pull_service.GetIssuesAllCommitStatus(ctx, issues) if err != nil { - ctx.ServerError("GetIssuesLastCommitStatus", err) + ctx.ServerError("GetIssuesAllCommitStatus", err) return } ctx.Data["Issues"] = issues - ctx.Data["CommitStatus"] = commitStatus + ctx.Data["CommitLastStatus"] = lastStatus + ctx.Data["CommitStatuses"] = commitStatuses // Get assignees. ctx.Data["Assignees"], err = models.GetRepoAssignees(repo) @@ -1051,8 +1053,8 @@ func NewIssuePost(ctx *context.Context) { } // roleDescriptor returns the Role Descriptor for a comment in/with the given repo, poster and issue -func roleDescriptor(repo *repo_model.Repository, poster *user_model.User, issue *models.Issue) (models.RoleDescriptor, error) { - perm, err := models.GetUserRepoPermission(repo, poster) +func roleDescriptor(ctx stdCtx.Context, repo *repo_model.Repository, poster *user_model.User, issue *models.Issue) (models.RoleDescriptor, error) { + perm, err := models.GetUserRepoPermission(ctx, repo, poster) if err != nil { return models.RoleDescriptorNone, err } @@ -1293,7 +1295,7 @@ func ViewIssue(ctx *context.Context) { if ctx.IsSigned { // Update issue-user. - if err = issue.ReadBy(ctx.Doer.ID); err != nil { + if err = issue.ReadBy(ctx, ctx.Doer.ID); err != nil { ctx.ServerError("ReadBy", err) return } @@ -1349,7 +1351,7 @@ func ViewIssue(ctx *context.Context) { // check if dependencies can be created across repositories ctx.Data["AllowCrossRepositoryDependencies"] = setting.Service.AllowCrossRepositoryDependencies - if issue.ShowRole, err = roleDescriptor(repo, issue.Poster, issue); err != nil { + if issue.ShowRole, err = roleDescriptor(ctx, repo, issue.Poster, issue); err != nil { ctx.ServerError("roleDescriptor", err) return } @@ -1388,7 +1390,7 @@ func ViewIssue(ctx *context.Context) { continue } - comment.ShowRole, err = roleDescriptor(repo, comment.Poster, issue) + comment.ShowRole, err = roleDescriptor(ctx, repo, comment.Poster, issue) if err != nil { ctx.ServerError("roleDescriptor", err) return @@ -1487,7 +1489,7 @@ func ViewIssue(ctx *context.Context) { continue } - c.ShowRole, err = roleDescriptor(repo, c.Poster, issue) + c.ShowRole, err = roleDescriptor(ctx, repo, c.Poster, issue) if err != nil { ctx.ServerError("roleDescriptor", err) return @@ -1525,34 +1527,37 @@ func ViewIssue(ctx *context.Context) { ctx.Data["AllowMerge"] = false if ctx.IsSigned { - if err := pull.LoadHeadRepo(); err != nil { + if err := pull.LoadHeadRepoCtx(ctx); err != nil { log.Error("LoadHeadRepo: %v", err) - } else if pull.HeadRepo != nil && pull.HeadBranch != pull.HeadRepo.DefaultBranch { - perm, err := models.GetUserRepoPermission(pull.HeadRepo, ctx.Doer) + } else if pull.HeadRepo != nil { + perm, err := models.GetUserRepoPermission(ctx, pull.HeadRepo, ctx.Doer) if err != nil { ctx.ServerError("GetUserRepoPermission", err) return } if perm.CanWrite(unit.TypeCode) { // Check if branch is not protected - if protected, err := models.IsProtectedBranch(pull.HeadRepo.ID, pull.HeadBranch); err != nil { - log.Error("IsProtectedBranch: %v", err) - } else if !protected { - canDelete = true - ctx.Data["DeleteBranchLink"] = issue.Link() + "/cleanup" + if pull.HeadBranch != pull.HeadRepo.DefaultBranch { + if protected, err := models.IsProtectedBranch(pull.HeadRepo.ID, pull.HeadBranch); err != nil { + log.Error("IsProtectedBranch: %v", err) + } else if !protected { + canDelete = true + ctx.Data["DeleteBranchLink"] = issue.Link() + "/cleanup" + } } + ctx.Data["CanWriteToHeadRepo"] = true } } - if err := pull.LoadBaseRepo(); err != nil { + if err := pull.LoadBaseRepoCtx(ctx); err != nil { log.Error("LoadBaseRepo: %v", err) } - perm, err := models.GetUserRepoPermission(pull.BaseRepo, ctx.Doer) + perm, err := models.GetUserRepoPermission(ctx, pull.BaseRepo, ctx.Doer) if err != nil { ctx.ServerError("GetUserRepoPermission", err) return } - ctx.Data["AllowMerge"], err = pull_service.IsUserAllowedToMerge(pull, perm, ctx.Doer) + ctx.Data["AllowMerge"], err = pull_service.IsUserAllowedToMerge(ctx, pull, perm, ctx.Doer) if err != nil { ctx.ServerError("IsUserAllowedToMerge", err) return @@ -1601,12 +1606,11 @@ func ViewIssue(ctx *context.Context) { if ctx.Doer != nil { showMergeInstructions = pull.ProtectedBranch.CanUserPush(ctx.Doer.ID) } - cnt := pull.ProtectedBranch.GetGrantedApprovalsCount(pull) - ctx.Data["IsBlockedByApprovals"] = !pull.ProtectedBranch.HasEnoughApprovals(pull) - ctx.Data["IsBlockedByRejection"] = pull.ProtectedBranch.MergeBlockedByRejectedReview(pull) - ctx.Data["IsBlockedByOfficialReviewRequests"] = pull.ProtectedBranch.MergeBlockedByOfficialReviewRequests(pull) + ctx.Data["IsBlockedByApprovals"] = !pull.ProtectedBranch.HasEnoughApprovals(ctx, pull) + ctx.Data["IsBlockedByRejection"] = pull.ProtectedBranch.MergeBlockedByRejectedReview(ctx, pull) + ctx.Data["IsBlockedByOfficialReviewRequests"] = pull.ProtectedBranch.MergeBlockedByOfficialReviewRequests(ctx, pull) ctx.Data["IsBlockedByOutdatedBranch"] = pull.ProtectedBranch.MergeBlockedByOutdatedBranch(pull) - ctx.Data["GrantedApprovals"] = cnt + ctx.Data["GrantedApprovals"] = pull.ProtectedBranch.GetGrantedApprovalsCount(ctx, pull) ctx.Data["RequireSigned"] = pull.ProtectedBranch.RequireSignedCommits ctx.Data["ChangedProtectedFiles"] = pull.ChangedProtectedFiles ctx.Data["IsBlockedByChangedProtectedFiles"] = len(pull.ChangedProtectedFiles) != 0 @@ -1636,7 +1640,7 @@ func ViewIssue(ctx *context.Context) { (!pull.HasMerged || ctx.Data["HeadBranchCommitID"] == ctx.Data["PullHeadCommitID"]) if isPullBranchDeletable && pull.HasMerged { - exist, err := models.HasUnmergedPullRequestsByHeadInfo(pull.HeadRepoID, pull.HeadBranch) + exist, err := models.HasUnmergedPullRequestsByHeadInfo(ctx, pull.HeadRepoID, pull.HeadBranch) if err != nil { ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err) return @@ -2037,7 +2041,7 @@ func UpdatePullReviewRequest(ctx *context.Context) { return } - err = issue_service.IsValidTeamReviewRequest(team, ctx.Doer, action == "attach", issue) + err = issue_service.IsValidTeamReviewRequest(ctx, team, ctx.Doer, action == "attach", issue) if err != nil { if models.IsErrNotValidReviewRequest(err) { log.Warn( @@ -2075,7 +2079,7 @@ func UpdatePullReviewRequest(ctx *context.Context) { return } - err = issue_service.IsValidReviewRequest(reviewer, ctx.Doer, action == "attach", issue, nil) + err = issue_service.IsValidReviewRequest(ctx, reviewer, ctx.Doer, action == "attach", issue, nil) if err != nil { if models.IsErrNotValidReviewRequest(err) { log.Warn( @@ -2167,6 +2171,7 @@ func SearchIssues(ctx *context.Context) { opts.TeamID = team.ID } + repoCond := models.SearchRepositoryCondition(opts) repoIDs, _, err := models.SearchRepositoryIDs(opts) if err != nil { ctx.Error(http.StatusInternalServerError, "SearchRepositoryByName", err.Error()) @@ -2227,7 +2232,7 @@ func SearchIssues(ctx *context.Context) { Page: ctx.FormInt("page"), PageSize: limit, }, - RepoIDs: repoIDs, + RepoCond: repoCond, IsClosed: isClosed, IssueIDs: issueIDs, IncludedLabelNames: includedLabelNames, @@ -2403,7 +2408,7 @@ func ListIssues(ctx *context.Context) { if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 { issuesOpt := &models.IssuesOptions{ ListOptions: listOptions, - RepoIDs: []int64{ctx.Repo.Repository.ID}, + RepoID: ctx.Repo.Repository.ID, IsClosed: isClosed, IssueIDs: issueIDs, LabelIDs: labelIDs, @@ -2917,7 +2922,7 @@ func filterXRefComments(ctx *context.Context, issue *models.Issue) error { if err != nil { return err } - perm, err := models.GetUserRepoPermission(c.RefRepo, ctx.Doer) + perm, err := models.GetUserRepoPermission(ctx, c.RefRepo, ctx.Doer) if err != nil { return err } diff --git a/routers/web/repo/issue_stopwatch.go b/routers/web/repo/issue_stopwatch.go index 7ef80e77254c..83e4ecedbf2e 100644 --- a/routers/web/repo/issue_stopwatch.go +++ b/routers/web/repo/issue_stopwatch.go @@ -9,7 +9,9 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/eventsource" ) // IssueStopwatch creates or stops a stopwatch for the given issue. @@ -59,6 +61,18 @@ func CancelStopwatch(c *context.Context) { return } + stopwatches, err := models.GetUserStopwatches(c.Doer.ID, db.ListOptions{}) + if err != nil { + c.ServerError("GetUserStopwatches", err) + return + } + if len(stopwatches) == 0 { + eventsource.GetManager().SendMessage(c.Doer.ID, &eventsource.Event{ + Name: "stopwatches", + Data: "{}", + }) + } + url := issue.HTMLURL() c.Redirect(url, http.StatusSeeOther) } diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 113e2d842112..6cda560f3ca9 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -70,7 +70,7 @@ func getRepository(ctx *context.Context, repoID int64) *repo_model.Repository { return nil } - perm, err := models.GetUserRepoPermission(repo, ctx.Doer) + perm, err := models.GetUserRepoPermission(ctx, repo, ctx.Doer) if err != nil { ctx.ServerError("GetUserRepoPermission", err) return nil @@ -283,14 +283,14 @@ func checkPullInfo(ctx *context.Context) *models.Issue { return nil } - if err = issue.PullRequest.LoadHeadRepo(); err != nil { + if err = issue.PullRequest.LoadHeadRepoCtx(ctx); err != nil { ctx.ServerError("LoadHeadRepo", err) return nil } if ctx.IsSigned { // Update issue-user. - if err = issue.ReadBy(ctx.Doer.ID); err != nil { + if err = issue.ReadBy(ctx, ctx.Doer.ID); err != nil { ctx.ServerError("ReadBy", err) return nil } @@ -397,12 +397,12 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare repo := ctx.Repo.Repository pull := issue.PullRequest - if err := pull.LoadHeadRepo(); err != nil { + if err := pull.LoadHeadRepoCtx(ctx); err != nil { ctx.ServerError("LoadHeadRepo", err) return nil } - if err := pull.LoadBaseRepo(); err != nil { + if err := pull.LoadBaseRepoCtx(ctx); err != nil { ctx.ServerError("LoadBaseRepo", err) return nil } @@ -499,7 +499,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare if headBranchExist { var err error - ctx.Data["UpdateAllowed"], ctx.Data["UpdateByRebaseAllowed"], err = pull_service.IsUserAllowedToUpdate(pull, ctx.Doer) + ctx.Data["UpdateAllowed"], ctx.Data["UpdateByRebaseAllowed"], err = pull_service.IsUserAllowedToUpdate(ctx, pull, ctx.Doer) if err != nil { ctx.ServerError("IsUserAllowedToUpdate", err) return nil @@ -785,16 +785,16 @@ func UpdatePullRequest(ctx *context.Context) { rebase := ctx.FormString("style") == "rebase" - if err := issue.PullRequest.LoadBaseRepo(); err != nil { + if err := issue.PullRequest.LoadBaseRepoCtx(ctx); err != nil { ctx.ServerError("LoadBaseRepo", err) return } - if err := issue.PullRequest.LoadHeadRepo(); err != nil { + if err := issue.PullRequest.LoadHeadRepoCtx(ctx); err != nil { ctx.ServerError("LoadHeadRepo", err) return } - allowedUpdateByMerge, allowedUpdateByRebase, err := pull_service.IsUserAllowedToUpdate(issue.PullRequest, ctx.Doer) + allowedUpdateByMerge, allowedUpdateByRebase, err := pull_service.IsUserAllowedToUpdate(ctx, issue.PullRequest, ctx.Doer) if err != nil { ctx.ServerError("IsUserAllowedToMerge", err) return @@ -866,6 +866,7 @@ func MergePullRequest(ctx *context.Context) { manuallMerge := repo_model.MergeStyle(form.Do) == repo_model.MergeStyleManuallyMerged forceMerge := form.ForceMerge != nil && *form.ForceMerge + // start with merging by checking if err := pull_service.CheckPullMergable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, manuallMerge, forceMerge); err != nil { if errors.Is(err, pull_service.ErrIsClosed) { if issue.IsPull { @@ -899,7 +900,6 @@ func MergePullRequest(ctx *context.Context) { } else { ctx.ServerError("WebCheck", err) } - return } @@ -909,14 +909,12 @@ func MergePullRequest(ctx *context.Context) { if models.IsErrInvalidMergeStyle(err) { ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option")) ctx.Redirect(issue.Link()) - return } else if strings.Contains(err.Error(), "Wrong commit ID") { ctx.Flash.Error(ctx.Tr("repo.pulls.wrong_commit_id")) ctx.Redirect(issue.Link()) - return + } else { + ctx.ServerError("MergedManually", err) } - - ctx.ServerError("MergedManually", err) return } @@ -925,16 +923,15 @@ func MergePullRequest(ctx *context.Context) { } // set defaults to propagate needed fields - if err := form.SetDefaults(pr); err != nil { + if err := form.SetDefaults(ctx, pr); err != nil { ctx.ServerError("SetDefaults", fmt.Errorf("SetDefaults: %v", err)) return } - if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, form.MergeTitleField); err != nil { + if err := pull_service.Merge(pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, form.MergeTitleField); err != nil { if models.IsErrInvalidMergeStyle(err) { ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option")) ctx.Redirect(issue.Link()) - return } else if models.IsErrMergeConflicts(err) { conflictError := err.(models.ErrMergeConflicts) flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ @@ -948,7 +945,6 @@ func MergePullRequest(ctx *context.Context) { } ctx.Flash.Error(flashError) ctx.Redirect(issue.Link()) - return } else if models.IsErrRebaseConflicts(err) { conflictError := err.(models.ErrRebaseConflicts) flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ @@ -962,22 +958,18 @@ func MergePullRequest(ctx *context.Context) { } ctx.Flash.Error(flashError) ctx.Redirect(issue.Link()) - return } else if models.IsErrMergeUnrelatedHistories(err) { log.Debug("MergeUnrelatedHistories error: %v", err) ctx.Flash.Error(ctx.Tr("repo.pulls.unrelated_histories")) ctx.Redirect(issue.Link()) - return } else if git.IsErrPushOutOfDate(err) { log.Debug("MergePushOutOfDate error: %v", err) ctx.Flash.Error(ctx.Tr("repo.pulls.merge_out_of_date")) ctx.Redirect(issue.Link()) - return } else if models.IsErrSHADoesNotMatch(err) { log.Debug("MergeHeadOutOfDate error: %v", err) ctx.Flash.Error(ctx.Tr("repo.pulls.head_out_of_date")) ctx.Redirect(issue.Link()) - return } else if git.IsErrPushRejected(err) { log.Debug("MergePushRejected error: %v", err) pushrejErr := err.(*git.ErrPushRejected) @@ -997,11 +989,12 @@ func MergePullRequest(ctx *context.Context) { ctx.Flash.Error(flashError) } ctx.Redirect(issue.Link()) - return + } else { + ctx.ServerError("Merge", err) } - ctx.ServerError("Merge", err) return } + log.Trace("Pull request merged: %d", pr.ID) if err := stopTimerIfAvailable(ctx.Doer, issue); err != nil { ctx.ServerError("CreateOrStopIssueStopwatch", err) @@ -1012,7 +1005,7 @@ func MergePullRequest(ctx *context.Context) { if form.DeleteBranchAfterMerge { // Don't cleanup when other pr use this branch as head branch - exist, err := models.HasUnmergedPullRequestsByHeadInfo(pr.HeadRepoID, pr.HeadBranch) + exist, err := models.HasUnmergedPullRequestsByHeadInfo(ctx, pr.HeadRepoID, pr.HeadBranch) if err != nil { ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err) return @@ -1132,14 +1125,15 @@ func CompareAndPullRequestPost(ctx *context.Context) { Content: form.Content, } pullRequest := &models.PullRequest{ - HeadRepoID: ci.HeadRepo.ID, - BaseRepoID: repo.ID, - HeadBranch: ci.HeadBranch, - BaseBranch: ci.BaseBranch, - HeadRepo: ci.HeadRepo, - BaseRepo: repo, - MergeBase: ci.CompareInfo.MergeBase, - Type: models.PullRequestGitea, + HeadRepoID: ci.HeadRepo.ID, + BaseRepoID: repo.ID, + HeadBranch: ci.HeadBranch, + BaseBranch: ci.BaseBranch, + HeadRepo: ci.HeadRepo, + BaseRepo: repo, + MergeBase: ci.CompareInfo.MergeBase, + Type: models.PullRequestGitea, + AllowMaintainerEdit: form.AllowMaintainerEdit, } // FIXME: check error in the case two people send pull request at almost same time, give nice error prompt // instead of 500. @@ -1192,7 +1186,7 @@ func CleanUpPullRequest(ctx *context.Context) { } // Don't cleanup when there are other PR's that use this branch as head branch. - exist, err := models.HasUnmergedPullRequestsByHeadInfo(pr.HeadRepoID, pr.HeadBranch) + exist, err := models.HasUnmergedPullRequestsByHeadInfo(ctx, pr.HeadRepoID, pr.HeadBranch) if err != nil { ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err) return @@ -1202,14 +1196,14 @@ func CleanUpPullRequest(ctx *context.Context) { return } - if err := pr.LoadHeadRepo(); err != nil { + if err := pr.LoadHeadRepoCtx(ctx); err != nil { ctx.ServerError("LoadHeadRepo", err) return } else if pr.HeadRepo == nil { // Forked repository has already been deleted ctx.NotFound("CleanUpPullRequest", nil) return - } else if err = pr.LoadBaseRepo(); err != nil { + } else if err = pr.LoadBaseRepoCtx(ctx); err != nil { ctx.ServerError("LoadBaseRepo", err) return } else if err = pr.HeadRepo.GetOwner(ctx); err != nil { @@ -1217,7 +1211,7 @@ func CleanUpPullRequest(ctx *context.Context) { return } - perm, err := models.GetUserRepoPermission(pr.HeadRepo, ctx.Doer) + perm, err := models.GetUserRepoPermission(ctx, pr.HeadRepo, ctx.Doer) if err != nil { ctx.ServerError("GetUserRepoPermission", err) return @@ -1303,7 +1297,7 @@ func deleteBranch(ctx *context.Context, pr *models.PullRequest, gitRepo *git.Rep return } - if err := models.AddDeletePRBranchComment(ctx.Doer, pr.BaseRepo, pr.IssueID, pr.HeadBranch); err != nil { + if err := models.AddDeletePRBranchComment(ctx, ctx.Doer, pr.BaseRepo, pr.IssueID, pr.HeadBranch); err != nil { // Do not fail here as branch has already been deleted log.Error("DeleteBranch: %v", err) } @@ -1411,3 +1405,31 @@ func UpdatePullRequestTarget(ctx *context.Context) { "base_branch": pr.BaseBranch, }) } + +// SetAllowEdits allow edits from maintainers to PRs +func SetAllowEdits(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.UpdateAllowEditsForm) + + pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) + if err != nil { + if models.IsErrPullRequestNotExist(err) { + ctx.NotFound("GetPullRequestByIndex", err) + } else { + ctx.ServerError("GetPullRequestByIndex", err) + } + return + } + + if err := pull_service.SetAllowEdits(ctx, ctx.Doer, pr, form.AllowMaintainerEdit); err != nil { + if errors.Is(pull_service.ErrUserHasNoPermissionForAction, err) { + ctx.Error(http.StatusForbidden) + return + } + ctx.ServerError("SetAllowEdits", err) + return + } + + ctx.JSON(http.StatusOK, map[string]interface{}{ + "allow_maintainer_edit": pr.AllowMaintainerEdit, + }) +} diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 60298121dfe4..199651b2f125 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -590,26 +590,28 @@ func SearchRepo(ctx *context.Context) { return } + ctx.SetTotalCountHeader(count) + + // To improve performance when only the count is requested + if ctx.FormBool("count_only") { + return + } + results := make([]*api.Repository, len(repos)) for i, repo := range repos { - if err = repo.GetOwner(ctx); err != nil { - ctx.JSON(http.StatusInternalServerError, api.SearchError{ - OK: false, - Error: err.Error(), - }) - return - } - accessMode, err := models.AccessLevel(ctx.Doer, repo) - if err != nil { - ctx.JSON(http.StatusInternalServerError, api.SearchError{ - OK: false, - Error: err.Error(), - }) + results[i] = &api.Repository{ + ID: repo.ID, + FullName: repo.FullName(), + Fork: repo.IsFork, + Private: repo.IsPrivate, + Template: repo.IsTemplate, + Mirror: repo.IsMirror, + Stars: repo.NumStars, + HTMLURL: repo.HTMLURL(), + Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate, } - results[i] = convert.ToRepo(repo, accessMode) } - ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, api.SearchResults{ OK: true, Data: results, diff --git a/routers/web/repo/setting_protected_branch.go b/routers/web/repo/setting_protected_branch.go index a26a0a620a92..1f6e2316e7df 100644 --- a/routers/web/repo/setting_protected_branch.go +++ b/routers/web/repo/setting_protected_branch.go @@ -261,7 +261,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) { protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns protectBranch.BlockOnOutdatedBranch = f.BlockOnOutdatedBranch - err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{ + err = models.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, models.WhitelistOptions{ UserIDs: whitelistUsers, TeamIDs: whitelistTeams, MergeUserIDs: mergeWhitelistUsers, diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 0faa01d573ce..86fc36fad7c8 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -146,6 +146,21 @@ func renderDirectory(ctx *context.Context, treeLink string) { ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName) } + // Check permission to add or upload new file. + if ctx.Repo.CanWrite(unit_model.TypeCode) && ctx.Repo.IsViewBranch { + ctx.Data["CanAddFile"] = !ctx.Repo.Repository.IsArchived + ctx.Data["CanUploadFile"] = setting.Repository.Upload.Enabled && !ctx.Repo.Repository.IsArchived + } + + readmeFile, readmeTreelink := findReadmeFile(ctx, entries, treeLink) + if ctx.Written() || readmeFile == nil { + return + } + + renderReadmeFile(ctx, readmeFile, readmeTreelink) +} + +func findReadmeFile(ctx *context.Context, entries git.Entries, treeLink string) (*namedBlob, string) { // 3 for the extensions in exts[] in order // the last one is for a readme that doesn't // strictly match an extension @@ -183,7 +198,7 @@ func renderDirectory(ctx *context.Context, treeLink string) { target, err = entry.FollowLinks() if err != nil && !git.IsErrBadLink(err) { ctx.ServerError("FollowLinks", err) - return + return nil, "" } } log.Debug("%t", target == nil) @@ -205,7 +220,7 @@ func renderDirectory(ctx *context.Context, treeLink string) { entry, err = entry.FollowLinks() if err != nil && !git.IsErrBadLink(err) { ctx.ServerError("FollowLinks", err) - return + return nil, "" } } if entry != nil && (entry.IsExecutable() || entry.IsRegular()) { @@ -236,7 +251,7 @@ func renderDirectory(ctx *context.Context, treeLink string) { readmeFile, err = getReadmeFileFromPath(ctx.Repo.Commit, entry.GetSubJumpablePathName()) if err != nil { ctx.ServerError("getReadmeFileFromPath", err) - return + return nil, "" } if readmeFile != nil { readmeFile.name = entry.Name() + "/" + readmeFile.name @@ -245,129 +260,127 @@ func renderDirectory(ctx *context.Context, treeLink string) { } } } + return readmeFile, readmeTreelink +} - if readmeFile != nil { - ctx.Data["RawFileLink"] = "" - ctx.Data["ReadmeInList"] = true - ctx.Data["ReadmeExist"] = true - ctx.Data["FileIsSymlink"] = readmeFile.isSymlink +func renderReadmeFile(ctx *context.Context, readmeFile *namedBlob, readmeTreelink string) { + ctx.Data["RawFileLink"] = "" + ctx.Data["ReadmeInList"] = true + ctx.Data["ReadmeExist"] = true + ctx.Data["FileIsSymlink"] = readmeFile.isSymlink - dataRc, err := readmeFile.blob.DataAsync() - if err != nil { - ctx.ServerError("Data", err) - return - } - defer dataRc.Close() - - buf := make([]byte, 1024) - n, _ := util.ReadAtMost(dataRc, buf) - buf = buf[:n] - - st := typesniffer.DetectContentType(buf) - isTextFile := st.IsText() - - ctx.Data["FileIsText"] = isTextFile - ctx.Data["FileName"] = readmeFile.name - fileSize := int64(0) - isLFSFile := false - ctx.Data["IsLFSFile"] = false - - // FIXME: what happens when README file is an image? - if isTextFile && setting.LFS.StartServer { - pointer, _ := lfs.ReadPointerFromBuffer(buf) - if pointer.IsValid() { - meta, err := models.GetLFSMetaObjectByOid(ctx.Repo.Repository.ID, pointer.Oid) - if err != nil && err != models.ErrLFSObjectNotExist { - ctx.ServerError("GetLFSMetaObject", err) - return - } - if meta != nil { - ctx.Data["IsLFSFile"] = true - isLFSFile = true + dataRc, err := readmeFile.blob.DataAsync() + if err != nil { + ctx.ServerError("Data", err) + return + } + defer dataRc.Close() - // OK read the lfs object - var err error - dataRc, err = lfs.ReadMetaObject(pointer) - if err != nil { - ctx.ServerError("ReadMetaObject", err) - return - } - defer dataRc.Close() + buf := make([]byte, 1024) + n, _ := util.ReadAtMost(dataRc, buf) + buf = buf[:n] - buf = make([]byte, 1024) - n, err = util.ReadAtMost(dataRc, buf) - if err != nil { - ctx.ServerError("Data", err) - return - } - buf = buf[:n] + st := typesniffer.DetectContentType(buf) + isTextFile := st.IsText() - st = typesniffer.DetectContentType(buf) - isTextFile = st.IsText() - ctx.Data["IsTextFile"] = isTextFile + ctx.Data["FileIsText"] = isTextFile + ctx.Data["FileName"] = readmeFile.name + fileSize := int64(0) + isLFSFile := false + ctx.Data["IsLFSFile"] = false - fileSize = meta.Size - ctx.Data["FileSize"] = meta.Size - filenameBase64 := base64.RawURLEncoding.EncodeToString([]byte(readmeFile.name)) - ctx.Data["RawFileLink"] = fmt.Sprintf("%s.git/info/lfs/objects/%s/%s", ctx.Repo.Repository.HTMLURL(), url.PathEscape(meta.Oid), url.PathEscape(filenameBase64)) - } + // FIXME: what happens when README file is an image? + if isTextFile && setting.LFS.StartServer { + pointer, _ := lfs.ReadPointerFromBuffer(buf) + if pointer.IsValid() { + meta, err := models.GetLFSMetaObjectByOid(ctx.Repo.Repository.ID, pointer.Oid) + if err != nil && err != models.ErrLFSObjectNotExist { + ctx.ServerError("GetLFSMetaObject", err) + return } - } - - if !isLFSFile { - fileSize = readmeFile.blob.Size() - } + if meta != nil { + ctx.Data["IsLFSFile"] = true + isLFSFile = true - if isTextFile { - if fileSize >= setting.UI.MaxDisplayFileSize { - // Pretend that this is a normal text file to display 'This file is too large to be shown' - ctx.Data["IsFileTooLarge"] = true - ctx.Data["IsTextFile"] = true - ctx.Data["FileSize"] = fileSize - } else { - rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc)) - - if markupType := markup.Type(readmeFile.name); markupType != "" { - ctx.Data["IsMarkup"] = true - ctx.Data["MarkupType"] = string(markupType) - var result strings.Builder - err := markup.Render(&markup.RenderContext{ - Ctx: ctx, - Filename: readmeFile.name, - URLPrefix: readmeTreelink, - Metas: ctx.Repo.Repository.ComposeDocumentMetas(), - GitRepo: ctx.Repo.GitRepo, - }, rd, &result) - if err != nil { - log.Error("Render failed: %v then fallback", err) - buf := &bytes.Buffer{} - ctx.Data["EscapeStatus"], _ = charset.EscapeControlReader(rd, buf) - ctx.Data["FileContent"] = strings.ReplaceAll( - gotemplate.HTMLEscapeString(buf.String()), "\n", `
`, - ) - } else { - ctx.Data["EscapeStatus"], ctx.Data["FileContent"] = charset.EscapeControlString(result.String()) - } - } else { - ctx.Data["IsRenderedHTML"] = true - buf := &bytes.Buffer{} - ctx.Data["EscapeStatus"], err = charset.EscapeControlReader(rd, buf) - if err != nil { - log.Error("Read failed: %v", err) - } + // OK read the lfs object + var err error + dataRc, err = lfs.ReadMetaObject(pointer) + if err != nil { + ctx.ServerError("ReadMetaObject", err) + return + } + defer dataRc.Close() - ctx.Data["FileContent"] = strings.ReplaceAll( - gotemplate.HTMLEscapeString(buf.String()), "\n", `
`, - ) + buf = make([]byte, 1024) + n, err = util.ReadAtMost(dataRc, buf) + if err != nil { + ctx.ServerError("Data", err) + return } + buf = buf[:n] + + st = typesniffer.DetectContentType(buf) + isTextFile = st.IsText() + ctx.Data["IsTextFile"] = isTextFile + + fileSize = meta.Size + ctx.Data["FileSize"] = meta.Size + filenameBase64 := base64.RawURLEncoding.EncodeToString([]byte(readmeFile.name)) + ctx.Data["RawFileLink"] = fmt.Sprintf("%s.git/info/lfs/objects/%s/%s", ctx.Repo.Repository.HTMLURL(), url.PathEscape(meta.Oid), url.PathEscape(filenameBase64)) } } } - // Check permission to add or upload new file. - if ctx.Repo.CanWrite(unit_model.TypeCode) && ctx.Repo.IsViewBranch { - ctx.Data["CanAddFile"] = !ctx.Repo.Repository.IsArchived - ctx.Data["CanUploadFile"] = setting.Repository.Upload.Enabled && !ctx.Repo.Repository.IsArchived + if !isTextFile { + return + } + + if !isLFSFile { + fileSize = readmeFile.blob.Size() + } + + if fileSize >= setting.UI.MaxDisplayFileSize { + // Pretend that this is a normal text file to display 'This file is too large to be shown' + ctx.Data["IsFileTooLarge"] = true + ctx.Data["IsTextFile"] = true + ctx.Data["FileSize"] = fileSize + return + } + + rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc)) + + if markupType := markup.Type(readmeFile.name); markupType != "" { + ctx.Data["IsMarkup"] = true + ctx.Data["MarkupType"] = string(markupType) + var result strings.Builder + err := markup.Render(&markup.RenderContext{ + Ctx: ctx, + Filename: readmeFile.name, + URLPrefix: readmeTreelink, + Metas: ctx.Repo.Repository.ComposeDocumentMetas(), + GitRepo: ctx.Repo.GitRepo, + }, rd, &result) + if err != nil { + log.Error("Render failed: %v then fallback", err) + buf := &bytes.Buffer{} + ctx.Data["EscapeStatus"], _ = charset.EscapeControlReader(rd, buf) + ctx.Data["FileContent"] = strings.ReplaceAll( + gotemplate.HTMLEscapeString(buf.String()), "\n", `
`, + ) + } else { + ctx.Data["EscapeStatus"], ctx.Data["FileContent"] = charset.EscapeControlString(result.String()) + } + } else { + ctx.Data["IsRenderedHTML"] = true + buf := &bytes.Buffer{} + ctx.Data["EscapeStatus"], err = charset.EscapeControlReader(rd, buf) + if err != nil { + log.Error("Read failed: %v", err) + } + + ctx.Data["FileContent"] = strings.ReplaceAll( + gotemplate.HTMLEscapeString(buf.String()), "\n", `
`, + ) } } @@ -566,7 +579,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st ctx.Data["LineEscapeStatus"] = statuses } if !isLFSFile { - if ctx.Repo.CanEnableEditor() { + if ctx.Repo.CanEnableEditor(ctx.Doer) { if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID { ctx.Data["CanEditFile"] = false ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.this_file_locked") @@ -576,7 +589,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st } } else if !ctx.Repo.IsViewBranch { ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch") - } else if !ctx.Repo.CanWrite(unit_model.TypeCode) { + } else if !ctx.Repo.CanWriteToBranch(ctx.Doer, ctx.Repo.BranchName) { ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit") } } @@ -616,7 +629,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st } } - if ctx.Repo.CanEnableEditor() { + if ctx.Repo.CanEnableEditor(ctx.Doer) { if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID { ctx.Data["CanDeleteFile"] = false ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.this_file_locked") @@ -626,7 +639,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st } } else if !ctx.Repo.IsViewBranch { ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch") - } else if !ctx.Repo.CanWrite(unit_model.TypeCode) { + } else if !ctx.Repo.CanWriteToBranch(ctx.Doer, ctx.Repo.BranchName) { ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access") } } diff --git a/routers/web/repo/webhook.go b/routers/web/repo/webhook.go index 2dafd4b5f426..86f3754a8edd 100644 --- a/routers/web/repo/webhook.go +++ b/routers/web/repo/webhook.go @@ -147,7 +147,6 @@ func WebhooksNew(ctx *context.Context) { if hookType == "discord" { ctx.Data["DiscordHook"] = map[string]interface{}{ "Username": "Gitea", - "IconURL": setting.AppURL + "img/favicon.png", } } ctx.Data["BaseLink"] = orCtx.LinkNew diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index 32f596ff370f..2dbd62d816b0 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -191,6 +191,9 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { ctx.Data["title"] = pageName ctx.Data["RequireHighlightJS"] = true + isSideBar := pageName == "_Sidebar" + isFooter := pageName == "_Footer" + // lookup filename in wiki - get filecontent, gitTree entry , real filename data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName) if noEntry { @@ -203,20 +206,30 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { return nil, nil } - sidebarContent, _, _, _ := wikiContentsByName(ctx, commit, "_Sidebar") - if ctx.Written() { - if wikiRepo != nil { - wikiRepo.Close() + var sidebarContent []byte + if !isSideBar { + sidebarContent, _, _, _ = wikiContentsByName(ctx, commit, "_Sidebar") + if ctx.Written() { + if wikiRepo != nil { + wikiRepo.Close() + } + return nil, nil } - return nil, nil + } else { + sidebarContent = data } - footerContent, _, _, _ := wikiContentsByName(ctx, commit, "_Footer") - if ctx.Written() { - if wikiRepo != nil { - wikiRepo.Close() + var footerContent []byte + if !isFooter { + footerContent, _, _, _ = wikiContentsByName(ctx, commit, "_Footer") + if ctx.Written() { + if wikiRepo != nil { + wikiRepo.Close() + } + return nil, nil } - return nil, nil + } else { + footerContent = data } rctx := &markup.RenderContext{ @@ -237,27 +250,35 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { ctx.Data["EscapeStatus"], ctx.Data["content"] = charset.EscapeControlString(buf.String()) - buf.Reset() - if err := markdown.Render(rctx, bytes.NewReader(sidebarContent), &buf); err != nil { - if wikiRepo != nil { - wikiRepo.Close() + if !isSideBar { + buf.Reset() + if err := markdown.Render(rctx, bytes.NewReader(sidebarContent), &buf); err != nil { + if wikiRepo != nil { + wikiRepo.Close() + } + ctx.ServerError("Render", err) + return nil, nil } - ctx.ServerError("Render", err) - return nil, nil + ctx.Data["sidebarPresent"] = sidebarContent != nil + ctx.Data["sidebarEscapeStatus"], ctx.Data["sidebarContent"] = charset.EscapeControlString(buf.String()) + } else { + ctx.Data["sidebarPresent"] = false } - ctx.Data["sidebarPresent"] = sidebarContent != nil - ctx.Data["sidebarEscapeStatus"], ctx.Data["sidebarContent"] = charset.EscapeControlString(buf.String()) - buf.Reset() - if err := markdown.Render(rctx, bytes.NewReader(footerContent), &buf); err != nil { - if wikiRepo != nil { - wikiRepo.Close() + if !isFooter { + buf.Reset() + if err := markdown.Render(rctx, bytes.NewReader(footerContent), &buf); err != nil { + if wikiRepo != nil { + wikiRepo.Close() + } + ctx.ServerError("Render", err) + return nil, nil } - ctx.ServerError("Render", err) - return nil, nil + ctx.Data["footerPresent"] = footerContent != nil + ctx.Data["footerEscapeStatus"], ctx.Data["footerContent"] = charset.EscapeControlString(buf.String()) + } else { + ctx.Data["footerPresent"] = false } - ctx.Data["footerPresent"] = footerContent != nil - ctx.Data["footerEscapeStatus"], ctx.Data["footerContent"] = charset.EscapeControlString(buf.String()) // get commit count - wiki revisions commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename) diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 33492aa209cf..2e7b382de691 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -463,13 +463,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { // to check if it's in the team(which possible isn't the case). opts.User = nil } - userRepoIDs, _, err := models.SearchRepositoryIDs(repoOpts) - if err != nil { - ctx.ServerError("models.SearchRepositoryIDs: %v", err) - return - } - - opts.RepoIDs = userRepoIDs + opts.RepoCond = models.SearchRepositoryCondition(repoOpts) } // keyword holds the search term entered into the search field. @@ -533,7 +527,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { // Gets set when clicking filters on the issues overview page. repoIDs := getRepoIDs(ctx.FormString("repos")) if len(repoIDs) > 0 { - opts.RepoIDs = repoIDs + opts.RepoCond = builder.In("issue.repo_id", repoIDs) } // ------------------------------ @@ -579,7 +573,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { } } - commitStatus, err := pull_service.GetIssuesLastCommitStatus(ctx, issues) + commitStatuses, lastStatus, err := pull_service.GetIssuesAllCommitStatus(ctx, issues) if err != nil { ctx.ServerError("GetIssuesLastCommitStatus", err) return @@ -656,7 +650,8 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { } return 0 } - ctx.Data["CommitStatus"] = commitStatus + ctx.Data["CommitLastStatus"] = lastStatus + ctx.Data["CommitStatuses"] = commitStatuses ctx.Data["Repos"] = showRepos ctx.Data["Counts"] = issueCountByRepo ctx.Data["IssueStats"] = issueStats diff --git a/routers/web/user/notification.go b/routers/web/user/notification.go index f7848de90a16..05421cf5556e 100644 --- a/routers/web/user/notification.go +++ b/routers/web/user/notification.go @@ -15,7 +15,7 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" - api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/structs" ) const ( @@ -194,6 +194,6 @@ func NotificationPurgePost(c *context.Context) { } // NewAvailable returns the notification counts -func NewAvailable(ctx *context.APIContext) { - ctx.JSON(http.StatusOK, api.NotificationCount{New: models.CountUnread(ctx.Doer)}) +func NewAvailable(ctx *context.Context) { + ctx.JSON(http.StatusOK, structs.NotificationCount{New: models.CountUnread(ctx.Doer)}) } diff --git a/routers/web/web.go b/routers/web/web.go index 0de6f1372275..dcaad3d2bde1 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -31,6 +31,7 @@ import ( "code.gitea.io/gitea/routers/web/events" "code.gitea.io/gitea/routers/web/explore" "code.gitea.io/gitea/routers/web/feed" + "code.gitea.io/gitea/routers/web/healthcheck" "code.gitea.io/gitea/routers/web/misc" "code.gitea.io/gitea/routers/web/org" "code.gitea.io/gitea/routers/web/repo" @@ -191,6 +192,8 @@ func Routes() *web.Route { rw.WriteHeader(http.StatusOK) }) + routes.Get("/api/healthz", healthcheck.Check) + // Removed: toolbox.Toolboxer middleware will provide debug information which seems unnecessary common = append(common, context.Contexter()) @@ -574,6 +577,7 @@ func RegisterRoutes(m *web.Route) { reqRepoAdmin := context.RequireRepoAdmin() reqRepoCodeWriter := context.RequireRepoWriter(unit.TypeCode) + canEnableEditor := context.CanEnableEditor() reqRepoCodeReader := context.RequireRepoReader(unit.TypeCode) reqRepoReleaseWriter := context.RequireRepoWriter(unit.TypeReleases) reqRepoReleaseReader := context.RequireRepoReader(unit.TypeReleases) @@ -925,12 +929,12 @@ func RegisterRoutes(m *web.Route) { Post(bindIgnErr(forms.EditRepoFileForm{}), repo.NewDiffPatchPost) m.Combo("/_cherrypick/{sha:([a-f0-9]{7,40})}/*").Get(repo.CherryPick). Post(bindIgnErr(forms.CherryPickForm{}), repo.CherryPickPost) - }, context.RepoRefByType(context.RepoRefBranch), repo.MustBeEditable) + }, repo.MustBeEditable) m.Group("", func() { m.Post("/upload-file", repo.UploadFileToServer) m.Post("/upload-remove", bindIgnErr(forms.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer) - }, context.RepoRef(), repo.MustBeEditable, repo.MustBeAbleToUpload) - }, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty) + }, repo.MustBeEditable, repo.MustBeAbleToUpload) + }, context.RepoRef(), canEnableEditor, context.RepoMustNotBeArchived(), repo.MustBeNotEmpty) m.Group("/branches", func() { m.Group("/_new", func() { @@ -1105,6 +1109,7 @@ func RegisterRoutes(m *web.Route) { m.Get("/commits", context.RepoRef(), repo.ViewPullCommits) m.Post("/merge", context.RepoMustNotBeArchived(), bindIgnErr(forms.MergePullRequestForm{}), repo.MergePullRequest) m.Post("/update", repo.UpdatePullRequest) + m.Post("/set_allow_maintainer_edit", bindIgnErr(forms.UpdateAllowEditsForm{}), repo.SetAllowEdits) m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest) m.Group("/files", func() { m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.ViewPullFiles) diff --git a/services/agit/agit.go b/services/agit/agit.go index 5f8d16172da4..288923618171 100644 --- a/services/agit/agit.go +++ b/services/agit/agit.go @@ -177,7 +177,7 @@ func ProcRecive(ctx *context.PrivateContext, opts *private.HookOptions) []privat } // update exist pull request - if err := pr.LoadBaseRepo(); err != nil { + if err := pr.LoadBaseRepoCtx(ctx); err != nil { log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err) ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ "Err": fmt.Sprintf("Unable to load base repository for PR[%d] Error: %v", pr.ID, err), diff --git a/services/asymkey/sign.go b/services/asymkey/sign.go index c3f093a80810..6b17c017fc63 100644 --- a/services/asymkey/sign.go +++ b/services/asymkey/sign.go @@ -271,7 +271,7 @@ Loop: // SignMerge determines if we should sign a PR merge commit to the base repository func SignMerge(ctx context.Context, pr *models.PullRequest, u *user_model.User, tmpBasePath, baseCommit, headCommit string) (bool, string, *git.Signature, error) { - if err := pr.LoadBaseRepo(); err != nil { + if err := pr.LoadBaseRepoCtx(ctx); err != nil { log.Error("Unable to get Base Repo for pull request") return false, "", nil, err } @@ -317,7 +317,7 @@ Loop: if protectedBranch == nil { return false, "", nil, &ErrWontSign{approved} } - if protectedBranch.GetGrantedApprovalsCount(pr) < 1 { + if protectedBranch.GetGrantedApprovalsCount(ctx, pr) < 1 { return false, "", nil, &ErrWontSign{approved} } case baseSigned: diff --git a/services/auth/reverseproxy.go b/services/auth/reverseproxy.go index 1b151f6504e3..299d7abd34ae 100644 --- a/services/auth/reverseproxy.go +++ b/services/auth/reverseproxy.go @@ -12,6 +12,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/services/mailer" @@ -105,11 +106,15 @@ func (r *ReverseProxy) newUser(req *http.Request) *user_model.User { } user := &user_model.User{ - Name: username, - Email: email, - IsActive: true, + Name: username, + Email: email, } - if err := user_model.CreateUser(user); err != nil { + + overwriteDefault := user_model.CreateUserOverwriteOptions{ + IsActive: util.OptionalBoolTrue, + } + + if err := user_model.CreateUser(user, &overwriteDefault); err != nil { // FIXME: should I create a system notice? log.Error("CreateUser: %v", err) return nil diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index ddd70627ed3f..d8d11f18e1ef 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/mailer" user_service "code.gitea.io/gitea/services/user" ) @@ -85,19 +86,21 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str } user = &user_model.User{ - LowerName: strings.ToLower(sr.Username), - Name: sr.Username, - FullName: composeFullName(sr.Name, sr.Surname, sr.Username), - Email: sr.Mail, - LoginType: source.authSource.Type, - LoginSource: source.authSource.ID, - LoginName: userName, - IsActive: true, - IsAdmin: sr.IsAdmin, - IsRestricted: sr.IsRestricted, + LowerName: strings.ToLower(sr.Username), + Name: sr.Username, + FullName: composeFullName(sr.Name, sr.Surname, sr.Username), + Email: sr.Mail, + LoginType: source.authSource.Type, + LoginSource: source.authSource.ID, + LoginName: userName, + IsAdmin: sr.IsAdmin, + } + overwriteDefault := &user_model.CreateUserOverwriteOptions{ + IsRestricted: util.OptionalBoolOf(sr.IsRestricted), + IsActive: util.OptionalBoolTrue, } - err := user_model.CreateUser(user) + err := user_model.CreateUser(user, overwriteDefault) if err != nil { return user, err } diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go index f2b940cabe02..d01fd14c8b97 100644 --- a/services/auth/source/ldap/source_search.go +++ b/services/auth/source/ldap/source_search.go @@ -433,14 +433,6 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul isRestricted = checkRestricted(l, ls, userDN) } - if !directBind && ls.AttributesInBind { - // binds user (checking password) after looking-up attributes in BindDN context - err = bindUser(l, userDN, passwd) - if err != nil { - return nil - } - } - if isAtributeAvatarSet { Avatar = sr.Entries[0].GetRawAttributeValue(ls.AttributeAvatar) } @@ -451,6 +443,14 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul teamsToAdd, teamsToRemove = ls.getMappedMemberships(l, uid) } + if !directBind && ls.AttributesInBind { + // binds user (checking password) after looking-up attributes in BindDN context + err = bindUser(l, userDN, passwd) + if err != nil { + return nil + } + } + return &SearchResult{ LowerName: strings.ToLower(username), Username: username, diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index 65efed78c17c..a245f4c6ff0d 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" user_service "code.gitea.io/gitea/services/user" ) @@ -102,19 +103,21 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { log.Trace("SyncExternalUsers[%s]: Creating user %s", source.authSource.Name, su.Username) usr = &user_model.User{ - LowerName: su.LowerName, - Name: su.Username, - FullName: fullName, - LoginType: source.authSource.Type, - LoginSource: source.authSource.ID, - LoginName: su.Username, - Email: su.Mail, - IsAdmin: su.IsAdmin, - IsRestricted: su.IsRestricted, - IsActive: true, + LowerName: su.LowerName, + Name: su.Username, + FullName: fullName, + LoginType: source.authSource.Type, + LoginSource: source.authSource.ID, + LoginName: su.Username, + Email: su.Mail, + IsAdmin: su.IsAdmin, + } + overwriteDefault := &user_model.CreateUserOverwriteOptions{ + IsRestricted: util.OptionalBoolOf(su.IsRestricted), + IsActive: util.OptionalBoolTrue, } - err = user_model.CreateUser(usr) + err = user_model.CreateUser(usr, overwriteDefault) if err != nil { log.Error("SyncExternalUsers[%s]: Error creating user %s: %v", source.authSource.Name, su.Username, err) diff --git a/services/auth/source/pam/source_authenticate.go b/services/auth/source/pam/source_authenticate.go index d5bd9409963f..16ddc0598e47 100644 --- a/services/auth/source/pam/source_authenticate.go +++ b/services/auth/source/pam/source_authenticate.go @@ -12,6 +12,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/auth/pam" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/mailer" "github.com/google/uuid" @@ -58,10 +59,12 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str LoginType: auth.PAM, LoginSource: source.authSource.ID, LoginName: userName, // This is what the user typed in - IsActive: true, + } + overwriteDefault := &user_model.CreateUserOverwriteOptions{ + IsActive: util.OptionalBoolTrue, } - if err := user_model.CreateUser(user); err != nil { + if err := user_model.CreateUser(user, overwriteDefault); err != nil { return user, err } diff --git a/services/auth/source/smtp/source_authenticate.go b/services/auth/source/smtp/source_authenticate.go index 3be2f1128de2..dff24d494ee0 100644 --- a/services/auth/source/smtp/source_authenticate.go +++ b/services/auth/source/smtp/source_authenticate.go @@ -74,10 +74,12 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str LoginType: auth_model.SMTP, LoginSource: source.authSource.ID, LoginName: userName, - IsActive: true, + } + overwriteDefault := &user_model.CreateUserOverwriteOptions{ + IsActive: util.OptionalBoolTrue, } - if err := user_model.CreateUser(user); err != nil { + if err := user_model.CreateUser(user, overwriteDefault); err != nil { return user, err } diff --git a/services/auth/sspi_windows.go b/services/auth/sspi_windows.go index 63e70e61d433..9bc4041a74ac 100644 --- a/services/auth/sspi_windows.go +++ b/services/auth/sspi_windows.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/services/auth/source/sspi" "code.gitea.io/gitea/services/mailer" @@ -187,17 +188,20 @@ func (s *SSPI) shouldAuthenticate(req *http.Request) (shouldAuth bool) { func (s *SSPI) newUser(username string, cfg *sspi.Source) (*user_model.User, error) { email := gouuid.New().String() + "@localhost.localdomain" user := &user_model.User{ - Name: username, - Email: email, - KeepEmailPrivate: true, - Passwd: gouuid.New().String(), - IsActive: cfg.AutoActivateUsers, - Language: cfg.DefaultLanguage, - UseCustomAvatar: true, - Avatar: avatars.DefaultAvatarLink(), - EmailNotificationsPreference: user_model.EmailNotificationsDisabled, - } - if err := user_model.CreateUser(user); err != nil { + Name: username, + Email: email, + Passwd: gouuid.New().String(), + Language: cfg.DefaultLanguage, + UseCustomAvatar: true, + Avatar: avatars.DefaultAvatarLink(), + } + emailNotificationPreference := user_model.EmailNotificationsDisabled + overwriteDefault := &user_model.CreateUserOverwriteOptions{ + IsActive: util.OptionalBoolOf(cfg.AutoActivateUsers), + KeepEmailPrivate: util.OptionalBoolTrue, + EmailNotificationsPreference: &emailNotificationPreference, + } + if err := user_model.CreateUser(user, overwriteDefault); err != nil { return nil, err } diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 80123e9af322..5c3adc1cd3eb 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -6,6 +6,7 @@ package forms import ( + stdContext "context" "net/http" "net/url" "strings" @@ -422,15 +423,16 @@ func (f *NewPackagistHookForm) Validate(req *http.Request, errs binding.Errors) // CreateIssueForm form for creating issue type CreateIssueForm struct { - Title string `binding:"Required;MaxSize(255)"` - LabelIDs string `form:"label_ids"` - AssigneeIDs string `form:"assignee_ids"` - Ref string `form:"ref"` - MilestoneID int64 - ProjectID int64 - AssigneeID int64 - Content string - Files []string + Title string `binding:"Required;MaxSize(255)"` + LabelIDs string `form:"label_ids"` + AssigneeIDs string `form:"assignee_ids"` + Ref string `form:"ref"` + MilestoneID int64 + ProjectID int64 + AssigneeID int64 + Content string + Files []string + AllowMaintainerEdit bool } // Validate validates the fields @@ -600,7 +602,7 @@ func (f *MergePullRequestForm) Validate(req *http.Request, errs binding.Errors) } // SetDefaults if not provided for mergestyle and commit message -func (f *MergePullRequestForm) SetDefaults(pr *models.PullRequest) (err error) { +func (f *MergePullRequestForm) SetDefaults(ctx stdContext.Context, pr *models.PullRequest) (err error) { if f.Do == "" { f.Do = "merge" } @@ -609,9 +611,9 @@ func (f *MergePullRequestForm) SetDefaults(pr *models.PullRequest) (err error) { if len(f.MergeTitleField) == 0 { switch f.Do { case "merge", "rebase-merge": - f.MergeTitleField, err = pr.GetDefaultMergeMessage() + f.MergeTitleField, err = pr.GetDefaultMergeMessage(ctx) case "squash": - f.MergeTitleField, err = pr.GetDefaultSquashMessage() + f.MergeTitleField, err = pr.GetDefaultSquashMessage(ctx) } } @@ -684,6 +686,11 @@ type DismissReviewForm struct { Message string } +// UpdateAllowEditsForm form for changing if PR allows edits from maintainers +type UpdateAllowEditsForm struct { + AllowMaintainerEdit bool +} + // __________ .__ // \______ \ ____ | | ____ _____ ______ ____ // | _// __ \| | _/ __ \\__ \ / ___// __ \ diff --git a/services/issue/assignee.go b/services/issue/assignee.go index 479c9cbb138a..e6169b9c7e55 100644 --- a/services/issue/assignee.go +++ b/services/issue/assignee.go @@ -5,6 +5,8 @@ package issue import ( + "context" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" @@ -78,7 +80,7 @@ func ReviewRequest(issue *models.Issue, doer, reviewer *user_model.User, isAdd b } // IsValidReviewRequest Check permission for ReviewRequest -func IsValidReviewRequest(reviewer, doer *user_model.User, isAdd bool, issue *models.Issue, permDoer *models.Permission) error { +func IsValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User, isAdd bool, issue *models.Issue, permDoer *models.Permission) error { if reviewer.IsOrganization() { return models.ErrNotValidReviewRequest{ Reason: "Organization can't be added as reviewer", @@ -94,14 +96,14 @@ func IsValidReviewRequest(reviewer, doer *user_model.User, isAdd bool, issue *mo } } - permReviewer, err := models.GetUserRepoPermission(issue.Repo, reviewer) + permReviewer, err := models.GetUserRepoPermission(ctx, issue.Repo, reviewer) if err != nil { return err } if permDoer == nil { permDoer = new(models.Permission) - *permDoer, err = models.GetUserRepoPermission(issue.Repo, doer) + *permDoer, err = models.GetUserRepoPermission(ctx, issue.Repo, doer) if err != nil { return err } @@ -168,7 +170,7 @@ func IsValidReviewRequest(reviewer, doer *user_model.User, isAdd bool, issue *mo } // IsValidTeamReviewRequest Check permission for ReviewRequest Team -func IsValidTeamReviewRequest(reviewer *organization.Team, doer *user_model.User, isAdd bool, issue *models.Issue) error { +func IsValidTeamReviewRequest(ctx context.Context, reviewer *organization.Team, doer *user_model.User, isAdd bool, issue *models.Issue) error { if doer.IsOrganization() { return models.ErrNotValidReviewRequest{ Reason: "Organization can't be doer to add reviewer", @@ -177,7 +179,7 @@ func IsValidTeamReviewRequest(reviewer *organization.Team, doer *user_model.User } } - permission, err := models.GetUserRepoPermission(issue.Repo, doer) + permission, err := models.GetUserRepoPermission(ctx, issue.Repo, doer) if err != nil { log.Error("Unable to GetUserRepoPermission for %-v in %-v#%d", doer, issue.Repo, issue.Index) return err @@ -185,7 +187,7 @@ func IsValidTeamReviewRequest(reviewer *organization.Team, doer *user_model.User if isAdd { if issue.Repo.IsPrivate { - hasTeam := organization.HasTeamRepo(db.DefaultContext, reviewer.OrgID, reviewer.ID, issue.RepoID) + hasTeam := organization.HasTeamRepo(ctx, reviewer.OrgID, reviewer.ID, issue.RepoID) if !hasTeam { return models.ErrNotValidReviewRequest{ diff --git a/services/issue/commit.go b/services/issue/commit.go index 0dda5f202f4f..b5d97e12a80f 100644 --- a/services/issue/commit.go +++ b/services/issue/commit.go @@ -14,6 +14,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/references" @@ -130,7 +131,7 @@ func UpdateIssuesCommit(doer *user_model.User, repo *repo_model.Repository, comm continue } - perm, err := models.GetUserRepoPermission(refRepo, doer) + perm, err := models.GetUserRepoPermission(db.DefaultContext, refRepo, doer) if err != nil { return err } diff --git a/services/issue/label.go b/services/issue/label.go index e72e1cb521cd..62ccc0ad6559 100644 --- a/services/issue/label.go +++ b/services/issue/label.go @@ -44,11 +44,17 @@ func AddLabels(issue *models.Issue, doer *user_model.User, labels []*models.Labe // RemoveLabel removes a label from issue by given ID. func RemoveLabel(issue *models.Issue, doer *user_model.User, label *models.Label) error { - if err := issue.LoadRepo(db.DefaultContext); err != nil { + ctx, committer, err := db.TxContext() + if err != nil { + return err + } + defer committer.Close() + + if err := issue.LoadRepo(ctx); err != nil { return err } - perm, err := models.GetUserRepoPermission(issue.Repo, doer) + perm, err := models.GetUserRepoPermission(ctx, issue.Repo, doer) if err != nil { return err } @@ -59,7 +65,11 @@ func RemoveLabel(issue *models.Issue, doer *user_model.User, label *models.Label return models.ErrRepoLabelNotExist{} } - if err := models.DeleteIssueLabel(issue, label, doer); err != nil { + if err := models.DeleteIssueLabel(ctx, issue, label, doer); err != nil { + return err + } + + if err := committer.Commit(); err != nil { return err } diff --git a/services/issue/status.go b/services/issue/status.go index 1af4508b09f5..d2b4fc303e86 100644 --- a/services/issue/status.go +++ b/services/issue/status.go @@ -5,6 +5,8 @@ package issue import ( + "context" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" @@ -14,10 +16,16 @@ import ( // ChangeStatus changes issue status to open or closed. func ChangeStatus(issue *models.Issue, doer *user_model.User, closed bool) error { - comment, err := models.ChangeIssueStatus(issue, doer, closed) + return changeStatusCtx(db.DefaultContext, issue, doer, closed) +} + +// changeStatusCtx changes issue status to open or closed. +// TODO: if context is not db.DefaultContext we get a deadlock!!! +func changeStatusCtx(ctx context.Context, issue *models.Issue, doer *user_model.User, closed bool) error { + comment, err := models.ChangeIssueStatus(ctx, issue, doer, closed) if err != nil { if models.IsErrDependenciesLeft(err) && closed { - if err := models.FinishIssueStopwatchIfPossible(db.DefaultContext, doer, issue); err != nil { + if err := models.FinishIssueStopwatchIfPossible(ctx, doer, issue); err != nil { log.Error("Unable to stop stopwatch for issue[%d]#%d: %v", issue.ID, issue.Index, err) } } @@ -25,7 +33,7 @@ func ChangeStatus(issue *models.Issue, doer *user_model.User, closed bool) error } if closed { - if err := models.FinishIssueStopwatchIfPossible(db.DefaultContext, doer, issue); err != nil { + if err := models.FinishIssueStopwatchIfPossible(ctx, doer, issue); err != nil { return err } } diff --git a/services/lfs/locks.go b/services/lfs/locks.go index fa51470d6262..029945220575 100644 --- a/services/lfs/locks.go +++ b/services/lfs/locks.go @@ -88,7 +88,7 @@ func GetListLockHandler(ctx *context.Context) { }) return } - lock, err := models.GetLFSLockByID(v) + lock, err := models.GetLFSLockByID(ctx, v) if err != nil && !models.IsErrLFSLockNotExist(err) { log.Error("Unable to get lock with ID[%s]: Error: %v", v, err) } @@ -98,7 +98,7 @@ func GetListLockHandler(ctx *context.Context) { path := ctx.FormString("path") if path != "" { // Case where we request a specific id - lock, err := models.GetLFSLock(repository, path) + lock, err := models.GetLFSLock(ctx, repository, path) if err != nil && !models.IsErrLFSLockNotExist(err) { log.Error("Unable to get lock for repository %-v with path %s: Error: %v", repository, path, err) } diff --git a/services/lfs/server.go b/services/lfs/server.go index 633aa0a69527..c095bbfab414 100644 --- a/services/lfs/server.go +++ b/services/lfs/server.go @@ -488,7 +488,7 @@ func authenticate(ctx *context.Context, repository *repo_model.Repository, autho } // ctx.IsSigned is unnecessary here, this will be checked in perm.CanAccess - perm, err := models.GetUserRepoPermission(repository, ctx.Doer) + perm, err := models.GetUserRepoPermission(ctx, repository, ctx.Doer) if err != nil { log.Error("Unable to GetUserRepoPermission for user %-v in repo %-v Error: %v", ctx.Doer, repository) return false diff --git a/services/mailer/mail.go b/services/mailer/mail.go index a5b60f71ec31..bdd7e25cabd4 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -367,6 +367,7 @@ func generateAdditionalHeaders(ctx *mailCommentContext, reason string, recipient //"List-Post": https://github.com/go-gitea/gitea/pull/13585 "List-Unsubscribe": ctx.Issue.HTMLURL(), + "X-Mailer": "Gitea", "X-Gitea-Reason": reason, "X-Gitea-Sender": ctx.Doer.DisplayName(), "X-Gitea-Recipient": recipient.DisplayName(), diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index 275b7026a0f6..34dd59d7fc0d 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -7,6 +7,7 @@ package migrations import ( "context" + "errors" "fmt" "io" "os" @@ -253,7 +254,6 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error { LowerTagName: strings.ToLower(release.TagName), Target: release.TargetCommitish, Title: release.Name, - Sha1: release.TargetCommitish, Note: release.Body, IsDraft: release.Draft, IsPrerelease: release.Prerelease, @@ -265,15 +265,18 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error { return err } - // calc NumCommits if no draft - if !release.Draft { + // calc NumCommits if possible + if rel.TagName != "" { commit, err := g.gitRepo.GetTagCommit(rel.TagName) - if err != nil { - return fmt.Errorf("GetTagCommit[%v]: %v", rel.TagName, err) - } - rel.NumCommits, err = commit.CommitsCount() - if err != nil { - return fmt.Errorf("CommitsCount: %v", err) + if !errors.Is(err, git.ErrNotExist{}) { + if err != nil { + return fmt.Errorf("GetTagCommit[%v]: %v", rel.TagName, err) + } + rel.Sha1 = commit.ID.String() + rel.NumCommits, err = commit.CommitsCount() + if err != nil { + return fmt.Errorf("CommitsCount: %v", err) + } } } @@ -553,7 +556,7 @@ func (g *GiteaLocalUploader) updateGitForPullRequest(pr *base.PullRequest) (head } if ok { - _, _, err = git.NewCommand(g.ctx, "fetch", remote, pr.Head.Ref).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()}) + _, _, err = git.NewCommand(g.ctx, "fetch", "--no-tags", "--", remote, pr.Head.Ref).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()}) if err != nil { log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err) } else { diff --git a/services/migrations/gitea_uploader_test.go b/services/migrations/gitea_uploader_test.go index 51c7ad9717e7..f57c8e23339b 100644 --- a/services/migrations/gitea_uploader_test.go +++ b/services/migrations/gitea_uploader_test.go @@ -105,7 +105,7 @@ func TestGiteaUploadRepo(t *testing.T) { assert.Len(t, releases, 1) issues, err := models.Issues(&models.IssuesOptions{ - RepoIDs: []int64{repo.ID}, + RepoID: repo.ID, IsPull: util.OptionalBoolFalse, SortType: "oldest", }) diff --git a/services/migrations/migrate.go b/services/migrations/migrate.go index b550be4ce75f..700f06af35dc 100644 --- a/services/migrations/migrate.go +++ b/services/migrations/migrate.go @@ -81,10 +81,9 @@ func IsMigrateURLAllowed(remoteURL string, doer *user_model.User) error { err = nil //nolint hostName = u.Host } - addrList, err := net.LookupIP(hostName) - if err != nil { - return &models.ErrInvalidCloneAddr{Host: u.Host, NotResolvedIP: true} - } + + // some users only use proxy, there is no DNS resolver. it's safe to ignore the LookupIP error + addrList, _ := net.LookupIP(hostName) var ipAllowed bool var ipBlocked bool diff --git a/services/pull/check.go b/services/pull/check.go index 29dc88e0f0c1..6852940b222d 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -14,6 +14,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -28,12 +29,12 @@ import ( asymkey_service "code.gitea.io/gitea/services/asymkey" ) -// prQueue represents a queue to handle update pull request tests -var prQueue queue.UniqueQueue +// prPatchCheckerQueue represents a queue to handle update pull request tests +var prPatchCheckerQueue queue.UniqueQueue var ( - ErrIsClosed = errors.New("pull is cosed") - ErrUserNotAllowedToMerge = errors.New("user not allowed to merge") + ErrIsClosed = errors.New("pull is closed") + ErrUserNotAllowedToMerge = models.ErrDisallowedToMerge{} ErrHasMerged = errors.New("has already been merged") ErrIsWorkInProgress = errors.New("work in progress PRs cannot be merged") ErrIsChecking = errors.New("cannot merge while conflict checking is in progress") @@ -43,7 +44,7 @@ var ( // AddToTaskQueue adds itself to pull request test task queue. func AddToTaskQueue(pr *models.PullRequest) { - err := prQueue.PushFunc(strconv.FormatInt(pr.ID, 10), func() error { + err := prPatchCheckerQueue.PushFunc(strconv.FormatInt(pr.ID, 10), func() error { pr.Status = models.PullRequestStatusChecking err := pr.UpdateColsIfNotMerged("status") if err != nil { @@ -59,70 +60,72 @@ func AddToTaskQueue(pr *models.PullRequest) { } // CheckPullMergable check if the pull mergable based on all conditions (branch protection, merge options, ...) -func CheckPullMergable(ctx context.Context, doer *user_model.User, perm *models.Permission, pr *models.PullRequest, manuallMerge, force bool) error { - if pr.HasMerged { - return ErrHasMerged - } +func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *models.Permission, pr *models.PullRequest, manuallMerge, force bool) error { + return db.WithTx(func(ctx context.Context) error { + if pr.HasMerged { + return ErrHasMerged + } - if err := pr.LoadIssue(); err != nil { - return err - } else if pr.Issue.IsClosed { - return ErrIsClosed - } + if err := pr.LoadIssueCtx(ctx); err != nil { + return err + } else if pr.Issue.IsClosed { + return ErrIsClosed + } - if allowedMerge, err := IsUserAllowedToMerge(pr, *perm, doer); err != nil { - return err - } else if !allowedMerge { - return ErrUserNotAllowedToMerge - } + if allowedMerge, err := IsUserAllowedToMerge(ctx, pr, *perm, doer); err != nil { + return err + } else if !allowedMerge { + return ErrUserNotAllowedToMerge + } - if manuallMerge { - // don't check rules to "auto merge", doer is going to mark this pull as merged manually - return nil - } + if manuallMerge { + // don't check rules to "auto merge", doer is going to mark this pull as merged manually + return nil + } - if pr.IsWorkInProgress() { - return ErrIsWorkInProgress - } + if pr.IsWorkInProgress() { + return ErrIsWorkInProgress + } - if !pr.CanAutoMerge() { - return ErrNotMergableState - } + if !pr.CanAutoMerge() { + return ErrNotMergableState + } - if pr.IsChecking() { - return ErrIsChecking - } + if pr.IsChecking() { + return ErrIsChecking + } - if err := CheckPRReadyToMerge(ctx, pr, false); err != nil { - if models.IsErrDisallowedToMerge(err) { - if force { - if isRepoAdmin, err := models.IsUserRepoAdmin(pr.BaseRepo, doer); err != nil { - return err - } else if !isRepoAdmin { - return ErrUserNotAllowedToMerge + if err := CheckPullBranchProtections(ctx, pr, false); err != nil { + if models.IsErrDisallowedToMerge(err) { + if force { + if isRepoAdmin, err2 := models.IsUserRepoAdminCtx(ctx, pr.BaseRepo, doer); err2 != nil { + return err2 + } else if !isRepoAdmin { + return err + } } + } else { + return err } - } else { - return err } - } - if _, err := isSignedIfRequired(ctx, pr, doer); err != nil { - return err - } + if _, err := isSignedIfRequired(ctx, pr, doer); err != nil { + return err + } - if noDeps, err := models.IssueNoDependenciesLeft(pr.Issue); err != nil { - return err - } else if !noDeps { - return ErrDependenciesLeft - } + if noDeps, err := models.IssueNoDependenciesLeft(ctx, pr.Issue); err != nil { + return err + } else if !noDeps { + return ErrDependenciesLeft + } - return nil + return nil + }, stdCtx) } // isSignedIfRequired check if merge will be signed if required func isSignedIfRequired(ctx context.Context, pr *models.PullRequest, doer *user_model.User) (bool, error) { - if err := pr.LoadProtectedBranch(); err != nil { + if err := pr.LoadProtectedBranchCtx(ctx); err != nil { return false, err } @@ -144,7 +147,7 @@ func checkAndUpdateStatus(pr *models.PullRequest) { } // Make sure there is no waiting test to process before leaving the checking status. - has, err := prQueue.Has(strconv.FormatInt(pr.ID, 10)) + has, err := prPatchCheckerQueue.Has(strconv.FormatInt(pr.ID, 10)) if err != nil { log.Error("Unable to check if the queue is waiting to reprocess pr.ID %d. Error: %v", pr.ID, err) } @@ -227,7 +230,7 @@ func getMergeCommit(ctx context.Context, pr *models.PullRequest) (*git.Commit, e // manuallyMerged checks if a pull request got manually merged // When a pull request got manually merged mark the pull request as merged func manuallyMerged(ctx context.Context, pr *models.PullRequest) bool { - if err := pr.LoadBaseRepo(); err != nil { + if err := pr.LoadBaseRepoCtx(ctx); err != nil { log.Error("PullRequest[%d].LoadBaseRepo: %v", pr.ID, err) return false } @@ -266,7 +269,7 @@ func manuallyMerged(ctx context.Context, pr *models.PullRequest) bool { pr.Merger = merger pr.MergerID = merger.ID - if merged, err := pr.SetMerged(); err != nil { + if merged, err := pr.SetMerged(ctx); err != nil { log.Error("PullRequest[%d].setMerged : %v", pr.ID, err) return false } else if !merged { @@ -293,7 +296,7 @@ func InitializePullRequests(ctx context.Context) { case <-ctx.Done(): return default: - if err := prQueue.PushFunc(strconv.FormatInt(prID, 10), func() error { + if err := prPatchCheckerQueue.PushFunc(strconv.FormatInt(prID, 10), func() error { log.Trace("Adding PR ID: %d to the pull requests patch checking queue", prID) return nil }); err != nil { @@ -314,10 +317,12 @@ func handle(data ...queue.Data) []queue.Data { } func testPR(id int64) { + pullWorkingPool.CheckIn(fmt.Sprint(id)) + defer pullWorkingPool.CheckOut(fmt.Sprint(id)) ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("Test PR[%d] from patch checking queue", id)) defer finished() - pr, err := models.GetPullRequestByID(id) + pr, err := models.GetPullRequestByID(ctx, id) if err != nil { log.Error("GetPullRequestByID[%d]: %v", id, err) return @@ -358,13 +363,13 @@ func CheckPrsForBaseBranch(baseRepo *repo_model.Repository, baseBranchName strin // Init runs the task queue to test all the checking status pull requests func Init() error { - prQueue = queue.CreateUniqueQueue("pr_patch_checker", handle, "") + prPatchCheckerQueue = queue.CreateUniqueQueue("pr_patch_checker", handle, "") - if prQueue == nil { + if prPatchCheckerQueue == nil { return fmt.Errorf("Unable to create pr_patch_checker Queue") } - go graceful.GetManager().RunWithShutdownFns(prQueue.Run) + go graceful.GetManager().RunWithShutdownFns(prPatchCheckerQueue.Run) go graceful.GetManager().RunWithShutdownContext(InitializePullRequests) return nil } diff --git a/services/pull/check_test.go b/services/pull/check_test.go index 65bcb9c0e44d..bc4c45ffada1 100644 --- a/services/pull/check_test.go +++ b/services/pull/check_test.go @@ -41,7 +41,7 @@ func TestPullRequest_AddToTaskQueue(t *testing.T) { queueShutdown := []func(){} queueTerminate := []func(){} - prQueue = q.(queue.UniqueQueue) + prPatchCheckerQueue = q.(queue.UniqueQueue) pr := unittest.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 2}).(*models.PullRequest) AddToTaskQueue(pr) @@ -51,11 +51,11 @@ func TestPullRequest_AddToTaskQueue(t *testing.T) { return pr.Status == models.PullRequestStatusChecking }, 1*time.Second, 100*time.Millisecond) - has, err := prQueue.Has(strconv.FormatInt(pr.ID, 10)) + has, err := prPatchCheckerQueue.Has(strconv.FormatInt(pr.ID, 10)) assert.True(t, has) assert.NoError(t, err) - prQueue.Run(func(shutdown func()) { + prPatchCheckerQueue.Run(func(shutdown func()) { queueShutdown = append(queueShutdown, shutdown) }, func(terminate func()) { queueTerminate = append(queueTerminate, terminate) @@ -68,7 +68,7 @@ func TestPullRequest_AddToTaskQueue(t *testing.T) { assert.Fail(t, "Timeout: nothing was added to pullRequestQueue") } - has, err = prQueue.Has(strconv.FormatInt(pr.ID, 10)) + has, err = prPatchCheckerQueue.Has(strconv.FormatInt(pr.ID, 10)) assert.False(t, has) assert.NoError(t, err) @@ -82,5 +82,5 @@ func TestPullRequest_AddToTaskQueue(t *testing.T) { callback() } - prQueue = nil + prPatchCheckerQueue = nil } diff --git a/services/pull/commit_status.go b/services/pull/commit_status.go index be8df0c9b158..143f3d50d098 100644 --- a/services/pull/commit_status.go +++ b/services/pull/commit_status.go @@ -83,7 +83,7 @@ func IsCommitStatusContextSuccess(commitStatuses []*models.CommitStatus, require // IsPullCommitStatusPass returns if all required status checks PASS func IsPullCommitStatusPass(ctx context.Context, pr *models.PullRequest) (bool, error) { - if err := pr.LoadProtectedBranch(); err != nil { + if err := pr.LoadProtectedBranchCtx(ctx); err != nil { return false, errors.Wrap(err, "GetLatestCommitStatus") } if pr.ProtectedBranch == nil || !pr.ProtectedBranch.EnableStatusCheck { @@ -100,7 +100,7 @@ func IsPullCommitStatusPass(ctx context.Context, pr *models.PullRequest) (bool, // GetPullRequestCommitStatusState returns pull request merged commit status state func GetPullRequestCommitStatusState(ctx context.Context, pr *models.PullRequest) (structs.CommitStatusState, error) { // Ensure HeadRepo is loaded - if err := pr.LoadHeadRepo(); err != nil { + if err := pr.LoadHeadRepoCtx(ctx); err != nil { return "", errors.Wrap(err, "LoadHeadRepo") } @@ -114,7 +114,7 @@ func GetPullRequestCommitStatusState(ctx context.Context, pr *models.PullRequest if pr.Flow == models.PullRequestFlowGithub && !headGitRepo.IsBranchExist(pr.HeadBranch) { return "", errors.New("Head branch does not exist, can not merge") } - if pr.Flow == models.PullRequestFlowAGit && !git.IsReferenceExist(headGitRepo.Ctx, headGitRepo.Path, pr.GetGitRefName()) { + if pr.Flow == models.PullRequestFlowAGit && !git.IsReferenceExist(ctx, headGitRepo.Path, pr.GetGitRefName()) { return "", errors.New("Head branch does not exist, can not merge") } @@ -128,11 +128,11 @@ func GetPullRequestCommitStatusState(ctx context.Context, pr *models.PullRequest return "", err } - if err := pr.LoadBaseRepo(); err != nil { + if err := pr.LoadBaseRepoCtx(ctx); err != nil { return "", errors.Wrap(err, "LoadBaseRepo") } - commitStatuses, _, err := models.GetLatestCommitStatus(pr.BaseRepo.ID, sha, db.ListOptions{}) + commitStatuses, _, err := models.GetLatestCommitStatusCtx(ctx, pr.BaseRepo.ID, sha, db.ListOptions{}) if err != nil { return "", errors.Wrap(err, "GetLatestCommitStatus") } diff --git a/services/pull/edits.go b/services/pull/edits.go new file mode 100644 index 000000000000..68515ec14157 --- /dev/null +++ b/services/pull/edits.go @@ -0,0 +1,40 @@ +// Copyright 2022 The Gitea Authors. +// All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package pull + +import ( + "context" + "errors" + + "code.gitea.io/gitea/models" + unit_model "code.gitea.io/gitea/models/unit" + user_model "code.gitea.io/gitea/models/user" +) + +var ErrUserHasNoPermissionForAction = errors.New("user not allowed to do this action") + +// SetAllowEdits allow edits from maintainers to PRs +func SetAllowEdits(ctx context.Context, doer *user_model.User, pr *models.PullRequest, allow bool) error { + if doer == nil || !pr.Issue.IsPoster(doer.ID) { + return ErrUserHasNoPermissionForAction + } + + if err := pr.LoadHeadRepo(); err != nil { + return err + } + + permission, err := models.GetUserRepoPermission(ctx, pr.HeadRepo, doer) + if err != nil { + return err + } + + if !permission.CanWrite(unit_model.TypeCode) { + return ErrUserHasNoPermissionForAction + } + + pr.AllowMaintainerEdit = allow + return models.UpdateAllowEdits(ctx, pr) +} diff --git a/services/pull/merge.go b/services/pull/merge.go index 0c615d93c851..fe295cbe03ac 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -17,6 +17,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -33,16 +34,18 @@ import ( // Merge merges pull request to base repository. // Caller should check PR is ready to be merged (review and status checks) -// FIXME: add repoWorkingPull make sure two merges does not happen at same time. -func Merge(ctx context.Context, pr *models.PullRequest, doer *user_model.User, baseGitRepo *git.Repository, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string) (err error) { - if err = pr.LoadHeadRepo(); err != nil { +func Merge(pr *models.PullRequest, doer *user_model.User, baseGitRepo *git.Repository, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string) error { + if err := pr.LoadHeadRepo(); err != nil { log.Error("LoadHeadRepo: %v", err) return fmt.Errorf("LoadHeadRepo: %v", err) - } else if err = pr.LoadBaseRepo(); err != nil { + } else if err := pr.LoadBaseRepo(); err != nil { log.Error("LoadBaseRepo: %v", err) return fmt.Errorf("LoadBaseRepo: %v", err) } + pullWorkingPool.CheckIn(fmt.Sprint(pr.ID)) + defer pullWorkingPool.CheckOut(fmt.Sprint(pr.ID)) + prUnit, err := pr.BaseRepo.GetUnit(unit.TypePullRequests) if err != nil { log.Error("pr.BaseRepo.GetUnit(unit.TypePullRequests): %v", err) @@ -59,7 +62,9 @@ func Merge(ctx context.Context, pr *models.PullRequest, doer *user_model.User, b go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false, "", "") }() - pr.MergedCommitID, err = rawMerge(ctx, pr, doer, mergeStyle, expectedHeadCommitID, message) + // TODO: make it able to do this in a database session + mergeCtx := context.Background() + pr.MergedCommitID, err = rawMerge(mergeCtx, pr, doer, mergeStyle, expectedHeadCommitID, message) if err != nil { return err } @@ -68,18 +73,18 @@ func Merge(ctx context.Context, pr *models.PullRequest, doer *user_model.User, b pr.Merger = doer pr.MergerID = doer.ID - if _, err := pr.SetMerged(); err != nil { + if _, err := pr.SetMerged(db.DefaultContext); err != nil { log.Error("setMerged [%d]: %v", pr.ID, err) } - if err := pr.LoadIssue(); err != nil { + if err := pr.LoadIssueCtx(db.DefaultContext); err != nil { log.Error("loadIssue [%d]: %v", pr.ID, err) } - if err := pr.Issue.LoadRepo(ctx); err != nil { + if err := pr.Issue.LoadRepo(db.DefaultContext); err != nil { log.Error("loadRepo for issue [%d]: %v", pr.ID, err) } - if err := pr.Issue.Repo.GetOwner(ctx); err != nil { + if err := pr.Issue.Repo.GetOwner(db.DefaultContext); err != nil { log.Error("GetOwner for issue repo [%d]: %v", pr.ID, err) } @@ -89,17 +94,17 @@ func Merge(ctx context.Context, pr *models.PullRequest, doer *user_model.User, b cache.Remove(pr.Issue.Repo.GetCommitsCountCacheKey(pr.BaseBranch, true)) // Resolve cross references - refs, err := pr.ResolveCrossReferences() + refs, err := pr.ResolveCrossReferences(db.DefaultContext) if err != nil { log.Error("ResolveCrossReferences: %v", err) return nil } for _, ref := range refs { - if err = ref.LoadIssue(); err != nil { + if err = ref.LoadIssueCtx(db.DefaultContext); err != nil { return err } - if err = ref.Issue.LoadRepo(ctx); err != nil { + if err = ref.Issue.LoadRepo(db.DefaultContext); err != nil { return err } close := ref.RefAction == references.XRefActionCloses @@ -112,7 +117,6 @@ func Merge(ctx context.Context, pr *models.PullRequest, doer *user_model.User, b } } } - return nil } @@ -504,6 +508,8 @@ func rawMerge(ctx context.Context, pr *models.PullRequest, doer *user_model.User } // Push back to upstream. + // TODO: this cause an api call to "/api/internal/hook/post-receive/...", + // that prevents us from doint the whole merge in one db transaction if err := pushCmd.Run(&git.RunOpts{ Env: env, Dir: tmpBasePath, @@ -645,30 +651,30 @@ func getDiffTree(ctx context.Context, repoPath, baseBranch, headBranch string) ( } // IsUserAllowedToMerge check if user is allowed to merge PR with given permissions and branch protections -func IsUserAllowedToMerge(pr *models.PullRequest, p models.Permission, user *user_model.User) (bool, error) { +func IsUserAllowedToMerge(ctx context.Context, pr *models.PullRequest, p models.Permission, user *user_model.User) (bool, error) { if user == nil { return false, nil } - err := pr.LoadProtectedBranch() + err := pr.LoadProtectedBranchCtx(ctx) if err != nil { return false, err } - if (p.CanWrite(unit.TypeCode) && pr.ProtectedBranch == nil) || (pr.ProtectedBranch != nil && models.IsUserMergeWhitelisted(pr.ProtectedBranch, user.ID, p)) { + if (p.CanWrite(unit.TypeCode) && pr.ProtectedBranch == nil) || (pr.ProtectedBranch != nil && models.IsUserMergeWhitelisted(ctx, pr.ProtectedBranch, user.ID, p)) { return true, nil } return false, nil } -// CheckPRReadyToMerge checks whether the PR is ready to be merged (reviews and status checks) -func CheckPRReadyToMerge(ctx context.Context, pr *models.PullRequest, skipProtectedFilesCheck bool) (err error) { - if err = pr.LoadBaseRepo(); err != nil { +// CheckPullBranchProtections checks whether the PR is ready to be merged (reviews and status checks) +func CheckPullBranchProtections(ctx context.Context, pr *models.PullRequest, skipProtectedFilesCheck bool) (err error) { + if err = pr.LoadBaseRepoCtx(ctx); err != nil { return fmt.Errorf("LoadBaseRepo: %v", err) } - if err = pr.LoadProtectedBranch(); err != nil { + if err = pr.LoadProtectedBranchCtx(ctx); err != nil { return fmt.Errorf("LoadProtectedBranch: %v", err) } if pr.ProtectedBranch == nil { @@ -685,17 +691,17 @@ func CheckPRReadyToMerge(ctx context.Context, pr *models.PullRequest, skipProtec } } - if !pr.ProtectedBranch.HasEnoughApprovals(pr) { + if !pr.ProtectedBranch.HasEnoughApprovals(ctx, pr) { return models.ErrDisallowedToMerge{ Reason: "Does not have enough approvals", } } - if pr.ProtectedBranch.MergeBlockedByRejectedReview(pr) { + if pr.ProtectedBranch.MergeBlockedByRejectedReview(ctx, pr) { return models.ErrDisallowedToMerge{ Reason: "There are requested changes", } } - if pr.ProtectedBranch.MergeBlockedByOfficialReviewRequests(pr) { + if pr.ProtectedBranch.MergeBlockedByOfficialReviewRequests(ctx, pr) { return models.ErrDisallowedToMerge{ Reason: "There are official review requests", } @@ -721,52 +727,61 @@ func CheckPRReadyToMerge(ctx context.Context, pr *models.PullRequest, skipProtec } // MergedManually mark pr as merged manually -func MergedManually(pr *models.PullRequest, doer *user_model.User, baseGitRepo *git.Repository, commitID string) (err error) { - prUnit, err := pr.BaseRepo.GetUnit(unit.TypePullRequests) - if err != nil { - return - } - prConfig := prUnit.PullRequestsConfig() +func MergedManually(pr *models.PullRequest, doer *user_model.User, baseGitRepo *git.Repository, commitID string) error { + pullWorkingPool.CheckIn(fmt.Sprint(pr.ID)) + defer pullWorkingPool.CheckOut(fmt.Sprint(pr.ID)) - // Check if merge style is correct and allowed - if !prConfig.IsMergeStyleAllowed(repo_model.MergeStyleManuallyMerged) { - return models.ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: repo_model.MergeStyleManuallyMerged} - } + if err := db.WithTx(func(ctx context.Context) error { + prUnit, err := pr.BaseRepo.GetUnitCtx(ctx, unit.TypePullRequests) + if err != nil { + return err + } + prConfig := prUnit.PullRequestsConfig() - if len(commitID) < 40 { - return fmt.Errorf("Wrong commit ID") - } + // Check if merge style is correct and allowed + if !prConfig.IsMergeStyleAllowed(repo_model.MergeStyleManuallyMerged) { + return models.ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: repo_model.MergeStyleManuallyMerged} + } - commit, err := baseGitRepo.GetCommit(commitID) - if err != nil { - if git.IsErrNotExist(err) { + if len(commitID) < 40 { return fmt.Errorf("Wrong commit ID") } - return - } - ok, err := baseGitRepo.IsCommitInBranch(commitID, pr.BaseBranch) - if err != nil { - return - } - if !ok { - return fmt.Errorf("Wrong commit ID") - } + commit, err := baseGitRepo.GetCommit(commitID) + if err != nil { + if git.IsErrNotExist(err) { + return fmt.Errorf("Wrong commit ID") + } + return err + } + commitID = commit.ID.String() - pr.MergedCommitID = commitID - pr.MergedUnix = timeutil.TimeStamp(commit.Author.When.Unix()) - pr.Status = models.PullRequestStatusManuallyMerged - pr.Merger = doer - pr.MergerID = doer.ID + ok, err := baseGitRepo.IsCommitInBranch(commitID, pr.BaseBranch) + if err != nil { + return err + } + if !ok { + return fmt.Errorf("Wrong commit ID") + } - merged := false - if merged, err = pr.SetMerged(); err != nil { - return - } else if !merged { - return fmt.Errorf("SetMerged failed") + pr.MergedCommitID = commitID + pr.MergedUnix = timeutil.TimeStamp(commit.Author.When.Unix()) + pr.Status = models.PullRequestStatusManuallyMerged + pr.Merger = doer + pr.MergerID = doer.ID + + merged := false + if merged, err = pr.SetMerged(ctx); err != nil { + return err + } else if !merged { + return fmt.Errorf("SetMerged failed") + } + return nil + }); err != nil { + return err } notification.NotifyMergePullRequest(pr, doer) - log.Info("manuallyMerged[%d]: Marked as manually merged into %s/%s by commit id: %s", pr.ID, pr.BaseRepo.Name, pr.BaseBranch, commit.ID.String()) + log.Info("manuallyMerged[%d]: Marked as manually merged into %s/%s by commit id: %s", pr.ID, pr.BaseRepo.Name, pr.BaseBranch, commitID) return nil } diff --git a/services/pull/patch.go b/services/pull/patch.go index f118ef33d022..eeedcf2d382a 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -27,7 +27,7 @@ import ( // DownloadDiffOrPatch will write the patch for the pr to the writer func DownloadDiffOrPatch(ctx context.Context, pr *models.PullRequest, w io.Writer, patch, binary bool) error { - if err := pr.LoadBaseRepo(); err != nil { + if err := pr.LoadBaseRepoCtx(ctx); err != nil { log.Error("Unable to load base repository ID %d for pr #%d [%d]", pr.BaseRepoID, pr.Index, pr.ID) return err } diff --git a/services/pull/pull.go b/services/pull/pull.go index 0537964b9de7..5cef3c356f04 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -25,9 +25,13 @@ import ( "code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/sync" issue_service "code.gitea.io/gitea/services/issue" ) +// TODO: use clustered lock (unique queue? or *abuse* cache) +var pullWorkingPool = sync.NewExclusivePool() + // NewPullRequest creates new pull request with labels for repository. func NewPullRequest(ctx context.Context, repo *repo_model.Repository, pull *models.Issue, labelIDs []int64, uuids []string, pr *models.PullRequest, assigneeIDs []int64) error { if err := TestPatch(pr); err != nil { @@ -124,6 +128,9 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, pull *mode // ChangeTargetBranch changes the target branch of this pull request, as the given user. func ChangeTargetBranch(ctx context.Context, pr *models.PullRequest, doer *user_model.User, targetBranch string) (err error) { + pullWorkingPool.CheckIn(fmt.Sprint(pr.ID)) + defer pullWorkingPool.CheckOut(fmt.Sprint(pr.ID)) + // Current target branch is already the same if pr.BaseBranch == targetBranch { return nil @@ -230,7 +237,7 @@ func checkForInvalidation(ctx context.Context, requests models.PullRequestList, } go func() { // FIXME: graceful: We need to tell the manager we're doing something... - err := requests.InvalidateCodeComments(doer, gitRepo, branch) + err := requests.InvalidateCodeComments(ctx, doer, gitRepo, branch) if err != nil { log.Error("PullRequestList.InvalidateCodeComments: %v", err) } @@ -341,14 +348,14 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string, // checkIfPRContentChanged checks if diff to target branch has changed by push // A commit can be considered to leave the PR untouched if the patch/diff with its merge base is unchanged func checkIfPRContentChanged(ctx context.Context, pr *models.PullRequest, oldCommitID, newCommitID string) (hasChanged bool, err error) { - if err = pr.LoadHeadRepo(); err != nil { + if err = pr.LoadHeadRepoCtx(ctx); err != nil { return false, fmt.Errorf("LoadHeadRepo: %v", err) } else if pr.HeadRepo == nil { // corrupt data assumed changed return true, nil } - if err = pr.LoadBaseRepo(); err != nil { + if err = pr.LoadBaseRepoCtx(ctx); err != nil { return false, fmt.Errorf("LoadBaseRepo: %v", err) } @@ -419,13 +426,13 @@ func PushToBaseRepo(ctx context.Context, pr *models.PullRequest) (err error) { func pushToBaseRepoHelper(ctx context.Context, pr *models.PullRequest, prefixHeadBranch string) (err error) { log.Trace("PushToBaseRepo[%d]: pushing commits to base repo '%s'", pr.BaseRepoID, pr.GetGitRefName()) - if err := pr.LoadHeadRepo(); err != nil { + if err := pr.LoadHeadRepoCtx(ctx); err != nil { log.Error("Unable to load head repository for PR[%d] Error: %v", pr.ID, err) return err } headRepoPath := pr.HeadRepo.RepoPath() - if err := pr.LoadBaseRepo(); err != nil { + if err := pr.LoadBaseRepoCtx(ctx); err != nil { log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err) return err } @@ -474,7 +481,7 @@ func pushToBaseRepoHelper(ctx context.Context, pr *models.PullRequest, prefixHea // UpdateRef update refs/pull/id/head directly for agit flow pull request func UpdateRef(ctx context.Context, pr *models.PullRequest) (err error) { log.Trace("UpdateRef[%d]: upgate pull request ref in base repo '%s'", pr.ID, pr.GetGitRefName()) - if err := pr.LoadBaseRepo(); err != nil { + if err := pr.LoadBaseRepoCtx(ctx); err != nil { log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err) return err } @@ -726,18 +733,25 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *models.PullRequest) s return stringBuilder.String() } -// GetIssuesLastCommitStatus returns a map +// GetIssuesLastCommitStatus returns a map of issue ID to the most recent commit's latest status func GetIssuesLastCommitStatus(ctx context.Context, issues models.IssueList) (map[int64]*models.CommitStatus, error) { + _, lastStatus, err := GetIssuesAllCommitStatus(ctx, issues) + return lastStatus, err +} + +// GetIssuesAllCommitStatus returns a map of issue ID to a list of all statuses for the most recent commit as well as a map of issue ID to only the commit's latest status +func GetIssuesAllCommitStatus(ctx context.Context, issues models.IssueList) (map[int64][]*models.CommitStatus, map[int64]*models.CommitStatus, error) { if err := issues.LoadPullRequests(); err != nil { - return nil, err + return nil, nil, err } if _, err := issues.LoadRepositories(); err != nil { - return nil, err + return nil, nil, err } var ( gitRepos = make(map[int64]*git.Repository) - res = make(map[int64]*models.CommitStatus) + res = make(map[int64][]*models.CommitStatus) + lastRes = make(map[int64]*models.CommitStatus) err error ) defer func() { @@ -760,34 +774,33 @@ func GetIssuesLastCommitStatus(ctx context.Context, issues models.IssueList) (ma gitRepos[issue.RepoID] = gitRepo } - status, err := getLastCommitStatus(gitRepo, issue.PullRequest) + statuses, lastStatus, err := getAllCommitStatus(gitRepo, issue.PullRequest) if err != nil { - log.Error("getLastCommitStatus: cant get last commit of pull [%d]: %v", issue.PullRequest.ID, err) + log.Error("getAllCommitStatus: cant get commit statuses of pull [%d]: %v", issue.PullRequest.ID, err) continue } - res[issue.PullRequest.ID] = status + res[issue.PullRequest.ID] = statuses + lastRes[issue.PullRequest.ID] = lastStatus } - return res, nil + return res, lastRes, nil } -// getLastCommitStatus get pr's last commit status. PR's last commit status is the head commit id's last commit status -func getLastCommitStatus(gitRepo *git.Repository, pr *models.PullRequest) (status *models.CommitStatus, err error) { - sha, err := gitRepo.GetRefCommitID(pr.GetGitRefName()) - if err != nil { - return nil, err +// getAllCommitStatus get pr's commit statuses. +func getAllCommitStatus(gitRepo *git.Repository, pr *models.PullRequest) (statuses []*models.CommitStatus, lastStatus *models.CommitStatus, err error) { + sha, shaErr := gitRepo.GetRefCommitID(pr.GetGitRefName()) + if shaErr != nil { + return nil, nil, shaErr } - statusList, _, err := models.GetLatestCommitStatus(pr.BaseRepo.ID, sha, db.ListOptions{}) - if err != nil { - return nil, err - } - return models.CalcCommitStatus(statusList), nil + statuses, _, err = models.GetLatestCommitStatus(pr.BaseRepo.ID, sha, db.ListOptions{}) + lastStatus = models.CalcCommitStatus(statuses) + return statuses, lastStatus, err } // IsHeadEqualWithBranch returns if the commits of branchName are available in pull request head func IsHeadEqualWithBranch(ctx context.Context, pr *models.PullRequest, branchName string) (bool, error) { var err error - if err = pr.LoadBaseRepo(); err != nil { + if err = pr.LoadBaseRepoCtx(ctx); err != nil { return false, err } baseGitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, pr.BaseRepo.RepoPath()) @@ -801,7 +814,7 @@ func IsHeadEqualWithBranch(ctx context.Context, pr *models.PullRequest, branchNa return false, err } - if err = pr.LoadHeadRepo(); err != nil { + if err = pr.LoadHeadRepoCtx(ctx); err != nil { return false, err } var headGitRepo *git.Repository diff --git a/services/pull/review.go b/services/pull/review.go index e7e6f3135ba9..940fe4470d16 100644 --- a/services/pull/review.go +++ b/services/pull/review.go @@ -122,7 +122,7 @@ func createCodeComment(ctx context.Context, doer *user_model.User, repo *repo_mo return nil, fmt.Errorf("GetPullRequestByIssueID: %v", err) } pr := issue.PullRequest - if err := pr.LoadBaseRepo(); err != nil { + if err := pr.LoadBaseRepoCtx(ctx); err != nil { return nil, fmt.Errorf("LoadHeadRepo: %v", err) } gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, pr.BaseRepo.RepoPath()) diff --git a/services/pull/temp_repo.go b/services/pull/temp_repo.go index 22ef53937d8a..f8f44ac0187b 100644 --- a/services/pull/temp_repo.go +++ b/services/pull/temp_repo.go @@ -21,7 +21,7 @@ import ( // createTemporaryRepo creates a temporary repo with "base" for pr.BaseBranch and "tracking" for pr.HeadBranch // it also create a second base branch called "original_base" func createTemporaryRepo(ctx context.Context, pr *models.PullRequest) (string, error) { - if err := pr.LoadHeadRepo(); err != nil { + if err := pr.LoadHeadRepoCtx(ctx); err != nil { log.Error("LoadHeadRepo: %v", err) return "", fmt.Errorf("LoadHeadRepo: %v", err) } else if pr.HeadRepo == nil { @@ -29,7 +29,7 @@ func createTemporaryRepo(ctx context.Context, pr *models.PullRequest) (string, e return "", &repo_model.ErrRepoNotExist{ ID: pr.HeadRepoID, } - } else if err := pr.LoadBaseRepo(); err != nil { + } else if err := pr.LoadBaseRepoCtx(ctx); err != nil { log.Error("LoadBaseRepo: %v", err) return "", fmt.Errorf("LoadBaseRepo: %v", err) } else if pr.BaseRepo == nil { diff --git a/services/pull/update.go b/services/pull/update.go index 2ad58ecd29ac..08967b59b390 100644 --- a/services/pull/update.go +++ b/services/pull/update.go @@ -23,6 +23,9 @@ func Update(ctx context.Context, pull *models.PullRequest, doer *user_model.User style repo_model.MergeStyle ) + pullWorkingPool.CheckIn(fmt.Sprint(pull.ID)) + defer pullWorkingPool.CheckOut(fmt.Sprint(pull.ID)) + if rebase { pr = pull style = repo_model.MergeStyleRebaseUpdate @@ -42,10 +45,10 @@ func Update(ctx context.Context, pull *models.PullRequest, doer *user_model.User return fmt.Errorf("Not support update agit flow pull request's head branch") } - if err := pr.LoadHeadRepo(); err != nil { + if err := pr.LoadHeadRepoCtx(ctx); err != nil { log.Error("LoadHeadRepo: %v", err) return fmt.Errorf("LoadHeadRepo: %v", err) - } else if err = pr.LoadBaseRepo(); err != nil { + } else if err = pr.LoadBaseRepoCtx(ctx); err != nil { log.Error("LoadBaseRepo: %v", err) return fmt.Errorf("LoadBaseRepo: %v", err) } @@ -71,7 +74,7 @@ func Update(ctx context.Context, pull *models.PullRequest, doer *user_model.User } // IsUserAllowedToUpdate check if user is allowed to update PR with given permissions and branch protections -func IsUserAllowedToUpdate(pull *models.PullRequest, user *user_model.User) (mergeAllowed, rebaseAllowed bool, err error) { +func IsUserAllowedToUpdate(ctx context.Context, pull *models.PullRequest, user *user_model.User) (mergeAllowed, rebaseAllowed bool, err error) { if pull.Flow == models.PullRequestFlowAGit { return false, false, nil } @@ -79,7 +82,7 @@ func IsUserAllowedToUpdate(pull *models.PullRequest, user *user_model.User) (mer if user == nil { return false, false, nil } - headRepoPerm, err := models.GetUserRepoPermission(pull.HeadRepo, user) + headRepoPerm, err := models.GetUserRepoPermission(ctx, pull.HeadRepo, user) if err != nil { return false, false, err } @@ -111,21 +114,35 @@ func IsUserAllowedToUpdate(pull *models.PullRequest, user *user_model.User) (mer return false, false, nil } - mergeAllowed, err = IsUserAllowedToMerge(pr, headRepoPerm, user) + baseRepoPerm, err := models.GetUserRepoPermission(ctx, pull.BaseRepo, user) + if err != nil { + return false, false, err + } + + mergeAllowed, err = IsUserAllowedToMerge(ctx, pr, headRepoPerm, user) if err != nil { return false, false, err } + if pull.AllowMaintainerEdit { + mergeAllowedMaintainer, err := IsUserAllowedToMerge(ctx, pr, baseRepoPerm, user) + if err != nil { + return false, false, err + } + + mergeAllowed = mergeAllowed || mergeAllowedMaintainer + } + return mergeAllowed, rebaseAllowed, nil } // GetDiverging determines how many commits a PR is ahead or behind the PR base branch func GetDiverging(ctx context.Context, pr *models.PullRequest) (*git.DivergeObject, error) { log.Trace("GetDiverging[%d]: compare commits", pr.ID) - if err := pr.LoadBaseRepo(); err != nil { + if err := pr.LoadBaseRepoCtx(ctx); err != nil { return nil, err } - if err := pr.LoadHeadRepo(); err != nil { + if err := pr.LoadHeadRepoCtx(ctx); err != nil { return nil, err } diff --git a/services/repository/archiver/archiver.go b/services/repository/archiver/archiver.go index 7c2cf237d515..ebd3eaf236ab 100644 --- a/services/repository/archiver/archiver.go +++ b/services/repository/archiver/archiver.go @@ -172,7 +172,7 @@ func doArchive(r *ArchiveRequest) (*repo_model.RepoArchiver, error) { w.Close() rd.Close() }() - done := make(chan error) + done := make(chan error, 1) // Ensure that there is some capacity which will ensure that the goroutine below can always finish repo, err := repo_model.GetRepositoryByID(archiver.RepoID) if err != nil { return nil, fmt.Errorf("archiver.LoadRepo failed: %v", err) diff --git a/services/repository/branch.go b/services/repository/branch.go index 6667cdee61a2..d9fc47c63a0f 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -35,7 +35,7 @@ func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_mode if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{ Remote: repo.RepoPath(), - Branch: fmt.Sprintf("%s:%s%s", oldBranchName, git.BranchPrefix, branchName), + Branch: fmt.Sprintf("%s%s:%s%s", git.BranchPrefix, oldBranchName, git.BranchPrefix, branchName), Env: models.PushingEnvironment(doer, repo), }); err != nil { if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { diff --git a/services/repository/transfer.go b/services/repository/transfer.go index 0abb03a88d93..3feeb68f223d 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -19,6 +19,7 @@ import ( ) // repoWorkingPool represents a working pool to order the parallel changes to the same repository +// TODO: use clustered lock (unique queue? or *abuse* cache) var repoWorkingPool = sync.NewExclusivePool() // TransferOwnership transfers all corresponding setting from old user to new one. diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go index 7998be53c283..77744473f1ce 100644 --- a/services/webhook/deliver.go +++ b/services/webhook/deliver.go @@ -15,7 +15,6 @@ import ( "io" "net/http" "net/url" - "strconv" "strings" "sync" "time" @@ -26,6 +25,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/proxy" + "code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/setting" "github.com/gobwas/glob" @@ -202,10 +202,8 @@ func Deliver(ctx context.Context, t *webhook_model.HookTask) error { return nil } -// DeliverHooks checks and delivers undelivered hooks. -// FIXME: graceful: This would likely benefit from either a worker pool with dummy queue -// or a full queue. Then more hooks could be sent at same time. -func DeliverHooks(ctx context.Context) { +// populateDeliverHooks checks and delivers undelivered hooks. +func populateDeliverHooks(ctx context.Context) { select { case <-ctx.Done(): return @@ -226,42 +224,9 @@ func DeliverHooks(ctx context.Context) { return default: } - if err = Deliver(ctx, t); err != nil { - log.Error("deliver: %v", err) - } - } - - // Start listening on new hook requests. - for { - select { - case <-ctx.Done(): - hookQueue.Close() - return - case repoIDStr := <-hookQueue.Queue(): - log.Trace("DeliverHooks [repo_id: %v]", repoIDStr) - hookQueue.Remove(repoIDStr) - - repoID, err := strconv.ParseInt(repoIDStr, 10, 64) - if err != nil { - log.Error("Invalid repo ID: %s", repoIDStr) - continue - } - tasks, err := webhook_model.FindRepoUndeliveredHookTasks(repoID) - if err != nil { - log.Error("Get repository [%d] hook tasks: %v", repoID, err) - continue - } - for _, t := range tasks { - select { - case <-ctx.Done(): - return - default: - } - if err = Deliver(ctx, t); err != nil { - log.Error("deliver: %v", err) - } - } + if err := addToTask(t.RepoID); err != nil { + log.Error("DeliverHook failed [%d]: %v", t.RepoID, err) } } } @@ -297,8 +262,8 @@ func webhookProxy() func(req *http.Request) (*url.URL, error) { } } -// InitDeliverHooks starts the hooks delivery thread -func InitDeliverHooks() { +// Init starts the hooks delivery thread +func Init() error { timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second allowedHostListValue := setting.Webhook.AllowedHostList @@ -316,5 +281,13 @@ func InitDeliverHooks() { }, } - go graceful.GetManager().RunWithShutdownContext(DeliverHooks) + hookQueue = queue.CreateUniqueQueue("webhook_sender", handle, "") + if hookQueue == nil { + return fmt.Errorf("Unable to create webhook_sender Queue") + } + go graceful.GetManager().RunWithShutdownFns(hookQueue.Run) + + populateDeliverHooks(graceful.GetManager().HammerContext()) + + return nil } diff --git a/services/webhook/main_test.go b/services/webhook/main_test.go index 25b9df0af668..1dc2e1bd83fb 100644 --- a/services/webhook/main_test.go +++ b/services/webhook/main_test.go @@ -9,12 +9,16 @@ import ( "testing" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/setting" _ "code.gitea.io/gitea/models" ) func TestMain(m *testing.M) { + setting.LoadForTest() + setting.NewQueueService() unittest.MainTest(m, &unittest.TestOptions{ GiteaRootPath: filepath.Join("..", ".."), + SetUp: Init, }) } diff --git a/services/webhook/webhook.go b/services/webhook/webhook.go index a3efc7535fc3..b15b8173f51f 100644 --- a/services/webhook/webhook.go +++ b/services/webhook/webhook.go @@ -12,10 +12,11 @@ import ( repo_model "code.gitea.io/gitea/models/repo" webhook_model "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/sync" "code.gitea.io/gitea/modules/util" "github.com/gobwas/glob" @@ -80,7 +81,7 @@ func IsValidHookTaskType(name string) bool { } // hookQueue is a global queue of web hooks -var hookQueue = sync.NewUniqueQueue(setting.Webhook.QueueLength) +var hookQueue queue.UniqueQueue // getPayloadBranch returns branch for hook event, if applicable. func getPayloadBranch(p api.Payloader) string { @@ -101,14 +102,47 @@ func getPayloadBranch(p api.Payloader) string { return "" } +// handle passed PR IDs and test the PRs +func handle(data ...queue.Data) []queue.Data { + for _, datum := range data { + repoIDStr := datum.(string) + log.Trace("DeliverHooks [repo_id: %v]", repoIDStr) + + repoID, err := strconv.ParseInt(repoIDStr, 10, 64) + if err != nil { + log.Error("Invalid repo ID: %s", repoIDStr) + continue + } + + tasks, err := webhook_model.FindRepoUndeliveredHookTasks(repoID) + if err != nil { + log.Error("Get repository [%d] hook tasks: %v", repoID, err) + continue + } + for _, t := range tasks { + if err = Deliver(graceful.GetManager().HammerContext(), t); err != nil { + log.Error("deliver: %v", err) + } + } + } + return nil +} + +func addToTask(repoID int64) error { + err := hookQueue.PushFunc(strconv.FormatInt(repoID, 10), nil) + if err != nil && err != queue.ErrAlreadyInQueue { + return err + } + return nil +} + // PrepareWebhook adds special webhook to task queue for given payload. func PrepareWebhook(w *webhook_model.Webhook, repo *repo_model.Repository, event webhook_model.HookEventType, p api.Payloader) error { if err := prepareWebhook(w, repo, event, p); err != nil { return err } - go hookQueue.Add(strconv.FormatInt(repo.ID, 10)) - return nil + return addToTask(repo.ID) } func checkBranch(w *webhook_model.Webhook, branch string) bool { @@ -188,8 +222,7 @@ func PrepareWebhooks(repo *repo_model.Repository, event webhook_model.HookEventT return err } - go hookQueue.Add(strconv.FormatInt(repo.ID, 10)) - return nil + return addToTask(repo.ID) } func prepareWebhooks(repo *repo_model.Repository, event webhook_model.HookEventType, p api.Payloader) error { @@ -240,7 +273,5 @@ func ReplayHookTask(w *webhook_model.Webhook, uuid string) error { return err } - go hookQueue.Add(strconv.FormatInt(t.RepoID, 10)) - - return nil + return addToTask(t.RepoID) } diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index 454f54983c10..796291fd3809 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -27,7 +27,8 @@ import ( var ( reservedWikiNames = []string{"_pages", "_new", "_edit", "raw"} - wikiWorkingPool = sync.NewExclusivePool() + // TODO: use clustered lock (unique queue? or *abuse* cache) + wikiWorkingPool = sync.NewExclusivePool() ) func nameAllowed(name string) error { diff --git a/templates/admin/auth/list.tmpl b/templates/admin/auth/list.tmpl index 71e5bfbda8d7..b4a703e4139f 100644 --- a/templates/admin/auth/list.tmpl +++ b/templates/admin/auth/list.tmpl @@ -10,7 +10,7 @@
- +
diff --git a/templates/admin/cron.tmpl b/templates/admin/cron.tmpl index 30277177ed0c..a73813ef8804 100644 --- a/templates/admin/cron.tmpl +++ b/templates/admin/cron.tmpl @@ -3,7 +3,7 @@
-
ID
+
diff --git a/templates/admin/emails/list.tmpl b/templates/admin/emails/list.tmpl index e73213c1dfa7..277c777a89c0 100644 --- a/templates/admin/emails/list.tmpl +++ b/templates/admin/emails/list.tmpl @@ -30,7 +30,7 @@
-
+
diff --git a/templates/admin/monitor.tmpl b/templates/admin/monitor.tmpl index 443159f8ceaa..86686101acf5 100644 --- a/templates/admin/monitor.tmpl +++ b/templates/admin/monitor.tmpl @@ -8,7 +8,7 @@ {{.i18n.Tr "admin.monitor.queues"}}
- +
diff --git a/templates/admin/notice.tmpl b/templates/admin/notice.tmpl index 9fec53b3156f..8d0e1c2206d1 100644 --- a/templates/admin/notice.tmpl +++ b/templates/admin/notice.tmpl @@ -7,7 +7,7 @@ {{.i18n.Tr "admin.notices.system_notice_list"}} ({{.i18n.Tr "admin.total" .Total}})
-
{{.i18n.Tr "admin.monitor.queue.name"}}
+
diff --git a/templates/admin/org/list.tmpl b/templates/admin/org/list.tmpl index 75c4d39196cf..0782ef64e97a 100644 --- a/templates/admin/org/list.tmpl +++ b/templates/admin/org/list.tmpl @@ -13,7 +13,7 @@ {{template "admin/base/search" .}}
-
+
diff --git a/templates/admin/packages/list.tmpl b/templates/admin/packages/list.tmpl index 373a97407b5e..df89d8bed236 100644 --- a/templates/admin/packages/list.tmpl +++ b/templates/admin/packages/list.tmpl @@ -29,7 +29,7 @@
-
ID{{SortArrow "oldest" "newest" $.SortType false}}
+
diff --git a/templates/admin/repo/list.tmpl b/templates/admin/repo/list.tmpl index d7561dde7d2b..da05bfab962e 100644 --- a/templates/admin/repo/list.tmpl +++ b/templates/admin/repo/list.tmpl @@ -13,7 +13,7 @@ {{template "admin/repo/search" .}}
-
ID
+
diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl index 17bd2b936c5e..1ee46f3077a3 100644 --- a/templates/admin/user/edit.tmpl +++ b/templates/admin/user/edit.tmpl @@ -116,7 +116,7 @@ -
+
diff --git a/templates/admin/user/list.tmpl b/templates/admin/user/list.tmpl index 93e6f38c2701..755e4436f89d 100644 --- a/templates/admin/user/list.tmpl +++ b/templates/admin/user/list.tmpl @@ -61,7 +61,7 @@
-
ID{{SortArrow "oldest" "newest" $.SortType false}}
+
diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index 35157e9b954b..f3dcfe8429f6 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -85,7 +85,7 @@ {{template "custom/body_inner_pre" .}} {{if not .PageIsInstall}} -
ID{{SortArrow "oldest" "newest" .SortType false}}