diff --git a/.drone.yml b/.drone.yml index 7be296fa5f1c1..a24f4c621e718 100644 --- a/.drone.yml +++ b/.drone.yml @@ -207,8 +207,14 @@ steps: commands: - git update-ref refs/heads/tag_test ${DRONE_COMMIT_SHA} + - name: fix-permissions + image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + commands: + - chown -R gitea:gitea . + - name: unit-test - image: golang:1.17 + image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + user: gitea commands: - make unit-test-coverage test-check environment: @@ -220,7 +226,8 @@ steps: - name: unit-test-gogit pull: always - image: golang:1.17 + image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + user: gitea commands: - make unit-test-coverage test-check environment: @@ -232,6 +239,7 @@ steps: - name: test-mysql image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + user: gitea commands: - make test-mysql-migration integration-test-coverage environment: @@ -246,6 +254,7 @@ steps: - name: test-mysql8 image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + user: gitea commands: - timeout -s ABRT 40m make test-mysql8-migration test-mysql8 environment: @@ -259,6 +268,7 @@ steps: - name: test-mssql image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + user: gitea commands: - make test-mssql-migration test-mssql environment: @@ -343,9 +353,15 @@ steps: exclude: - pull_request + - name: fix-permissions + image: gitea/test_env:linux-arm64 # https://gitea.com/gitea/test-env + commands: + - chown -R gitea:gitea . + - name: build pull: always - image: golang:1.17 + image: gitea/test_env:linux-arm64 # https://gitea.com/gitea/test-env + user: gitea commands: - make backend environment: @@ -355,6 +371,7 @@ steps: - name: test-sqlite image: gitea/test_env:linux-arm64 # https://gitea.com/gitea/test-env + user: gitea commands: - timeout -s ABRT 40m make test-sqlite-migration test-sqlite environment: @@ -368,6 +385,7 @@ steps: - name: test-pgsql image: gitea/test_env:linux-arm64 # https://gitea.com/gitea/test-env + user: gitea commands: - timeout -s ABRT 40m make test-pgsql-migration test-pgsql environment: @@ -534,7 +552,7 @@ steps: - name: release-branch pull: always - image: plugins/s3:1 + image: woodpeckerci/plugin-s3:latest settings: acl: public-read bucket: gitea-artifacts @@ -555,7 +573,7 @@ steps: - push - name: release-main - image: plugins/s3:1 + image: woodpeckerci/plugin-s3:latest settings: acl: public-read bucket: gitea-artifacts @@ -630,7 +648,7 @@ steps: - name: release-tag pull: always - image: plugins/s3:1 + image: woodpeckerci/plugin-s3:latest settings: acl: public-read bucket: gitea-artifacts diff --git a/.github/issue_template.md b/.gitea/issue_template.md similarity index 100% rename from .github/issue_template.md rename to .gitea/issue_template.md diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml new file mode 100644 index 0000000000000..394594dc30c3a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yaml @@ -0,0 +1,89 @@ +name: Bug Report +description: Found something you weren't expecting? Report it here! +body: +- type: markdown + attributes: + value: | + NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue. +- type: markdown + attributes: + value: | + 1. Please speak English, this is the language all maintainers can speak and write. + 2. Please ask questions or configuration/deploy problems on our Discord + server (https://discord.gg/gitea) or forum (https://discourse.gitea.io). + 3. Please take a moment to check that your issue doesn't already exist. + 4. Make sure it's not mentioned in the FAQ (https://docs.gitea.io/en-us/faq) + 5. Please give all relevant information below for bug reports, because + incomplete details will be handled as an invalid report. +- type: input + id: gitea-ver + attributes: + label: Gitea Version + description: Gitea version (or commit reference) your instance is running + validations: + required: true +- type: input + id: git-ver + attributes: + label: Git Version + description: The version of git running on the server +- type: input + id: os-ver + attributes: + label: Operating System + description: The operating system you are using to run Gitea +- type: textarea + id: run-info + attributes: + label: How are you running Gitea? + description: | + Please include information on whether you built gitea yourself, used one of our downloads, are using https://try.gitea.io or are using some other package + Please also tell us how you are running gitea, e.g. if it is being run from docker, a command-line, systemd etc. + If you are using a package or systemd tell us what distribution you are using + validations: + required: true +- type: dropdown + id: database + attributes: + label: Database + description: What database system are you running? + options: + - PostgreSQL + - MySQL + - MSSQL + - SQLite +- type: dropdown + id: can-reproduce + attributes: + label: Can you reproduce the bug on the Gitea demo site? + description: | + If so, please provide a URL in the Description field + URL of Gitea demo: https://try.gitea.io + options: + - "Yes" + - "No" + validations: + required: true +- type: markdown + attributes: + value: | + It really is important to provide pertinent logs + Please read https://docs.gitea.io/en-us/logging-configuration/#debugging-problems + In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini +- type: input + id: logs + attributes: + label: Log Gist + description: Please provide a gist URL of your logs, with any sensitive information (e.g. API keys) removed/hidden +- type: textarea + id: description + attributes: + label: Description + description: | + Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see above) + If using a proxy or a CDN (e.g. CloudFlare) in front of gitea, please disable the proxy/CDN fully and connect to gitea directly to confirm the issue still persists without those services. +- type: textarea + id: screenshots + attributes: + label: Screenshots + description: If this issue involves the Web Interface, please provide a screenshot or multiple screenshots diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000000..e79cc9d4328d5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,17 @@ +blank_issues_enabled: true +contact_links: + - name: Security Concern + url: https://tinyurl.com/security-gitea + about: For security concerns, please send a mail to security@gitea.io instead of opening a public issue. + - name: Discord Server + url: https://discord.gg/gitea + about: Please ask questions and discuss configuration or deployment problems here. + - name: Discourse Forum + url: https://discourse.gitea.io + about: Questions and configuration or deployment problems can also be discussed on our forum. + - name: Frequently Asked Questions + url: https://docs.gitea.io/en-us/faq + about: Please check if your question isn't mentioned here. + - name: Crowdin Translations + url: https://crowdin.com/project/gitea + about: Translations are managed here. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yaml b/.github/ISSUE_TEMPLATE/feature-request.yaml new file mode 100644 index 0000000000000..69b338ddf8d02 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yaml @@ -0,0 +1,23 @@ +name: Feature Request +description: Got an idea for a feature that Gitea doesn't have currently? Submit your idea here! +body: +- type: markdown + attributes: + value: | + 1. Please speak English, this is the language all maintainers can speak and write. + 2. Please ask questions or configuration/deploy problems on our Discord + server (https://discord.gg/gitea) or forum (https://discourse.gitea.io). + 3. Please take a moment to check that your feature hasn't already been suggested. +- type: textarea + id: description + attributes: + label: Feature Description + placeholder: | + I think it would be great if Gitea had... + validations: + required: true +- type: textarea + id: screenshots + attributes: + label: Screenshots + description: If you can, provide screenshots of an implementation on another site e.g. GitHub diff --git a/.github/ISSUE_TEMPLATE/ui.bug-report.yaml b/.github/ISSUE_TEMPLATE/ui.bug-report.yaml new file mode 100644 index 0000000000000..deae94052e67a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ui.bug-report.yaml @@ -0,0 +1,62 @@ +name: Web Interface Bug Report +description: Something doesn't look quite as it should? Report it here! +body: +- type: markdown + attributes: + value: | + NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue. +- type: markdown + attributes: + value: | + 1. Please speak English, this is the language all maintainers can speak and write. + 2. Please ask questions or configuration/deploy problems on our Discord + server (https://discord.gg/gitea) or forum (https://discourse.gitea.io). + 3. Please take a moment to check that your issue doesn't already exist. + 4. Make sure it's not mentioned in the FAQ (https://docs.gitea.io/en-us/faq) + 5. Please give all relevant information below for bug reports, because + incomplete details will be handled as an invalid report. +- type: input + id: gitea-ver + attributes: + label: Gitea Version + description: Gitea version (or commit reference) your instance is running + validations: + required: true +- type: input + id: os-ver + attributes: + label: Operating System + description: The operating system you are using to access Gitea +- type: input + id: browser-ver + attributes: + label: Browser Version + description: The browser and version that you are using to access Gitea + validations: + required: true +- type: dropdown + id: can-reproduce + attributes: + label: Can you reproduce the bug on the Gitea demo site? + description: | + If so, please provide a URL in the Description field + URL of Gitea demo: https://try.gitea.io + options: + - "Yes" + - "No" + validations: + required: true +- type: textarea + id: description + attributes: + label: Description + description: | + Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see above) + If using a proxy or a CDN (e.g. CloudFlare) in front of gitea, please disable the proxy/CDN fully and connect to gitea directly to confirm the issue still persists without those services. +- type: textarea + id: screenshots + attributes: + label: Screenshots + description: Please provide at least 1 screenshot showing the issue. + validations: + required: true diff --git a/.golangci.yml b/.golangci.yml index c3dd47ec29da6..2d66e01ffaf8a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -111,4 +111,3 @@ issues: linters: - staticcheck text: "svc.IsAnInteractiveSession is deprecated: Use IsWindowsService instead." - diff --git a/.stylelintrc b/.stylelintrc index 1d95b964bd287..42c45f0ea7c29 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -13,3 +13,4 @@ rules: rule-empty-line-before: null selector-pseudo-element-colon-notation: double shorthand-property-no-redundant-values: true + no-invalid-position-at-import-rule: null diff --git a/CHANGELOG.md b/CHANGELOG.md index 01c388fdd0c0b..d4bb000c937e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log; to see the highlights of what has been added to each release, please refer to the [blog](https://blog.gitea.io). +## [1.15.3](https://github.com/go-gitea/gitea/releases/tag/v1.15.3) - 2021-09-19 + +* ENHANCEMENTS + * Add fluid to ui container class to remove margin (#16396) (#16976) + * Add caller to cat-file batch calls (#17082) (#17089) +* BUGFIXES + * Render full plain readme. (#17083) (#17090) + * Upgrade xorm to v1.2.4 (#17059) + * Fix bug of migrate comments which only fetch one page (#17055) (#17058) + * Do not show issue context popup on external issues (#17050) (#17054) + * Decrement Fork Num when converting from Fork (#17035) (#17046) + * Correctly rollback in ForkRepository (#17034) (#17045) + * Fix missing close in WalkGitLog (#17008) (#17009) + * Add prefix to SVG id/class attributes (#16997) (#17000) + * Fix bug of migrated repository not index (#16991) (#16996) + * Skip AllowedUserVisibilityModes validation on update user if it is an organisation (#16988) (#16990) + * Fix storage Iterate bug and Add storage doctor to delete garbage attachments (#16971) (#16977) + * Fix issue with issue default mail template (#16956) (#16975) + * Ensure that rebase conflicts are handled in updates (#16952) (#16960) + * Prevent panic on diff generation (#16950) (#16951) + ## [1.15.2](https://github.com/go-gitea/gitea/releases/tag/v1.15.2) - 2021-09-03 * BUGFIXES diff --git a/MAINTAINERS b/MAINTAINERS index f43ba5ab99065..926a308d1eb6c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -44,3 +44,4 @@ Patrick Schratz (@pat-s) Janis Estelmann (@KN4CK3R) Steven Kriegler (@justusbunsi) Jimmy Praet (@jpraet) +Leon Hofmeister (@delvh) diff --git a/build/generate-bindata.go b/build/generate-bindata.go index efd172f779aac..477139d67bded 100644 --- a/build/generate-bindata.go +++ b/build/generate-bindata.go @@ -11,7 +11,6 @@ import ( "bytes" "crypto/sha1" "fmt" - "io/ioutil" "log" "net/http" "os" @@ -28,7 +27,7 @@ func needsUpdate(dir string, filename string) (bool, []byte) { needRegen = true } - oldHash, err := ioutil.ReadFile(filename + ".hash") + oldHash, err := os.ReadFile(filename + ".hash") if err != nil { oldHash = []byte{} } @@ -83,5 +82,5 @@ func main() { if err != nil { log.Fatalf("%v\n", err) } - _ = ioutil.WriteFile(filename+".hash", newHash, 0666) + _ = os.WriteFile(filename+".hash", newHash, 0666) } diff --git a/build/generate-emoji.go b/build/generate-emoji.go index da4e12373ce09..aa56d45f74402 100644 --- a/build/generate-emoji.go +++ b/build/generate-emoji.go @@ -12,9 +12,10 @@ import ( "flag" "fmt" "go/format" - "io/ioutil" + "io" "log" "net/http" + "os" "regexp" "sort" "strconv" @@ -67,7 +68,7 @@ func main() { } // write - err = ioutil.WriteFile(*flagOut, buf, 0644) + err = os.WriteFile(*flagOut, buf, 0644) if err != nil { log.Fatal(err) } @@ -96,7 +97,7 @@ func generate() ([]byte, error) { defer res.Body.Close() // read all - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) if err != nil { return nil, err } @@ -157,7 +158,7 @@ func generate() ([]byte, error) { // write a JSON file to use with tribute (write before adding skin tones since we can't support them there yet) file, _ := json.Marshal(data) - _ = ioutil.WriteFile("assets/emoji.json", file, 0644) + _ = os.WriteFile("assets/emoji.json", file, 0644) // Add skin tones to emoji that support it var ( diff --git a/build/generate-gitignores.go b/build/generate-gitignores.go index d0b972e803e00..811953ee4a164 100644 --- a/build/generate-gitignores.go +++ b/build/generate-gitignores.go @@ -9,7 +9,6 @@ import ( "flag" "fmt" "io" - "io/ioutil" "log" "net/http" "os" @@ -34,7 +33,7 @@ func main() { flag.StringVar(&githubApiToken, "token", "", "github api token") flag.Parse() - file, err := ioutil.TempFile(os.TempDir(), prefix) + file, err := os.CreateTemp(os.TempDir(), prefix) if err != nil { log.Fatalf("Failed to create temp file. %s", err) @@ -114,13 +113,13 @@ func main() { for dst, src := range filesToCopy { // Read all content of src to data src = path.Join(destination, src) - data, err := ioutil.ReadFile(src) + data, err := os.ReadFile(src) if err != nil { log.Fatalf("Failed to read src file. %s", err) } // Write data to dst dst = path.Join(destination, dst) - err = ioutil.WriteFile(dst, data, 0644) + err = os.WriteFile(dst, data, 0644) if err != nil { log.Fatalf("Failed to write new file. %s", err) } diff --git a/build/generate-licenses.go b/build/generate-licenses.go index 4009a0351d60a..75fb7cc8100a8 100644 --- a/build/generate-licenses.go +++ b/build/generate-licenses.go @@ -9,7 +9,6 @@ import ( "flag" "fmt" "io" - "io/ioutil" "log" "net/http" "os" @@ -34,7 +33,7 @@ func main() { flag.StringVar(&githubApiToken, "token", "", "github api token") flag.Parse() - file, err := ioutil.TempFile(os.TempDir(), prefix) + file, err := os.CreateTemp(os.TempDir(), prefix) if err != nil { log.Fatalf("Failed to create temp file. %s", err) diff --git a/cmd/admin.go b/cmd/admin.go index 94e78186c902f..099083ae9100b 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -14,6 +14,8 @@ import ( "text/tabwriter" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" @@ -21,6 +23,7 @@ import ( repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" + auth_service "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth/source/oauth2" "github.com/urfave/cli" @@ -288,6 +291,10 @@ var ( Value: "", Usage: "Custom icon URL for OAuth2 login source", }, + cli.BoolFlag{ + Name: "skip-local-2fa", + Usage: "Set to true to skip local 2fa for users authenticated by this source", + }, } microcmdAuthUpdateOauth = cli.Command{ @@ -525,7 +532,7 @@ func runRepoSyncReleases(_ *cli.Context) error { log.Trace("Synchronizing repository releases (this may take a while)") for page := 1; ; page++ { repos, count, err := models.SearchRepositoryByName(&models.SearchRepoOptions{ - ListOptions: models.ListOptions{ + ListOptions: db.ListOptions{ PageSize: models.RepositoryListDefaultPageSize, Page: page, }, @@ -616,6 +623,7 @@ func parseOAuth2Config(c *cli.Context) *oauth2.Source { OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"), CustomURLMapping: customURLMapping, IconURL: c.String("icon-url"), + SkipLocalTwoFA: c.Bool("skip-local-2fa"), } } @@ -624,8 +632,8 @@ func runAddOauth(c *cli.Context) error { return err } - return models.CreateLoginSource(&models.LoginSource{ - Type: models.LoginOAuth2, + return login.CreateSource(&login.Source{ + Type: login.OAuth2, Name: c.String("name"), IsActive: true, Cfg: parseOAuth2Config(c), @@ -641,7 +649,7 @@ func runUpdateOauth(c *cli.Context) error { return err } - source, err := models.GetLoginSourceByID(c.Int64("id")) + source, err := login.GetSourceByID(c.Int64("id")) if err != nil { return err } @@ -700,7 +708,7 @@ func runUpdateOauth(c *cli.Context) error { oAuth2Config.CustomURLMapping = customURLMapping source.Cfg = oAuth2Config - return models.UpdateSource(source) + return login.UpdateSource(source) } func runListAuth(c *cli.Context) error { @@ -708,7 +716,7 @@ func runListAuth(c *cli.Context) error { return err } - loginSources, err := models.LoginSources() + loginSources, err := login.Sources() if err != nil { return err @@ -728,7 +736,7 @@ func runListAuth(c *cli.Context) error { w := tabwriter.NewWriter(os.Stdout, c.Int("min-width"), c.Int("tab-width"), c.Int("padding"), padChar, flags) fmt.Fprintf(w, "ID\tName\tType\tEnabled\n") for _, source := range loginSources { - fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, models.LoginNames[source.Type], source.IsActive) + fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, source.Type.String(), source.IsActive) } w.Flush() @@ -744,10 +752,10 @@ func runDeleteAuth(c *cli.Context) error { return err } - source, err := models.GetLoginSourceByID(c.Int64("id")) + source, err := login.GetSourceByID(c.Int64("id")) if err != nil { return err } - return models.DeleteSource(source) + return auth_service.DeleteLoginSource(source) } diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index 4314930a3e083..517904957f1da 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -8,7 +8,7 @@ import ( "fmt" "strings" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/services/auth/source/ldap" "github.com/urfave/cli" @@ -17,9 +17,9 @@ import ( type ( authService struct { initDB func() error - createLoginSource func(loginSource *models.LoginSource) error - updateLoginSource func(loginSource *models.LoginSource) error - getLoginSourceByID func(id int64) (*models.LoginSource, error) + createLoginSource func(loginSource *login.Source) error + updateLoginSource func(loginSource *login.Source) error + getLoginSourceByID func(id int64) (*login.Source, error) } ) @@ -89,6 +89,14 @@ var ( Name: "public-ssh-key-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s public ssh key.", }, + cli.BoolFlag{ + Name: "skip-local-2fa", + Usage: "Set to true to skip local 2fa for users authenticated by this source", + }, + cli.StringFlag{ + Name: "avatar-attribute", + Usage: "The attribute of the user’s LDAP record containing the user’s avatar.", + }, } ldapBindDnCLIFlags = append(commonLdapCLIFlags, @@ -160,14 +168,14 @@ var ( func newAuthService() *authService { return &authService{ initDB: initDB, - createLoginSource: models.CreateLoginSource, - updateLoginSource: models.UpdateSource, - getLoginSourceByID: models.GetLoginSourceByID, + createLoginSource: login.CreateSource, + updateLoginSource: login.UpdateSource, + getLoginSourceByID: login.GetSourceByID, } } // parseLoginSource assigns values on loginSource according to command line flags. -func parseLoginSource(c *cli.Context, loginSource *models.LoginSource) { +func parseLoginSource(c *cli.Context, loginSource *login.Source) { if c.IsSet("name") { loginSource.Name = c.String("name") } @@ -230,6 +238,9 @@ func parseLdapConfig(c *cli.Context, config *ldap.Source) error { if c.IsSet("public-ssh-key-attribute") { config.AttributeSSHPublicKey = c.String("public-ssh-key-attribute") } + if c.IsSet("avatar-attribute") { + config.AttributeAvatar = c.String("avatar-attribute") + } if c.IsSet("page-size") { config.SearchPageSize = uint32(c.Uint("page-size")) } @@ -245,6 +256,10 @@ func parseLdapConfig(c *cli.Context, config *ldap.Source) error { if c.IsSet("allow-deactivate-all") { config.AllowDeactivateAll = c.Bool("allow-deactivate-all") } + if c.IsSet("skip-local-2fa") { + config.SkipLocalTwoFA = c.Bool("skip-local-2fa") + } + return nil } @@ -261,7 +276,7 @@ func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) { // getLoginSource gets the login source by its id defined in the command line flags. // It returns an error if the id is not set, does not match any source or if the source is not of expected type. -func (a *authService) getLoginSource(c *cli.Context, loginType models.LoginType) (*models.LoginSource, error) { +func (a *authService) getLoginSource(c *cli.Context, loginType login.Type) (*login.Source, error) { if err := argsSet(c, "id"); err != nil { return nil, err } @@ -272,7 +287,7 @@ func (a *authService) getLoginSource(c *cli.Context, loginType models.LoginType) } if loginSource.Type != loginType { - return nil, fmt.Errorf("Invalid authentication type. expected: %s, actual: %s", models.LoginNames[loginType], models.LoginNames[loginSource.Type]) + return nil, fmt.Errorf("Invalid authentication type. expected: %s, actual: %s", loginType.String(), loginSource.Type.String()) } return loginSource, nil @@ -288,8 +303,8 @@ func (a *authService) addLdapBindDn(c *cli.Context) error { return err } - loginSource := &models.LoginSource{ - Type: models.LoginLDAP, + loginSource := &login.Source{ + Type: login.LDAP, IsActive: true, // active by default Cfg: &ldap.Source{ Enabled: true, // always true @@ -310,7 +325,7 @@ func (a *authService) updateLdapBindDn(c *cli.Context) error { return err } - loginSource, err := a.getLoginSource(c, models.LoginLDAP) + loginSource, err := a.getLoginSource(c, login.LDAP) if err != nil { return err } @@ -333,8 +348,8 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error { return err } - loginSource := &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource := &login.Source{ + Type: login.DLDAP, IsActive: true, // active by default Cfg: &ldap.Source{ Enabled: true, // always true @@ -355,7 +370,7 @@ func (a *authService) updateLdapSimpleAuth(c *cli.Context) error { return err } - loginSource, err := a.getLoginSource(c, models.LoginDLDAP) + loginSource, err := a.getLoginSource(c, login.DLDAP) if err != nil { return err } diff --git a/cmd/admin_auth_ldap_test.go b/cmd/admin_auth_ldap_test.go index 692b11e3f422d..db1ba13bcd5b3 100644 --- a/cmd/admin_auth_ldap_test.go +++ b/cmd/admin_auth_ldap_test.go @@ -7,7 +7,7 @@ package cmd import ( "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/services/auth/source/ldap" "github.com/stretchr/testify/assert" @@ -23,7 +23,7 @@ func TestAddLdapBindDn(t *testing.T) { // Test cases var cases = []struct { args []string - loginSource *models.LoginSource + loginSource *login.Source errMsg string }{ // case 0 @@ -45,14 +45,15 @@ func TestAddLdapBindDn(t *testing.T) { "--surname-attribute", "sn-bind full", "--email-attribute", "mail-bind full", "--public-ssh-key-attribute", "publickey-bind full", + "--avatar-attribute", "avatar-bind full", "--bind-dn", "cn=readonly,dc=full-domain-bind,dc=org", "--bind-password", "secret-bind-full", "--attributes-in-bind", "--synchronize-users", "--page-size", "99", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Name: "ldap (via Bind DN) source full", IsActive: false, IsSyncEnabled: true, @@ -71,6 +72,7 @@ func TestAddLdapBindDn(t *testing.T) { AttributeMail: "mail-bind full", AttributesInBind: true, AttributeSSHPublicKey: "publickey-bind full", + AttributeAvatar: "avatar-bind full", SearchPageSize: 99, Filter: "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)", AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)", @@ -91,8 +93,8 @@ func TestAddLdapBindDn(t *testing.T) { "--user-filter", "(memberOf=cn=user-group,ou=example,dc=min-domain-bind,dc=org)", "--email-attribute", "mail-bind min", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Name: "ldap (via Bind DN) source min", IsActive: true, Cfg: &ldap.Source{ @@ -203,20 +205,20 @@ func TestAddLdapBindDn(t *testing.T) { for n, c := range cases { // Mock functions. - var createdLoginSource *models.LoginSource + var createdLoginSource *login.Source service := &authService{ initDB: func() error { return nil }, - createLoginSource: func(loginSource *models.LoginSource) error { + createLoginSource: func(loginSource *login.Source) error { createdLoginSource = loginSource return nil }, - updateLoginSource: func(loginSource *models.LoginSource) error { + updateLoginSource: func(loginSource *login.Source) error { assert.FailNow(t, "case %d: should not call updateLoginSource", n) return nil }, - getLoginSourceByID: func(id int64) (*models.LoginSource, error) { + getLoginSourceByID: func(id int64) (*login.Source, error) { assert.FailNow(t, "case %d: should not call getLoginSourceByID", n) return nil, nil }, @@ -247,7 +249,7 @@ func TestAddLdapSimpleAuth(t *testing.T) { // Test cases var cases = []struct { args []string - loginSource *models.LoginSource + loginSource *login.Source errMsg string }{ // case 0 @@ -269,10 +271,11 @@ func TestAddLdapSimpleAuth(t *testing.T) { "--surname-attribute", "sn-simple full", "--email-attribute", "mail-simple full", "--public-ssh-key-attribute", "publickey-simple full", + "--avatar-attribute", "avatar-simple full", "--user-dn", "cn=%s,ou=Users,dc=full-domain-simple,dc=org", }, - loginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource: &login.Source{ + Type: login.DLDAP, Name: "ldap (simple auth) source full", IsActive: false, Cfg: &ldap.Source{ @@ -288,6 +291,7 @@ func TestAddLdapSimpleAuth(t *testing.T) { AttributeSurname: "sn-simple full", AttributeMail: "mail-simple full", AttributeSSHPublicKey: "publickey-simple full", + AttributeAvatar: "avatar-simple full", Filter: "(&(objectClass=posixAccount)(full-simple-cn=%s))", AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)", RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-simple,dc=org)", @@ -307,8 +311,8 @@ func TestAddLdapSimpleAuth(t *testing.T) { "--email-attribute", "mail-simple min", "--user-dn", "cn=%s,ou=Users,dc=min-domain-simple,dc=org", }, - loginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource: &login.Source{ + Type: login.DLDAP, Name: "ldap (simple auth) source min", IsActive: true, Cfg: &ldap.Source{ @@ -432,20 +436,20 @@ func TestAddLdapSimpleAuth(t *testing.T) { for n, c := range cases { // Mock functions. - var createdLoginSource *models.LoginSource + var createdLoginSource *login.Source service := &authService{ initDB: func() error { return nil }, - createLoginSource: func(loginSource *models.LoginSource) error { + createLoginSource: func(loginSource *login.Source) error { createdLoginSource = loginSource return nil }, - updateLoginSource: func(loginSource *models.LoginSource) error { + updateLoginSource: func(loginSource *login.Source) error { assert.FailNow(t, "case %d: should not call updateLoginSource", n) return nil }, - getLoginSourceByID: func(id int64) (*models.LoginSource, error) { + getLoginSourceByID: func(id int64) (*login.Source, error) { assert.FailNow(t, "case %d: should not call getLoginSourceByID", n) return nil, nil }, @@ -477,8 +481,8 @@ func TestUpdateLdapBindDn(t *testing.T) { var cases = []struct { args []string id int64 - existingLoginSource *models.LoginSource - loginSource *models.LoginSource + existingLoginSource *login.Source + loginSource *login.Source errMsg string }{ // case 0 @@ -501,21 +505,22 @@ func TestUpdateLdapBindDn(t *testing.T) { "--surname-attribute", "sn-bind full", "--email-attribute", "mail-bind full", "--public-ssh-key-attribute", "publickey-bind full", + "--avatar-attribute", "avatar-bind full", "--bind-dn", "cn=readonly,dc=full-domain-bind,dc=org", "--bind-password", "secret-bind-full", "--synchronize-users", "--page-size", "99", }, id: 23, - existingLoginSource: &models.LoginSource{ - Type: models.LoginLDAP, + existingLoginSource: &login.Source{ + Type: login.LDAP, IsActive: true, Cfg: &ldap.Source{ Enabled: true, }, }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Name: "ldap (via Bind DN) source full", IsActive: false, IsSyncEnabled: true, @@ -534,6 +539,7 @@ func TestUpdateLdapBindDn(t *testing.T) { AttributeMail: "mail-bind full", AttributesInBind: false, AttributeSSHPublicKey: "publickey-bind full", + AttributeAvatar: "avatar-bind full", SearchPageSize: 99, Filter: "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)", AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)", @@ -548,8 +554,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "ldap-test", "--id", "1", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Cfg: &ldap.Source{}, }, }, @@ -560,8 +566,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--name", "ldap (via Bind DN) source", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Name: "ldap (via Bind DN) source", Cfg: &ldap.Source{ Name: "ldap (via Bind DN) source", @@ -575,13 +581,13 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--not-active", }, - existingLoginSource: &models.LoginSource{ - Type: models.LoginLDAP, + existingLoginSource: &login.Source{ + Type: login.LDAP, IsActive: true, Cfg: &ldap.Source{}, }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, IsActive: false, Cfg: &ldap.Source{}, }, @@ -593,8 +599,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--security-protocol", "LDAPS", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Cfg: &ldap.Source{ SecurityProtocol: ldap.SecurityProtocol(1), }, @@ -607,8 +613,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--skip-tls-verify", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Cfg: &ldap.Source{ SkipVerify: true, }, @@ -621,8 +627,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--host", "ldap-server", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Cfg: &ldap.Source{ Host: "ldap-server", }, @@ -635,8 +641,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--port", "389", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Cfg: &ldap.Source{ Port: 389, }, @@ -649,8 +655,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--user-search-base", "ou=Users,dc=domain,dc=org", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Cfg: &ldap.Source{ UserBase: "ou=Users,dc=domain,dc=org", }, @@ -663,8 +669,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Cfg: &ldap.Source{ Filter: "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", }, @@ -677,8 +683,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Cfg: &ldap.Source{ AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)", }, @@ -691,8 +697,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--username-attribute", "uid", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Cfg: &ldap.Source{ AttributeUsername: "uid", }, @@ -705,8 +711,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--firstname-attribute", "givenName", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Cfg: &ldap.Source{ AttributeName: "givenName", }, @@ -719,8 +725,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--surname-attribute", "sn", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Cfg: &ldap.Source{ AttributeSurname: "sn", }, @@ -733,8 +739,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--email-attribute", "mail", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Cfg: &ldap.Source{ AttributeMail: "mail", }, @@ -747,8 +753,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--attributes-in-bind", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Cfg: &ldap.Source{ AttributesInBind: true, }, @@ -761,8 +767,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--public-ssh-key-attribute", "publickey", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Cfg: &ldap.Source{ AttributeSSHPublicKey: "publickey", }, @@ -775,8 +781,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--bind-dn", "cn=readonly,dc=domain,dc=org", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Cfg: &ldap.Source{ BindDN: "cn=readonly,dc=domain,dc=org", }, @@ -789,8 +795,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--bind-password", "secret", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Cfg: &ldap.Source{ BindPassword: "secret", }, @@ -803,8 +809,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--synchronize-users", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, IsSyncEnabled: true, Cfg: &ldap.Source{}, }, @@ -816,8 +822,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--page-size", "12", }, - loginSource: &models.LoginSource{ - Type: models.LoginLDAP, + loginSource: &login.Source{ + Type: login.LDAP, Cfg: &ldap.Source{ SearchPageSize: 12, }, @@ -845,8 +851,8 @@ func TestUpdateLdapBindDn(t *testing.T) { "ldap-test", "--id", "1", }, - existingLoginSource: &models.LoginSource{ - Type: models.LoginOAuth2, + existingLoginSource: &login.Source{ + Type: login.OAuth2, Cfg: &ldap.Source{}, }, errMsg: "Invalid authentication type. expected: LDAP (via BindDN), actual: OAuth2", @@ -855,28 +861,28 @@ func TestUpdateLdapBindDn(t *testing.T) { for n, c := range cases { // Mock functions. - var updatedLoginSource *models.LoginSource + var updatedLoginSource *login.Source service := &authService{ initDB: func() error { return nil }, - createLoginSource: func(loginSource *models.LoginSource) error { + createLoginSource: func(loginSource *login.Source) error { assert.FailNow(t, "case %d: should not call createLoginSource", n) return nil }, - updateLoginSource: func(loginSource *models.LoginSource) error { + updateLoginSource: func(loginSource *login.Source) error { updatedLoginSource = loginSource return nil }, - getLoginSourceByID: func(id int64) (*models.LoginSource, error) { + getLoginSourceByID: func(id int64) (*login.Source, error) { if c.id != 0 { assert.Equal(t, c.id, id, "case %d: wrong id", n) } if c.existingLoginSource != nil { return c.existingLoginSource, nil } - return &models.LoginSource{ - Type: models.LoginLDAP, + return &login.Source{ + Type: login.LDAP, Cfg: &ldap.Source{}, }, nil }, @@ -908,8 +914,8 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { var cases = []struct { args []string id int64 - existingLoginSource *models.LoginSource - loginSource *models.LoginSource + existingLoginSource *login.Source + loginSource *login.Source errMsg string }{ // case 0 @@ -932,11 +938,12 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "--surname-attribute", "sn-simple full", "--email-attribute", "mail-simple full", "--public-ssh-key-attribute", "publickey-simple full", + "--avatar-attribute", "avatar-simple full", "--user-dn", "cn=%s,ou=Users,dc=full-domain-simple,dc=org", }, id: 7, - loginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource: &login.Source{ + Type: login.DLDAP, Name: "ldap (simple auth) source full", IsActive: false, Cfg: &ldap.Source{ @@ -952,6 +959,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { AttributeSurname: "sn-simple full", AttributeMail: "mail-simple full", AttributeSSHPublicKey: "publickey-simple full", + AttributeAvatar: "avatar-simple full", Filter: "(&(objectClass=posixAccount)(full-simple-cn=%s))", AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)", RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-simple,dc=org)", @@ -964,8 +972,8 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "ldap-test", "--id", "1", }, - loginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource: &login.Source{ + Type: login.DLDAP, Cfg: &ldap.Source{}, }, }, @@ -976,8 +984,8 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "--id", "1", "--name", "ldap (simple auth) source", }, - loginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource: &login.Source{ + Type: login.DLDAP, Name: "ldap (simple auth) source", Cfg: &ldap.Source{ Name: "ldap (simple auth) source", @@ -991,13 +999,13 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "--id", "1", "--not-active", }, - existingLoginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + existingLoginSource: &login.Source{ + Type: login.DLDAP, IsActive: true, Cfg: &ldap.Source{}, }, - loginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource: &login.Source{ + Type: login.DLDAP, IsActive: false, Cfg: &ldap.Source{}, }, @@ -1009,8 +1017,8 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "--id", "1", "--security-protocol", "starttls", }, - loginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource: &login.Source{ + Type: login.DLDAP, Cfg: &ldap.Source{ SecurityProtocol: ldap.SecurityProtocol(2), }, @@ -1023,8 +1031,8 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "--id", "1", "--skip-tls-verify", }, - loginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource: &login.Source{ + Type: login.DLDAP, Cfg: &ldap.Source{ SkipVerify: true, }, @@ -1037,8 +1045,8 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "--id", "1", "--host", "ldap-server", }, - loginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource: &login.Source{ + Type: login.DLDAP, Cfg: &ldap.Source{ Host: "ldap-server", }, @@ -1051,8 +1059,8 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "--id", "1", "--port", "987", }, - loginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource: &login.Source{ + Type: login.DLDAP, Cfg: &ldap.Source{ Port: 987, }, @@ -1065,8 +1073,8 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "--id", "1", "--user-search-base", "ou=Users,dc=domain,dc=org", }, - loginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource: &login.Source{ + Type: login.DLDAP, Cfg: &ldap.Source{ UserBase: "ou=Users,dc=domain,dc=org", }, @@ -1079,8 +1087,8 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "--id", "1", "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", }, - loginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource: &login.Source{ + Type: login.DLDAP, Cfg: &ldap.Source{ Filter: "(&(objectClass=posixAccount)(cn=%s))", }, @@ -1093,8 +1101,8 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "--id", "1", "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)", }, - loginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource: &login.Source{ + Type: login.DLDAP, Cfg: &ldap.Source{ AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)", }, @@ -1107,8 +1115,8 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "--id", "1", "--username-attribute", "uid", }, - loginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource: &login.Source{ + Type: login.DLDAP, Cfg: &ldap.Source{ AttributeUsername: "uid", }, @@ -1121,8 +1129,8 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "--id", "1", "--firstname-attribute", "givenName", }, - loginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource: &login.Source{ + Type: login.DLDAP, Cfg: &ldap.Source{ AttributeName: "givenName", }, @@ -1135,8 +1143,8 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "--id", "1", "--surname-attribute", "sn", }, - loginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource: &login.Source{ + Type: login.DLDAP, Cfg: &ldap.Source{ AttributeSurname: "sn", }, @@ -1149,8 +1157,8 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "--id", "1", "--email-attribute", "mail", }, - loginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource: &login.Source{ + Type: login.DLDAP, Cfg: &ldap.Source{ AttributeMail: "mail", @@ -1164,8 +1172,8 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "--id", "1", "--public-ssh-key-attribute", "publickey", }, - loginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource: &login.Source{ + Type: login.DLDAP, Cfg: &ldap.Source{ AttributeSSHPublicKey: "publickey", }, @@ -1178,8 +1186,8 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "--id", "1", "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", }, - loginSource: &models.LoginSource{ - Type: models.LoginDLDAP, + loginSource: &login.Source{ + Type: login.DLDAP, Cfg: &ldap.Source{ UserDN: "cn=%s,ou=Users,dc=domain,dc=org", }, @@ -1207,8 +1215,8 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "ldap-test", "--id", "1", }, - existingLoginSource: &models.LoginSource{ - Type: models.LoginPAM, + existingLoginSource: &login.Source{ + Type: login.PAM, Cfg: &ldap.Source{}, }, errMsg: "Invalid authentication type. expected: LDAP (simple auth), actual: PAM", @@ -1217,28 +1225,28 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { for n, c := range cases { // Mock functions. - var updatedLoginSource *models.LoginSource + var updatedLoginSource *login.Source service := &authService{ initDB: func() error { return nil }, - createLoginSource: func(loginSource *models.LoginSource) error { + createLoginSource: func(loginSource *login.Source) error { assert.FailNow(t, "case %d: should not call createLoginSource", n) return nil }, - updateLoginSource: func(loginSource *models.LoginSource) error { + updateLoginSource: func(loginSource *login.Source) error { updatedLoginSource = loginSource return nil }, - getLoginSourceByID: func(id int64) (*models.LoginSource, error) { + getLoginSourceByID: func(id int64) (*login.Source, error) { if c.id != 0 { assert.Equal(t, c.id, id, "case %d: wrong id", n) } if c.existingLoginSource != nil { return c.existingLoginSource, nil } - return &models.LoginSource{ - Type: models.LoginDLDAP, + return &login.Source{ + Type: login.DLDAP, Cfg: &ldap.Source{}, }, nil }, diff --git a/cmd/cmd.go b/cmd/cmd.go index 8d9d1ee077ed5..bb7ab8f9b7c25 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -15,7 +15,7 @@ import ( "strings" "syscall" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -65,7 +65,7 @@ func initDBDisableConsole(disableConsole bool) error { setting.InitDBConfig() setting.NewXORMLogService(disableConsole) - if err := models.SetEngine(); err != nil { + if err := db.SetEngine(); err != nil { return fmt.Errorf("models.SetEngine: %v", err) } return nil diff --git a/cmd/convert.go b/cmd/convert.go index e2ffd403acbc3..20e4045c3497f 100644 --- a/cmd/convert.go +++ b/cmd/convert.go @@ -7,7 +7,7 @@ package cmd import ( "fmt" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -31,6 +31,7 @@ func runConvert(ctx *cli.Context) error { log.Info("AppWorkPath: %s", setting.AppWorkPath) log.Info("Custom path: %s", setting.CustomPath) log.Info("Log path: %s", setting.LogRootPath) + log.Info("Configuration file: %s", setting.CustomConf) setting.InitDBConfig() if !setting.Database.UseMySQL { @@ -38,7 +39,7 @@ func runConvert(ctx *cli.Context) error { return nil } - if err := models.ConvertUtf8ToUtf8mb4(); err != nil { + if err := db.ConvertUtf8ToUtf8mb4(); err != nil { log.Fatal("Failed to convert database from utf8 to utf8mb4: %v", err) return err } diff --git a/cmd/doctor.go b/cmd/doctor.go index 2e7927543481c..31708e990de5c 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -12,7 +12,7 @@ import ( "strings" "text/tabwriter" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/migrations" "code.gitea.io/gitea/modules/doctor" "code.gitea.io/gitea/modules/log" @@ -96,7 +96,7 @@ func runRecreateTable(ctx *cli.Context) error { setting.Cfg.Section("log").Key("XORM").SetValue(",") setting.NewXORMLogService(!ctx.Bool("debug")) - if err := models.SetEngine(); err != nil { + if err := db.SetEngine(); err != nil { fmt.Println(err) fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.") return nil @@ -108,13 +108,13 @@ func runRecreateTable(ctx *cli.Context) error { names = append(names, args.Get(i)) } - beans, err := models.NamesToBean(names...) + beans, err := db.NamesToBean(names...) if err != nil { return err } recreateTables := migrations.RecreateTables(beans...) - return models.NewEngine(context.Background(), func(x *xorm.Engine) error { + return db.NewEngine(context.Background(), func(x *xorm.Engine) error { if err := migrations.EnsureUpToDate(x); err != nil { return err } diff --git a/cmd/dump.go b/cmd/dump.go index 9ce7c2636a0d2..fd89eb89f9078 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -7,14 +7,13 @@ package cmd import ( "fmt" - "io/ioutil" "os" "path" "path/filepath" "strings" "time" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -174,7 +173,7 @@ func runDump(ctx *cli.Context) error { } setting.NewServices() // cannot access session settings otherwise - err := models.SetEngine() + err := db.SetEngine() if err != nil { return err } @@ -247,7 +246,7 @@ func runDump(ctx *cli.Context) error { fatal("Path does not exist: %s", tmpDir) } - dbDump, err := ioutil.TempFile(tmpDir, "gitea-db.sql") + dbDump, err := os.CreateTemp(tmpDir, "gitea-db.sql") if err != nil { fatal("Failed to create tmp file: %v", err) } @@ -264,7 +263,7 @@ func runDump(ctx *cli.Context) error { log.Info("Dumping database...") } - if err := models.DumpDatabase(dbDump.Name(), targetDBType); err != nil { + if err := db.DumpDatabase(dbDump.Name(), targetDBType); err != nil { fatal("Failed to dump database: %v", err) } diff --git a/cmd/dump_repo.go b/cmd/dump_repo.go index 69813e3c872f6..3ea82f6d1a0cb 100644 --- a/cmd/dump_repo.go +++ b/cmd/dump_repo.go @@ -84,6 +84,7 @@ func runDumpRepository(ctx *cli.Context) error { log.Info("AppWorkPath: %s", setting.AppWorkPath) log.Info("Custom path: %s", setting.CustomPath) log.Info("Log path: %s", setting.LogRootPath) + log.Info("Configuration file: %s", setting.CustomConf) setting.InitDBConfig() var ( diff --git a/cmd/migrate.go b/cmd/migrate.go index 23bc97b0c41f2..a5dfed20b21da 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -7,7 +7,7 @@ package cmd import ( "context" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/migrations" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -32,9 +32,10 @@ func runMigrate(ctx *cli.Context) error { log.Info("AppWorkPath: %s", setting.AppWorkPath) log.Info("Custom path: %s", setting.CustomPath) log.Info("Log path: %s", setting.LogRootPath) + log.Info("Configuration file: %s", setting.CustomConf) setting.InitDBConfig() - if err := models.NewEngine(context.Background(), migrations.Migrate); err != nil { + if err := db.NewEngine(context.Background(), migrations.Migrate); err != nil { log.Fatal("Failed to initialize ORM engine: %v", err) return err } diff --git a/cmd/migrate_storage.go b/cmd/migrate_storage.go index 8123716f9ba95..d9ca8a08390eb 100644 --- a/cmd/migrate_storage.go +++ b/cmd/migrate_storage.go @@ -10,6 +10,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/migrations" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -114,9 +115,10 @@ func runMigrateStorage(ctx *cli.Context) error { log.Info("AppWorkPath: %s", setting.AppWorkPath) log.Info("Custom path: %s", setting.CustomPath) log.Info("Log path: %s", setting.LogRootPath) + log.Info("Configuration file: %s", setting.CustomConf) setting.InitDBConfig() - if err := models.NewEngine(context.Background(), migrations.Migrate); err != nil { + if err := db.NewEngine(context.Background(), migrations.Migrate); err != nil { log.Fatal("Failed to initialize ORM engine: %v", err) return err } diff --git a/contrib/fixtures/fixture_generation.go b/contrib/fixtures/fixture_generation.go index 802a2ca30c935..5e7dd39a78fb1 100644 --- a/contrib/fixtures/fixture_generation.go +++ b/contrib/fixtures/fixture_generation.go @@ -6,11 +6,11 @@ package main import ( "fmt" - "io/ioutil" "os" "path/filepath" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) // To generate derivative fixtures, execute the following from Gitea's repository base dir: @@ -31,11 +31,13 @@ var ( func main() { pathToGiteaRoot := "." fixturesDir = filepath.Join(pathToGiteaRoot, "models", "fixtures") - if err := models.CreateTestEngine(fixturesDir); err != nil { + if err := db.CreateTestEngine(db.FixturesOptions{ + Dir: fixturesDir, + }); err != nil { fmt.Printf("CreateTestEngine: %+v", err) os.Exit(1) } - if err := models.PrepareTestDatabase(); err != nil { + if err := db.PrepareTestDatabase(); err != nil { fmt.Printf("PrepareTestDatabase: %+v\n", err) os.Exit(1) } @@ -64,7 +66,7 @@ func generate(name string) error { return err } path := filepath.Join(fixturesDir, name+".yml") - if err := ioutil.WriteFile(path, []byte(data), 0644); err != nil { + if err := os.WriteFile(path, []byte(data), 0644); err != nil { return fmt.Errorf("%s: %+v", path, err) } fmt.Printf("%s created.\n", path) diff --git a/contrib/pr/checkout.go b/contrib/pr/checkout.go index 902c9ea623473..d831ebdabdb2f 100644 --- a/contrib/pr/checkout.go +++ b/contrib/pr/checkout.go @@ -12,7 +12,6 @@ import ( "context" "flag" "fmt" - "io/ioutil" "log" "net/http" "net/url" @@ -26,6 +25,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" gitea_git "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/external" @@ -51,11 +51,11 @@ func runPR() { setting.SetCustomPathAndConf("", "", "") setting.NewContext() - setting.RepoRootPath, err = ioutil.TempDir(os.TempDir(), "repos") + setting.RepoRootPath, err = os.MkdirTemp(os.TempDir(), "repos") if err != nil { log.Fatalf("TempDir: %v\n", err) } - setting.AppDataPath, err = ioutil.TempDir(os.TempDir(), "appdata") + setting.AppDataPath, err = os.MkdirTemp(os.TempDir(), "appdata") if err != nil { log.Fatalf("TempDir: %v\n", err) } @@ -87,27 +87,29 @@ func runPR() { setting.Database.Path = ":memory:" setting.Database.Timeout = 500 */ - db := setting.Cfg.Section("database") - db.NewKey("DB_TYPE", "sqlite3") - db.NewKey("PATH", ":memory:") + dbCfg := setting.Cfg.Section("database") + dbCfg.NewKey("DB_TYPE", "sqlite3") + dbCfg.NewKey("PATH", ":memory:") routers.NewServices() setting.Database.LogSQL = true //x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared") - models.NewEngine(context.Background(), func(_ *xorm.Engine) error { + db.NewEngine(context.Background(), func(_ *xorm.Engine) error { return nil }) - models.HasEngine = true + db.HasEngine = true //x.ShowSQL(true) - err = models.InitFixtures( - path.Join(curDir, "models/fixtures/"), + err = db.InitFixtures( + db.FixturesOptions{ + Dir: path.Join(curDir, "models/fixtures/"), + }, ) if err != nil { fmt.Printf("Error initializing test database: %v\n", err) os.Exit(1) } - models.LoadFixtures() + db.LoadFixtures() util.RemoveAll(setting.RepoRootPath) util.RemoveAll(models.LocalCopyPath()) util.CopyDir(path.Join(curDir, "integrations/gitea-repositories-meta"), setting.RepoRootPath) @@ -180,7 +182,7 @@ func main() { codeFilePath = filepath.FromSlash(codeFilePath) //Convert to running OS //Copy this file if it will not exist in the PR branch - dat, err := ioutil.ReadFile(codeFilePath) + dat, err := os.ReadFile(codeFilePath) if err != nil { log.Fatalf("Failed to cache this code file : %v", err) } @@ -244,7 +246,7 @@ func main() { if err != nil { log.Fatalf("Failed to duplicate this code file in PR : %v", err) } - err = ioutil.WriteFile(codeFilePath, dat, 0644) + err = os.WriteFile(codeFilePath, dat, 0644) if err != nil { log.Fatalf("Failed to duplicate this code file in PR : %v", err) } diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index f8a4dda4f2226..0a25e7e5cd540 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1035,10 +1035,10 @@ PATH = ;SHOW_USER_EMAIL = true ;; ;; Set the default theme for the Gitea install -;DEFAULT_THEME = gitea +;DEFAULT_THEME = auto ;; ;; All available themes. Allow users select personalized themes regardless of the value of `DEFAULT_THEME`. -;THEMES = gitea,arc-green +;THEMES = auto,gitea,arc-green ;; ;; All available reactions users can choose on issues/prs and comments. ;; Values can be emoji alias (:smile:) or a unicode emoji. @@ -1883,7 +1883,7 @@ PATH = ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;ENABLED = false ;RUN_AT_START = false -;NO_SUCCESS_NOTICE = f;alse +;NO_SUCCESS_NOTICE = false ;SCHEDULE = @every 72h ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1907,7 +1907,7 @@ PATH = ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;ENABLED = false ;RUN_AT_START = false -;NO_SUCCESS_NOTICE = f;alse +;NO_SUCCESS_NOTICE = false ;SCHEDULE = @every 72h ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -2041,6 +2041,10 @@ PATH = ;ENABLED = false ;; If you want to add authorization, specify a token here ;TOKEN = +;; Enable issue by label metrics; default is false +;ENABLED_ISSUE_BY_LABEL = false +;; Enable issue by repository metrics; default is false +;ENABLED_ISSUE_BY_REPOSITORY = false ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -2081,6 +2085,15 @@ PATH = ;; Allow private addresses defined by RFC 1918, RFC 1122, RFC 4632 and RFC 4291 (false by default) ;ALLOW_LOCALNETWORKS = false +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[federation] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Enable/Disable federation capabilities +; ENABLED = true + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; default storage for attachments, lfs and avatars diff --git a/docs/config.yaml b/docs/config.yaml index 69f4e60b9a497..05e038af553a0 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -18,7 +18,7 @@ params: description: Git with a cup of tea author: The Gitea Authors website: https://docs.gitea.io - version: 1.15.2 + version: 1.15.3 minGoVersion: 1.16 goVersion: 1.17 minNodeVersion: 12.17 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 6c2c92b814d6f..d224533e964c3 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -115,6 +115,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `ALLOWED_TYPES`: **\**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. - `DEFAULT_PAGING_NUM`: **10**: The default paging number of releases user interface +- For settings related to file attachments on releases, see the `attachment` section. ### Repository - Signing (`repository.signing`) @@ -173,9 +174,9 @@ The following configuration set `Content-Type: application/vnd.android.package-a - `FEED_PAGING_NUM`: **20**: Number of items that are displayed in home feed. - `GRAPH_MAX_COMMIT_NUM`: **100**: Number of maximum commits shown in the commit graph. - `CODE_COMMENT_LINES`: **4**: Number of line of codes shown for a code comment. -- `DEFAULT_THEME`: **gitea**: \[gitea, arc-green\]: Set the default theme for the Gitea install. +- `DEFAULT_THEME`: **auto**: \[auto, gitea, arc-green\]: Set the default theme for the Gitea install. - `SHOW_USER_EMAIL`: **true**: Whether the email of the user should be shown in the Explore Users page. -- `THEMES`: **gitea,arc-green**: All available themes. Allow users select personalized themes. +- `THEMES`: **auto,gitea,arc-green**: All available themes. Allow users select personalized themes. regardless of the value of `DEFAULT_THEME`. - `THEME_COLOR_META_TAG`: **#6cc644**: Value of `theme-color` meta tag, used by Android >= 5.0. An invalid color like "none" or "disable" will have the default style. More info: https://developers.google.com/web/updates/2014/11/Support-for-theme-color-in-Chrome-39-for-Android - `MAX_DISPLAY_FILE_SIZE`: **8388608**: Max size of files to be displayed (default is 8MiB) @@ -852,6 +853,8 @@ NB: You must have `DISABLE_ROUTER_LOG` set to `false` for this option to take ef ## Metrics (`metrics`) - `ENABLED`: **false**: Enables /metrics endpoint for prometheus. +- `ENABLED_ISSUE_BY_LABEL`: **false**: Enable issue by label metrics with format `gitea_issues_by_label{label="bug"} 2`. +- `ENABLED_ISSUE_BY_REPOSITORY`: **false**: Enable issue by repository metrics with format `gitea_issues_by_repository{repository="org/repo"} 5`. - `TOKEN`: **\**: You need to specify the token, if you want to include in the authorization the metrics . The same token need to be used in prometheus parameters `bearer_token` or `bearer_token_file`. ## API (`api`) @@ -952,6 +955,10 @@ Task queue configuration has been moved to `queue.task`. However, the below conf - `ALLOW_LOCALNETWORKS`: **false**: Allow private addresses defined by RFC 1918, RFC 1122, RFC 4632 and RFC 4291 - `SKIP_TLS_VERIFY`: **false**: Allow skip tls verify +## Federation (`federation`) + +- `ENABLED`: **true**: Enable/Disable federation capabilities + ## Mirror (`mirror`) - `ENABLED`: **true**: Enables the mirror functionality. Set to **false** to disable all mirrors. diff --git a/docs/content/doc/advanced/customizing-gitea.en-us.md b/docs/content/doc/advanced/customizing-gitea.en-us.md index fe18a801f04ec..040dcfd3e85b0 100644 --- a/docs/content/doc/advanced/customizing-gitea.en-us.md +++ b/docs/content/doc/advanced/customizing-gitea.en-us.md @@ -321,7 +321,7 @@ A full list of supported emoji's is at [emoji list](https://gitea.com/gitea/gite ## Customizing the look of Gitea -The default built-in themes are `gitea` (light) and `arc-green` (dark). +The default built-in themes are `gitea` (light), `arc-green` (dark), and `auto` (chooses light or dark depending on operating system settings). The default theme can be changed via `DEFAULT_THEME` in the [ui](https://docs.gitea.io/en-us/config-cheat-sheet/#ui-ui) section of `app.ini`. Gitea also has support for user themes, which means every user can select which theme should be used. diff --git a/docs/content/doc/help/faq.en-us.md b/docs/content/doc/help/faq.en-us.md index 953aa9f0120de..e9551bbb71165 100644 --- a/docs/content/doc/help/faq.en-us.md +++ b/docs/content/doc/help/faq.en-us.md @@ -158,7 +158,7 @@ Use [Fail2Ban]({{< relref "doc/usage/fail2ban-setup.en-us.md" >}}) to monitor an ## How to add/use custom themes -Gitea supports two official themes right now, `gitea` and `arc-green` (`light` and `dark` respectively) +Gitea supports three official themes right now, `gitea` (light), `arc-green` (dark), and `auto` (automatically switches between the previous two depending on operating system settings). To add your own theme, currently the only way is to provide a complete theme (not just color overrides) As an example, let's say our theme is `arc-blue` (this is a real theme, and can be found [in this issue](https://github.com/go-gitea/gitea/issues/6011)) diff --git a/docs/content/doc/installation/database-preparation.en-us.md b/docs/content/doc/installation/database-preparation.en-us.md index 3b4a7e958d6df..13a215d81483c 100644 --- a/docs/content/doc/installation/database-preparation.en-us.md +++ b/docs/content/doc/installation/database-preparation.en-us.md @@ -15,7 +15,7 @@ menu: # Database Preparation -You need a database to use Gitea. Gitea supports PostgreSQL, MySQL, SQLite, and MSSQL. This page will guide into preparing database. Only PostgreSQL and MySQL will be covered here since those database engines are widely-used in production. +You need a database to use Gitea. Gitea supports PostgreSQL (>=10), MySQL (>=5.7), SQLite, and MSSQL (>=2008R2 SP3). This page will guide into preparing database. Only PostgreSQL and MySQL will be covered here since those database engines are widely-used in production. Database instance can be on same machine as Gitea (local database setup), or on different machine (remote database). diff --git a/docs/content/doc/usage/command-line.en-us.md b/docs/content/doc/usage/command-line.en-us.md index 0bc8d70fdb53a..b12400c57ec19 100644 --- a/docs/content/doc/usage/command-line.en-us.md +++ b/docs/content/doc/usage/command-line.en-us.md @@ -152,6 +152,7 @@ Admin operations: - `--surname-attribute value`: The attribute of the user’s LDAP record containing the user’s surname. - `--email-attribute value`: The attribute of the user’s LDAP record containing the user’s email address. Required. - `--public-ssh-key-attribute value`: The attribute of the user’s LDAP record containing the user’s public ssh key. + - `--avatar-attribute value`: The attribute of the user’s LDAP record containing the user’s avatar. - `--bind-dn value`: The DN to bind to the LDAP server with when searching for the user. - `--bind-password value`: The password for the Bind DN, if any. - `--attributes-in-bind`: Fetch attributes in bind DN context. @@ -177,6 +178,7 @@ Admin operations: - `--surname-attribute value`: The attribute of the user’s LDAP record containing the user’s surname. - `--email-attribute value`: The attribute of the user’s LDAP record containing the user’s email address. - `--public-ssh-key-attribute value`: The attribute of the user’s LDAP record containing the user’s public ssh key. + - `--avatar-attribute value`: The attribute of the user’s LDAP record containing the user’s avatar. - `--bind-dn value`: The DN to bind to the LDAP server with when searching for the user. - `--bind-password value`: The password for the Bind DN, if any. - `--attributes-in-bind`: Fetch attributes in bind DN context. @@ -202,6 +204,7 @@ Admin operations: - `--surname-attribute value`: The attribute of the user’s LDAP record containing the user’s surname. - `--email-attribute value`: The attribute of the user’s LDAP record containing the user’s email address. Required. - `--public-ssh-key-attribute value`: The attribute of the user’s LDAP record containing the user’s public ssh key. + - `--avatar-attribute value`: The attribute of the user’s LDAP record containing the user’s avatar. - `--user-dn value`: The user’s DN. Required. - Examples: - `gitea admin auth add-ldap-simple --name ldap --security-protocol unencrypted --host mydomain.org --port 389 --user-dn "cn=%s,ou=Users,dc=mydomain,dc=org" --user-filter "(&(objectClass=posixAccount)(cn=%s))" --email-attribute mail` @@ -223,6 +226,7 @@ Admin operations: - `--surname-attribute value`: The attribute of the user’s LDAP record containing the user’s surname. - `--email-attribute value`: The attribute of the user’s LDAP record containing the user’s email address. - `--public-ssh-key-attribute value`: The attribute of the user’s LDAP record containing the user’s public ssh key. + - `--avatar-attribute value`: The attribute of the user’s LDAP record containing the user’s avatar. - `--user-dn value`: The user’s DN. - Examples: - `gitea admin auth update-ldap-simple --id 1 --name "my ldap auth source"` diff --git a/docs/content/page/index.en-us.md b/docs/content/page/index.en-us.md index a5204e17dff78..48ca13cc66e74 100644 --- a/docs/content/page/index.en-us.md +++ b/docs/content/page/index.en-us.md @@ -69,10 +69,10 @@ Windows, on architectures like amd64, i386, ARM, PowerPC, and others. - Logging - Configuration - Databases - - MySQL - - PostgreSQL + - MySQL (>=5.7) + - PostgreSQL (>=10) - SQLite3 - - MSSQL + - MSSQL (>=2008R2 SP3) - TiDB (experimental, not recommended) - Configuration file - [app.ini](https://github.com/go-gitea/gitea/blob/master/custom/conf/app.example.ini) diff --git a/go.mod b/go.mod index ed557a30ab79b..e5b1d1b6e5f7e 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( gitea.com/go-chi/binding v0.0.0-20210301195521-1fe1c9a555e7 gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e gitea.com/go-chi/captcha v0.0.0-20210110083842-e7696c336a1e - gitea.com/go-chi/session v0.0.0-20210108030337-0cb48c5ba8ee + gitea.com/go-chi/session v0.0.0-20210913064732-2ac132b0fa07 gitea.com/lunny/levelqueue v0.4.1 github.com/Microsoft/go-winio v0.5.0 // indirect github.com/NYTimes/gziphandler v1.1.1 @@ -139,7 +139,7 @@ require ( mvdan.cc/xurls/v2 v2.2.0 strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 xorm.io/builder v0.3.9 - xorm.io/xorm v1.2.2 + xorm.io/xorm v1.2.5 ) replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1 diff --git a/go.sum b/go.sum index a76bfc8a4a62e..2ef9e9f9fd0e9 100644 --- a/go.sum +++ b/go.sum @@ -47,8 +47,8 @@ gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e h1:zgPGaf3kXP0cVm9J0l8 gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0= gitea.com/go-chi/captcha v0.0.0-20210110083842-e7696c336a1e h1:YjaQU6XFicdhPN+MlGolcXO8seYY2+EY5g7vZPB17CQ= gitea.com/go-chi/captcha v0.0.0-20210110083842-e7696c336a1e/go.mod h1:nfA7JaGv3hbGQ1ktdhAsZhdS84qKffI8NMlHr+Opsog= -gitea.com/go-chi/session v0.0.0-20210108030337-0cb48c5ba8ee h1:9U6HuKUBt/cGK6T/64dEuz0r7Yp97WAAEJvXHDlY3ws= -gitea.com/go-chi/session v0.0.0-20210108030337-0cb48c5ba8ee/go.mod h1:Ozg8IchVNb/Udg+ui39iHRYqVHSvf3C99ixdpLR8Vu0= +gitea.com/go-chi/session v0.0.0-20210913064732-2ac132b0fa07 h1:1xF0xbIgDWnBB0iURAUFCoac4KfhGDdI7y7+3/8I8i4= +gitea.com/go-chi/session v0.0.0-20210913064732-2ac132b0fa07/go.mod h1:Ozg8IchVNb/Udg+ui39iHRYqVHSvf3C99ixdpLR8Vu0= gitea.com/lunny/levelqueue v0.4.1 h1:RZ+AFx5gBsZuyqCvofhAkPQ9uaVDPJnsULoJZIYaJNw= gitea.com/lunny/levelqueue v0.4.1/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= @@ -1761,5 +1761,5 @@ xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/builder v0.3.9 h1:Sd65/LdWyO7LR8+Cbd+e7mm3sK/7U9k0jS3999IDHMc= xorm.io/builder v0.3.9/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/xorm v1.0.6/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4= -xorm.io/xorm v1.2.2 h1:FFBOEvJ++8fYBA9cywf2sxDVmFktl1SpJzTAG1ab06Y= -xorm.io/xorm v1.2.2/go.mod h1:fTG8tSjk6O1BYxwuohZUK+S1glnRycsCF05L1qQyEU0= +xorm.io/xorm v1.2.5 h1:tqN7OhN8P9xi52qBb76I8m5maAJMz/SSbgK2RGPCPbo= +xorm.io/xorm v1.2.5/go.mod h1:fTG8tSjk6O1BYxwuohZUK+S1glnRycsCF05L1qQyEU0= diff --git a/integrations/admin_user_test.go b/integrations/admin_user_test.go index 96233436e861a..3acf3490410ad 100644 --- a/integrations/admin_user_test.go +++ b/integrations/admin_user_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) @@ -60,7 +61,7 @@ func makeRequest(t *testing.T, formData models.User, headerCode int) { }) session.MakeRequest(t, req, headerCode) - user := models.AssertExistsAndLoadBean(t, &models.User{ID: formData.ID}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: formData.ID}).(*models.User) assert.Equal(t, formData.Name, user.Name) assert.Equal(t, formData.LoginName, user.LoginName) assert.Equal(t, formData.Email, user.Email) diff --git a/integrations/api_admin_org_test.go b/integrations/api_admin_org_test.go index b10f5607d300f..7b149a38d5484 100644 --- a/integrations/api_admin_org_test.go +++ b/integrations/api_admin_org_test.go @@ -11,6 +11,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -42,7 +43,7 @@ func TestAPIAdminOrgCreate(t *testing.T) { assert.Equal(t, org.Location, apiOrg.Location) assert.Equal(t, org.Visibility, apiOrg.Visibility) - models.AssertExistsAndLoadBean(t, &models.User{ + db.AssertExistsAndLoadBean(t, &models.User{ Name: org.UserName, LowerName: strings.ToLower(org.UserName), FullName: org.FullName, diff --git a/integrations/api_admin_test.go b/integrations/api_admin_test.go index da6a89c9746e1..ae3cc7dcfb017 100644 --- a/integrations/api_admin_test.go +++ b/integrations/api_admin_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" @@ -20,7 +21,7 @@ func TestAPIAdminCreateAndDeleteSSHKey(t *testing.T) { defer prepareTestEnv(t)() // user1 is an admin user session := loginUser(t, "user1") - keyOwner := models.AssertExistsAndLoadBean(t, &models.User{Name: "user2"}).(*models.User) + keyOwner := db.AssertExistsAndLoadBean(t, &models.User{Name: "user2"}).(*models.User) token := getTokenForLoggedInUser(t, session) urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys?token=%s", keyOwner.Name, token) @@ -32,7 +33,7 @@ func TestAPIAdminCreateAndDeleteSSHKey(t *testing.T) { var newPublicKey api.PublicKey DecodeJSON(t, resp, &newPublicKey) - models.AssertExistsAndLoadBean(t, &models.PublicKey{ + db.AssertExistsAndLoadBean(t, &models.PublicKey{ ID: newPublicKey.ID, Name: newPublicKey.Title, Content: newPublicKey.Key, @@ -43,7 +44,7 @@ func TestAPIAdminCreateAndDeleteSSHKey(t *testing.T) { req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d?token=%s", keyOwner.Name, newPublicKey.ID, token) session.MakeRequest(t, req, http.StatusNoContent) - models.AssertNotExistsBean(t, &models.PublicKey{ID: newPublicKey.ID}) + db.AssertNotExistsBean(t, &models.PublicKey{ID: newPublicKey.ID}) } func TestAPIAdminDeleteMissingSSHKey(t *testing.T) { @@ -52,7 +53,7 @@ func TestAPIAdminDeleteMissingSSHKey(t *testing.T) { session := loginUser(t, "user1") token := getTokenForLoggedInUser(t, session) - req := NewRequestf(t, "DELETE", "/api/v1/admin/users/user1/keys/%d?token=%s", models.NonexistentID, token) + req := NewRequestf(t, "DELETE", "/api/v1/admin/users/user1/keys/%d?token=%s", db.NonexistentID, token) session.MakeRequest(t, req, http.StatusNotFound) } @@ -127,7 +128,7 @@ func TestAPIListUsers(t *testing.T) { } } assert.True(t, found) - numberOfUsers := models.GetCount(t, &models.User{}, "type = 0") + numberOfUsers := db.GetCount(t, &models.User{}, "type = 0") assert.Equal(t, numberOfUsers, len(users)) } @@ -193,7 +194,7 @@ func TestAPIEditUser(t *testing.T) { json.Unmarshal(resp.Body.Bytes(), &errMap) assert.EqualValues(t, "email is not allowed to be empty string", errMap["message"].(string)) - user2 := models.AssertExistsAndLoadBean(t, &models.User{LoginName: "user2"}).(*models.User) + user2 := db.AssertExistsAndLoadBean(t, &models.User{LoginName: "user2"}).(*models.User) assert.False(t, user2.IsRestricted) bTrue := true req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ @@ -204,6 +205,6 @@ func TestAPIEditUser(t *testing.T) { Restricted: &bTrue, }) session.MakeRequest(t, req, http.StatusOK) - user2 = models.AssertExistsAndLoadBean(t, &models.User{LoginName: "user2"}).(*models.User) + user2 = db.AssertExistsAndLoadBean(t, &models.User{LoginName: "user2"}).(*models.User) assert.True(t, user2.IsRestricted) } diff --git a/integrations/api_comment_test.go b/integrations/api_comment_test.go index f64f4a1c9c0f2..a5513af6e300c 100644 --- a/integrations/api_comment_test.go +++ b/integrations/api_comment_test.go @@ -11,6 +11,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" @@ -20,11 +21,11 @@ import ( func TestAPIListRepoComments(t *testing.T) { defer prepareTestEnv(t)() - comment := models.AssertExistsAndLoadBean(t, &models.Comment{}, - models.Cond("type = ?", models.CommentTypeComment)).(*models.Comment) - issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: comment.IssueID}).(*models.Issue) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: issue.RepoID}).(*models.Repository) - repoOwner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + comment := db.AssertExistsAndLoadBean(t, &models.Comment{}, + db.Cond("type = ?", models.CommentTypeComment)).(*models.Comment) + issue := db.AssertExistsAndLoadBean(t, &models.Issue{ID: comment.IssueID}).(*models.Issue) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: issue.RepoID}).(*models.Repository) + repoOwner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, repoOwner.Name) link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments", repoOwner.Name, repo.Name)) @@ -36,9 +37,9 @@ func TestAPIListRepoComments(t *testing.T) { assert.Len(t, apiComments, 2) for _, apiComment := range apiComments { c := &models.Comment{ID: apiComment.ID} - models.AssertExistsAndLoadBean(t, c, - models.Cond("type = ?", models.CommentTypeComment)) - models.AssertExistsAndLoadBean(t, &models.Issue{ID: c.IssueID, RepoID: repo.ID}) + db.AssertExistsAndLoadBean(t, c, + db.Cond("type = ?", models.CommentTypeComment)) + db.AssertExistsAndLoadBean(t, &models.Issue{ID: c.IssueID, RepoID: repo.ID}) } //test before and since filters @@ -66,11 +67,11 @@ func TestAPIListRepoComments(t *testing.T) { func TestAPIListIssueComments(t *testing.T) { defer prepareTestEnv(t)() - comment := models.AssertExistsAndLoadBean(t, &models.Comment{}, - models.Cond("type = ?", models.CommentTypeComment)).(*models.Comment) - issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: comment.IssueID}).(*models.Issue) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: issue.RepoID}).(*models.Repository) - repoOwner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + comment := db.AssertExistsAndLoadBean(t, &models.Comment{}, + db.Cond("type = ?", models.CommentTypeComment)).(*models.Comment) + issue := db.AssertExistsAndLoadBean(t, &models.Issue{ID: comment.IssueID}).(*models.Issue) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: issue.RepoID}).(*models.Repository) + repoOwner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, repoOwner.Name) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/comments", @@ -79,8 +80,8 @@ func TestAPIListIssueComments(t *testing.T) { var comments []*api.Comment DecodeJSON(t, resp, &comments) - expectedCount := models.GetCount(t, &models.Comment{IssueID: issue.ID}, - models.Cond("type = ?", models.CommentTypeComment)) + expectedCount := db.GetCount(t, &models.Comment{IssueID: issue.ID}, + db.Cond("type = ?", models.CommentTypeComment)) assert.EqualValues(t, expectedCount, len(comments)) } @@ -88,9 +89,9 @@ func TestAPICreateComment(t *testing.T) { defer prepareTestEnv(t)() const commentBody = "Comment body" - issue := models.AssertExistsAndLoadBean(t, &models.Issue{}).(*models.Issue) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: issue.RepoID}).(*models.Repository) - repoOwner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + issue := db.AssertExistsAndLoadBean(t, &models.Issue{}).(*models.Issue) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: issue.RepoID}).(*models.Repository) + repoOwner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, repoOwner.Name) token := getTokenForLoggedInUser(t, session) @@ -104,16 +105,16 @@ func TestAPICreateComment(t *testing.T) { var updatedComment api.Comment DecodeJSON(t, resp, &updatedComment) assert.EqualValues(t, commentBody, updatedComment.Body) - models.AssertExistsAndLoadBean(t, &models.Comment{ID: updatedComment.ID, IssueID: issue.ID, Content: commentBody}) + db.AssertExistsAndLoadBean(t, &models.Comment{ID: updatedComment.ID, IssueID: issue.ID, Content: commentBody}) } func TestAPIGetComment(t *testing.T) { defer prepareTestEnv(t)() - comment := models.AssertExistsAndLoadBean(t, &models.Comment{ID: 2}).(*models.Comment) + comment := db.AssertExistsAndLoadBean(t, &models.Comment{ID: 2}).(*models.Comment) assert.NoError(t, comment.LoadIssue()) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: comment.Issue.RepoID}).(*models.Repository) - repoOwner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: comment.Issue.RepoID}).(*models.Repository) + repoOwner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, repoOwner.Name) token := getTokenForLoggedInUser(t, session) @@ -138,11 +139,11 @@ func TestAPIEditComment(t *testing.T) { defer prepareTestEnv(t)() const newCommentBody = "This is the new comment body" - comment := models.AssertExistsAndLoadBean(t, &models.Comment{}, - models.Cond("type = ?", models.CommentTypeComment)).(*models.Comment) - issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: comment.IssueID}).(*models.Issue) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: issue.RepoID}).(*models.Repository) - repoOwner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + comment := db.AssertExistsAndLoadBean(t, &models.Comment{}, + db.Cond("type = ?", models.CommentTypeComment)).(*models.Comment) + issue := db.AssertExistsAndLoadBean(t, &models.Issue{ID: comment.IssueID}).(*models.Issue) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: issue.RepoID}).(*models.Repository) + repoOwner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, repoOwner.Name) token := getTokenForLoggedInUser(t, session) @@ -157,17 +158,17 @@ func TestAPIEditComment(t *testing.T) { DecodeJSON(t, resp, &updatedComment) assert.EqualValues(t, comment.ID, updatedComment.ID) assert.EqualValues(t, newCommentBody, updatedComment.Body) - models.AssertExistsAndLoadBean(t, &models.Comment{ID: comment.ID, IssueID: issue.ID, Content: newCommentBody}) + db.AssertExistsAndLoadBean(t, &models.Comment{ID: comment.ID, IssueID: issue.ID, Content: newCommentBody}) } func TestAPIDeleteComment(t *testing.T) { defer prepareTestEnv(t)() - comment := models.AssertExistsAndLoadBean(t, &models.Comment{}, - models.Cond("type = ?", models.CommentTypeComment)).(*models.Comment) - issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: comment.IssueID}).(*models.Issue) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: issue.RepoID}).(*models.Repository) - repoOwner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + comment := db.AssertExistsAndLoadBean(t, &models.Comment{}, + db.Cond("type = ?", models.CommentTypeComment)).(*models.Comment) + issue := db.AssertExistsAndLoadBean(t, &models.Issue{ID: comment.IssueID}).(*models.Issue) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: issue.RepoID}).(*models.Repository) + repoOwner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, repoOwner.Name) token := getTokenForLoggedInUser(t, session) @@ -175,5 +176,5 @@ func TestAPIDeleteComment(t *testing.T) { repoOwner.Name, repo.Name, comment.ID, token) session.MakeRequest(t, req, http.StatusNoContent) - models.AssertNotExistsBean(t, &models.Comment{ID: comment.ID}) + db.AssertNotExistsBean(t, &models.Comment{ID: comment.ID}) } diff --git a/integrations/api_helper_for_declarative_test.go b/integrations/api_helper_for_declarative_test.go index 5d44cde5bfd79..8745ada96dc03 100644 --- a/integrations/api_helper_for_declarative_test.go +++ b/integrations/api_helper_for_declarative_test.go @@ -7,9 +7,9 @@ package integrations import ( "context" "fmt" - "io/ioutil" "net/http" "net/url" + "os" "testing" "time" @@ -163,7 +163,7 @@ func doAPICreateUserKey(ctx APITestContext, keyname, keyFile string, callback .. return func(t *testing.T) { urlStr := fmt.Sprintf("/api/v1/user/keys?token=%s", ctx.Token) - dataPubKey, err := ioutil.ReadFile(keyFile + ".pub") + dataPubKey, err := os.ReadFile(keyFile + ".pub") assert.NoError(t, err) req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateKeyOption{ Title: keyname, @@ -199,7 +199,7 @@ func doAPICreateDeployKey(ctx APITestContext, keyname, keyFile string, readOnly return func(t *testing.T) { urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/keys?token=%s", ctx.Username, ctx.Reponame, ctx.Token) - dataPubKey, err := ioutil.ReadFile(keyFile + ".pub") + dataPubKey, err := os.ReadFile(keyFile + ".pub") assert.NoError(t, err) req := NewRequestWithJSON(t, "POST", urlStr, api.CreateKeyOption{ Title: keyname, diff --git a/integrations/api_issue_label_test.go b/integrations/api_issue_label_test.go index 64c3515dd2bb3..4508069e17646 100644 --- a/integrations/api_issue_label_test.go +++ b/integrations/api_issue_label_test.go @@ -11,16 +11,17 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" ) func TestAPIModifyLabels(t *testing.T) { - assert.NoError(t, models.LoadFixtures()) + assert.NoError(t, db.LoadFixtures()) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/labels?token=%s", owner.Name, repo.Name, token) @@ -34,7 +35,7 @@ func TestAPIModifyLabels(t *testing.T) { resp := session.MakeRequest(t, req, http.StatusCreated) apiLabel := new(api.Label) DecodeJSON(t, resp, &apiLabel) - dbLabel := models.AssertExistsAndLoadBean(t, &models.Label{ID: apiLabel.ID, RepoID: repo.ID}).(*models.Label) + dbLabel := db.AssertExistsAndLoadBean(t, &models.Label{ID: apiLabel.ID, RepoID: repo.ID}).(*models.Label) assert.EqualValues(t, dbLabel.Name, apiLabel.Name) assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) @@ -87,12 +88,12 @@ func TestAPIModifyLabels(t *testing.T) { } func TestAPIAddIssueLabels(t *testing.T) { - assert.NoError(t, models.LoadFixtures()) + assert.NoError(t, db.LoadFixtures()) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - issue := models.AssertExistsAndLoadBean(t, &models.Issue{RepoID: repo.ID}).(*models.Issue) - _ = models.AssertExistsAndLoadBean(t, &models.Label{RepoID: repo.ID, ID: 2}).(*models.Label) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + issue := db.AssertExistsAndLoadBean(t, &models.Issue{RepoID: repo.ID}).(*models.Issue) + _ = db.AssertExistsAndLoadBean(t, &models.Label{RepoID: repo.ID, ID: 2}).(*models.Label) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session) @@ -104,18 +105,18 @@ func TestAPIAddIssueLabels(t *testing.T) { resp := session.MakeRequest(t, req, http.StatusOK) var apiLabels []*api.Label DecodeJSON(t, resp, &apiLabels) - assert.Len(t, apiLabels, models.GetCount(t, &models.IssueLabel{IssueID: issue.ID})) + assert.Len(t, apiLabels, db.GetCount(t, &models.IssueLabel{IssueID: issue.ID})) - models.AssertExistsAndLoadBean(t, &models.IssueLabel{IssueID: issue.ID, LabelID: 2}) + db.AssertExistsAndLoadBean(t, &models.IssueLabel{IssueID: issue.ID, LabelID: 2}) } func TestAPIReplaceIssueLabels(t *testing.T) { - assert.NoError(t, models.LoadFixtures()) + assert.NoError(t, db.LoadFixtures()) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - issue := models.AssertExistsAndLoadBean(t, &models.Issue{RepoID: repo.ID}).(*models.Issue) - label := models.AssertExistsAndLoadBean(t, &models.Label{RepoID: repo.ID}).(*models.Label) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + issue := db.AssertExistsAndLoadBean(t, &models.Issue{RepoID: repo.ID}).(*models.Issue) + label := db.AssertExistsAndLoadBean(t, &models.Label{RepoID: repo.ID}).(*models.Label) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session) @@ -131,15 +132,15 @@ func TestAPIReplaceIssueLabels(t *testing.T) { assert.EqualValues(t, label.ID, apiLabels[0].ID) } - models.AssertCount(t, &models.IssueLabel{IssueID: issue.ID}, 1) - models.AssertExistsAndLoadBean(t, &models.IssueLabel{IssueID: issue.ID, LabelID: label.ID}) + db.AssertCount(t, &models.IssueLabel{IssueID: issue.ID}, 1) + db.AssertExistsAndLoadBean(t, &models.IssueLabel{IssueID: issue.ID, LabelID: label.ID}) } func TestAPIModifyOrgLabels(t *testing.T) { - assert.NoError(t, models.LoadFixtures()) + assert.NoError(t, db.LoadFixtures()) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) user := "user1" session := loginUser(t, user) token := getTokenForLoggedInUser(t, session) @@ -154,7 +155,7 @@ func TestAPIModifyOrgLabels(t *testing.T) { resp := session.MakeRequest(t, req, http.StatusCreated) apiLabel := new(api.Label) DecodeJSON(t, resp, &apiLabel) - dbLabel := models.AssertExistsAndLoadBean(t, &models.Label{ID: apiLabel.ID, OrgID: owner.ID}).(*models.Label) + dbLabel := db.AssertExistsAndLoadBean(t, &models.Label{ID: apiLabel.ID, OrgID: owner.ID}).(*models.Label) assert.EqualValues(t, dbLabel.Name, apiLabel.Name) assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) diff --git a/integrations/api_issue_milestone_test.go b/integrations/api_issue_milestone_test.go index b9942d9a3c7b2..ba4ada12136f5 100644 --- a/integrations/api_issue_milestone_test.go +++ b/integrations/api_issue_milestone_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -18,9 +19,9 @@ import ( func TestAPIIssuesMilestone(t *testing.T) { defer prepareTestEnv(t)() - milestone := models.AssertExistsAndLoadBean(t, &models.Milestone{ID: 1}).(*models.Milestone) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: milestone.RepoID}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + milestone := db.AssertExistsAndLoadBean(t, &models.Milestone{ID: 1}).(*models.Milestone) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: milestone.RepoID}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) assert.Equal(t, int64(1), int64(milestone.NumIssues)) assert.Equal(t, structs.StateOpen, milestone.State()) diff --git a/integrations/api_issue_reaction_test.go b/integrations/api_issue_reaction_test.go index ad4adc1eae750..d064f488545be 100644 --- a/integrations/api_issue_reaction_test.go +++ b/integrations/api_issue_reaction_test.go @@ -11,6 +11,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" @@ -20,14 +21,14 @@ import ( func TestAPIIssuesReactions(t *testing.T) { defer prepareTestEnv(t)() - issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 1}).(*models.Issue) + issue := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 1}).(*models.Issue) _ = issue.LoadRepo() - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: issue.Repo.OwnerID}).(*models.User) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: issue.Repo.OwnerID}).(*models.User) session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session) - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions?token=%s", owner.Name, issue.Repo.Name, issue.Index, token) @@ -77,17 +78,17 @@ func TestAPIIssuesReactions(t *testing.T) { func TestAPICommentReactions(t *testing.T) { defer prepareTestEnv(t)() - comment := models.AssertExistsAndLoadBean(t, &models.Comment{ID: 2}).(*models.Comment) + comment := db.AssertExistsAndLoadBean(t, &models.Comment{ID: 2}).(*models.Comment) _ = comment.LoadIssue() issue := comment.Issue _ = issue.LoadRepo() - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: issue.Repo.OwnerID}).(*models.User) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: issue.Repo.OwnerID}).(*models.User) session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session) - user1 := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user1 := db.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/reactions?token=%s", owner.Name, issue.Repo.Name, comment.ID, token) diff --git a/integrations/api_issue_stopwatch_test.go b/integrations/api_issue_stopwatch_test.go index c0b8fd9c69746..8ccd79f691e0e 100644 --- a/integrations/api_issue_stopwatch_test.go +++ b/integrations/api_issue_stopwatch_test.go @@ -9,6 +9,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -17,8 +18,8 @@ import ( func TestAPIListStopWatches(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session) @@ -26,8 +27,8 @@ func TestAPIListStopWatches(t *testing.T) { resp := session.MakeRequest(t, req, http.StatusOK) var apiWatches []*api.StopWatch DecodeJSON(t, resp, &apiWatches) - stopwatch := models.AssertExistsAndLoadBean(t, &models.Stopwatch{UserID: owner.ID}).(*models.Stopwatch) - issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: stopwatch.IssueID}).(*models.Issue) + stopwatch := db.AssertExistsAndLoadBean(t, &models.Stopwatch{UserID: owner.ID}).(*models.Stopwatch) + issue := db.AssertExistsAndLoadBean(t, &models.Issue{ID: stopwatch.IssueID}).(*models.Issue) if assert.Len(t, apiWatches, 1) { assert.EqualValues(t, stopwatch.CreatedUnix.AsTime().Unix(), apiWatches[0].Created.Unix()) assert.EqualValues(t, issue.Index, apiWatches[0].IssueIndex) @@ -41,10 +42,10 @@ func TestAPIListStopWatches(t *testing.T) { func TestAPIStopStopWatches(t *testing.T) { defer prepareTestEnv(t)() - issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue) + issue := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue) _ = issue.LoadRepo() - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: issue.Repo.OwnerID}).(*models.User) - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: issue.Repo.OwnerID}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) @@ -57,10 +58,10 @@ func TestAPIStopStopWatches(t *testing.T) { func TestAPICancelStopWatches(t *testing.T) { defer prepareTestEnv(t)() - issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 1}).(*models.Issue) + issue := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 1}).(*models.Issue) _ = issue.LoadRepo() - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: issue.Repo.OwnerID}).(*models.User) - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: issue.Repo.OwnerID}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) @@ -73,10 +74,10 @@ func TestAPICancelStopWatches(t *testing.T) { func TestAPIStartStopWatches(t *testing.T) { defer prepareTestEnv(t)() - issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 3}).(*models.Issue) + issue := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 3}).(*models.Issue) _ = issue.LoadRepo() - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: issue.Repo.OwnerID}).(*models.User) - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: issue.Repo.OwnerID}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) diff --git a/integrations/api_issue_subscription_test.go b/integrations/api_issue_subscription_test.go index d0b192101556b..30c307661140e 100644 --- a/integrations/api_issue_subscription_test.go +++ b/integrations/api_issue_subscription_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -18,20 +19,20 @@ import ( func TestAPIIssueSubscriptions(t *testing.T) { defer prepareTestEnv(t)() - issue1 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 1}).(*models.Issue) - issue2 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue) - issue3 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 3}).(*models.Issue) - issue4 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 4}).(*models.Issue) - issue5 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 8}).(*models.Issue) + issue1 := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 1}).(*models.Issue) + issue2 := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue) + issue3 := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 3}).(*models.Issue) + issue4 := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 4}).(*models.Issue) + issue5 := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 8}).(*models.Issue) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: issue1.PosterID}).(*models.User) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: issue1.PosterID}).(*models.User) session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session) testSubscription := func(issue *models.Issue, isWatching bool) { - issueRepo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: issue.RepoID}).(*models.Repository) + issueRepo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: issue.RepoID}).(*models.Repository) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/subscriptions/check?token=%s", issueRepo.OwnerName, issueRepo.Name, issue.Index, token) req := NewRequest(t, "GET", urlStr) @@ -52,7 +53,7 @@ func TestAPIIssueSubscriptions(t *testing.T) { testSubscription(issue4, false) testSubscription(issue5, false) - issue1Repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: issue1.RepoID}).(*models.Repository) + issue1Repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: issue1.RepoID}).(*models.Repository) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/subscriptions/%s?token=%s", issue1Repo.OwnerName, issue1Repo.Name, issue1.Index, owner.Name, token) req := NewRequest(t, "DELETE", urlStr) session.MakeRequest(t, req, http.StatusCreated) @@ -62,7 +63,7 @@ func TestAPIIssueSubscriptions(t *testing.T) { session.MakeRequest(t, req, http.StatusOK) testSubscription(issue1, false) - issue5Repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: issue5.RepoID}).(*models.Repository) + issue5Repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: issue5.RepoID}).(*models.Repository) urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/subscriptions/%s?token=%s", issue5Repo.OwnerName, issue5Repo.Name, issue5.Index, owner.Name, token) req = NewRequest(t, "PUT", urlStr) session.MakeRequest(t, req, http.StatusCreated) diff --git a/integrations/api_issue_test.go b/integrations/api_issue_test.go index 245ab76c7b6cc..df258a9831045 100644 --- a/integrations/api_issue_test.go +++ b/integrations/api_issue_test.go @@ -12,6 +12,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -20,8 +21,8 @@ import ( func TestAPIListIssues(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session) @@ -31,9 +32,9 @@ func TestAPIListIssues(t *testing.T) { resp := session.MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK) var apiIssues []*api.Issue DecodeJSON(t, resp, &apiIssues) - assert.Len(t, apiIssues, models.GetCount(t, &models.Issue{RepoID: repo.ID})) + assert.Len(t, apiIssues, db.GetCount(t, &models.Issue{RepoID: repo.ID})) for _, apiIssue := range apiIssues { - models.AssertExistsAndLoadBean(t, &models.Issue{ID: apiIssue.ID, RepoID: repo.ID}) + db.AssertExistsAndLoadBean(t, &models.Issue{ID: apiIssue.ID, RepoID: repo.ID}) } // test milestone filter @@ -71,8 +72,8 @@ func TestAPICreateIssue(t *testing.T) { defer prepareTestEnv(t)() const body, title = "apiTestBody", "apiTestTitle" - repoBefore := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repoBefore.OwnerID}).(*models.User) + repoBefore := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repoBefore.OwnerID}).(*models.User) session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session) @@ -88,14 +89,14 @@ func TestAPICreateIssue(t *testing.T) { assert.Equal(t, body, apiIssue.Body) assert.Equal(t, title, apiIssue.Title) - models.AssertExistsAndLoadBean(t, &models.Issue{ + db.AssertExistsAndLoadBean(t, &models.Issue{ RepoID: repoBefore.ID, AssigneeID: owner.ID, Content: body, Title: title, }) - repoAfter := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + repoAfter := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) assert.Equal(t, repoBefore.NumIssues+1, repoAfter.NumIssues) assert.Equal(t, repoBefore.NumClosedIssues, repoAfter.NumClosedIssues) } @@ -103,9 +104,9 @@ func TestAPICreateIssue(t *testing.T) { func TestAPIEditIssue(t *testing.T) { defer prepareTestEnv(t)() - issueBefore := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 10}).(*models.Issue) - repoBefore := models.AssertExistsAndLoadBean(t, &models.Repository{ID: issueBefore.RepoID}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repoBefore.OwnerID}).(*models.User) + issueBefore := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 10}).(*models.Issue) + repoBefore := db.AssertExistsAndLoadBean(t, &models.Repository{ID: issueBefore.RepoID}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repoBefore.OwnerID}).(*models.User) assert.NoError(t, issueBefore.LoadAttributes()) assert.Equal(t, int64(1019307200), int64(issueBefore.DeadlineUnix)) assert.Equal(t, api.StateOpen, issueBefore.State()) @@ -134,8 +135,8 @@ func TestAPIEditIssue(t *testing.T) { var apiIssue api.Issue DecodeJSON(t, resp, &apiIssue) - issueAfter := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 10}).(*models.Issue) - repoAfter := models.AssertExistsAndLoadBean(t, &models.Repository{ID: issueBefore.RepoID}).(*models.Repository) + issueAfter := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 10}).(*models.Issue) + repoAfter := db.AssertExistsAndLoadBean(t, &models.Repository{ID: issueBefore.RepoID}).(*models.Repository) // check deleted user assert.Equal(t, int64(500), issueAfter.PosterID) diff --git a/integrations/api_issue_tracked_time_test.go b/integrations/api_issue_tracked_time_test.go index 9731c5a93b6c7..fcf18e45df956 100644 --- a/integrations/api_issue_tracked_time_test.go +++ b/integrations/api_issue_tracked_time_test.go @@ -11,6 +11,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -19,8 +20,8 @@ import ( func TestAPIGetTrackedTimes(t *testing.T) { defer prepareTestEnv(t)() - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - issue2 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue) + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + issue2 := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue) assert.NoError(t, issue2.LoadRepo()) session := loginUser(t, user2.Name) @@ -61,10 +62,10 @@ func TestAPIGetTrackedTimes(t *testing.T) { func TestAPIDeleteTrackedTime(t *testing.T) { defer prepareTestEnv(t)() - time6 := models.AssertExistsAndLoadBean(t, &models.TrackedTime{ID: 6}).(*models.TrackedTime) - issue2 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue) + time6 := db.AssertExistsAndLoadBean(t, &models.TrackedTime{ID: 6}).(*models.TrackedTime) + issue2 := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue) assert.NoError(t, issue2.LoadRepo()) - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) session := loginUser(t, user2.Name) token := getTokenForLoggedInUser(t, session) @@ -73,7 +74,7 @@ func TestAPIDeleteTrackedTime(t *testing.T) { req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/%d/times/%d?token=%s", user2.Name, issue2.Repo.Name, issue2.Index, time6.ID, token) session.MakeRequest(t, req, http.StatusForbidden) - time3 := models.AssertExistsAndLoadBean(t, &models.TrackedTime{ID: 3}).(*models.TrackedTime) + time3 := db.AssertExistsAndLoadBean(t, &models.TrackedTime{ID: 3}).(*models.TrackedTime) req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/%d/times/%d?token=%s", user2.Name, issue2.Repo.Name, issue2.Index, time3.ID, token) session.MakeRequest(t, req, http.StatusNoContent) //Delete non existing time @@ -96,10 +97,10 @@ func TestAPIDeleteTrackedTime(t *testing.T) { func TestAPIAddTrackedTimes(t *testing.T) { defer prepareTestEnv(t)() - issue2 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue) + issue2 := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue) assert.NoError(t, issue2.LoadRepo()) - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - admin := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + admin := db.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) session := loginUser(t, admin.Name) token := getTokenForLoggedInUser(t, session) diff --git a/integrations/api_keys_test.go b/integrations/api_keys_test.go index 50e1e7a35bcd4..161b05e8ce96f 100644 --- a/integrations/api_keys_test.go +++ b/integrations/api_keys_test.go @@ -11,6 +11,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -45,8 +46,8 @@ func TestDeleteDeployKeyNoLogin(t *testing.T) { func TestCreateReadOnlyDeployKey(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{Name: "repo1"}).(*models.Repository) - repoOwner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{Name: "repo1"}).(*models.Repository) + repoOwner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, repoOwner.Name) token := getTokenForLoggedInUser(t, session) @@ -61,7 +62,7 @@ func TestCreateReadOnlyDeployKey(t *testing.T) { var newDeployKey api.DeployKey DecodeJSON(t, resp, &newDeployKey) - models.AssertExistsAndLoadBean(t, &models.DeployKey{ + db.AssertExistsAndLoadBean(t, &models.DeployKey{ ID: newDeployKey.ID, Name: rawKeyBody.Title, Content: rawKeyBody.Key, @@ -71,8 +72,8 @@ func TestCreateReadOnlyDeployKey(t *testing.T) { func TestCreateReadWriteDeployKey(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{Name: "repo1"}).(*models.Repository) - repoOwner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{Name: "repo1"}).(*models.Repository) + repoOwner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, repoOwner.Name) token := getTokenForLoggedInUser(t, session) @@ -86,7 +87,7 @@ func TestCreateReadWriteDeployKey(t *testing.T) { var newDeployKey api.DeployKey DecodeJSON(t, resp, &newDeployKey) - models.AssertExistsAndLoadBean(t, &models.DeployKey{ + db.AssertExistsAndLoadBean(t, &models.DeployKey{ ID: newDeployKey.ID, Name: rawKeyBody.Title, Content: rawKeyBody.Key, @@ -96,7 +97,7 @@ func TestCreateReadWriteDeployKey(t *testing.T) { func TestCreateUserKey(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{Name: "user1"}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{Name: "user1"}).(*models.User) session := loginUser(t, "user1") token := url.QueryEscape(getTokenForLoggedInUser(t, session)) @@ -112,7 +113,7 @@ func TestCreateUserKey(t *testing.T) { var newPublicKey api.PublicKey DecodeJSON(t, resp, &newPublicKey) - models.AssertExistsAndLoadBean(t, &models.PublicKey{ + db.AssertExistsAndLoadBean(t, &models.PublicKey{ ID: newPublicKey.ID, OwnerID: user.ID, Name: rawKeyBody.Title, diff --git a/integrations/api_notification_test.go b/integrations/api_notification_test.go index 96af14fb82a9f..3049f1b99546b 100644 --- a/integrations/api_notification_test.go +++ b/integrations/api_notification_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -18,9 +19,9 @@ import ( func TestAPINotification(t *testing.T) { defer prepareTestEnv(t)() - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - thread5 := models.AssertExistsAndLoadBean(t, &models.Notification{ID: 5}).(*models.Notification) + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo1 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + thread5 := db.AssertExistsAndLoadBean(t, &models.Notification{ID: 5}).(*models.Notification) assert.NoError(t, thread5.LoadAttributes()) session := loginUser(t, user2.Name) token := getTokenForLoggedInUser(t, session) @@ -111,7 +112,7 @@ func TestAPINotification(t *testing.T) { resp = session.MakeRequest(t, req, http.StatusResetContent) assert.Equal(t, models.NotificationStatusUnread, thread5.Status) - thread5 = models.AssertExistsAndLoadBean(t, &models.Notification{ID: 5}).(*models.Notification) + thread5 = db.AssertExistsAndLoadBean(t, &models.Notification{ID: 5}).(*models.Notification) assert.Equal(t, models.NotificationStatusRead, thread5.Status) // -- check notifications -- diff --git a/integrations/api_oauth2_apps_test.go b/integrations/api_oauth2_apps_test.go index 5c90dbb3bca12..6f0a46249f515 100644 --- a/integrations/api_oauth2_apps_test.go +++ b/integrations/api_oauth2_apps_test.go @@ -10,6 +10,8 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -25,7 +27,7 @@ func TestOAuth2Application(t *testing.T) { } func testAPICreateOAuth2Application(t *testing.T) { - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) appBody := api.CreateOAuth2ApplicationOptions{ Name: "test-app-1", RedirectURIs: []string{ @@ -45,21 +47,21 @@ func testAPICreateOAuth2Application(t *testing.T) { assert.Len(t, createdApp.ClientID, 36) assert.NotEmpty(t, createdApp.Created) assert.EqualValues(t, appBody.RedirectURIs[0], createdApp.RedirectURIs[0]) - models.AssertExistsAndLoadBean(t, &models.OAuth2Application{UID: user.ID, Name: createdApp.Name}) + db.AssertExistsAndLoadBean(t, &login.OAuth2Application{UID: user.ID, Name: createdApp.Name}) } func testAPIListOAuth2Applications(t *testing.T) { - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) - existApp := models.AssertExistsAndLoadBean(t, &models.OAuth2Application{ + existApp := db.AssertExistsAndLoadBean(t, &login.OAuth2Application{ UID: user.ID, Name: "test-app-1", RedirectURIs: []string{ "http://www.google.com", }, - }).(*models.OAuth2Application) + }).(*login.OAuth2Application) urlStr := fmt.Sprintf("/api/v1/user/applications/oauth2?token=%s", token) req := NewRequest(t, "GET", urlStr) @@ -74,24 +76,24 @@ func testAPIListOAuth2Applications(t *testing.T) { assert.Len(t, expectedApp.ClientID, 36) assert.Empty(t, expectedApp.ClientSecret) assert.EqualValues(t, existApp.RedirectURIs[0], expectedApp.RedirectURIs[0]) - models.AssertExistsAndLoadBean(t, &models.OAuth2Application{ID: expectedApp.ID, Name: expectedApp.Name}) + db.AssertExistsAndLoadBean(t, &login.OAuth2Application{ID: expectedApp.ID, Name: expectedApp.Name}) } func testAPIDeleteOAuth2Application(t *testing.T) { - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) - oldApp := models.AssertExistsAndLoadBean(t, &models.OAuth2Application{ + oldApp := db.AssertExistsAndLoadBean(t, &login.OAuth2Application{ UID: user.ID, Name: "test-app-1", - }).(*models.OAuth2Application) + }).(*login.OAuth2Application) urlStr := fmt.Sprintf("/api/v1/user/applications/oauth2/%d?token=%s", oldApp.ID, token) req := NewRequest(t, "DELETE", urlStr) session.MakeRequest(t, req, http.StatusNoContent) - models.AssertNotExistsBean(t, &models.OAuth2Application{UID: oldApp.UID, Name: oldApp.Name}) + db.AssertNotExistsBean(t, &login.OAuth2Application{UID: oldApp.UID, Name: oldApp.Name}) // Delete again will return not found req = NewRequest(t, "DELETE", urlStr) @@ -99,17 +101,17 @@ func testAPIDeleteOAuth2Application(t *testing.T) { } func testAPIGetOAuth2Application(t *testing.T) { - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) - existApp := models.AssertExistsAndLoadBean(t, &models.OAuth2Application{ + existApp := db.AssertExistsAndLoadBean(t, &login.OAuth2Application{ UID: user.ID, Name: "test-app-1", RedirectURIs: []string{ "http://www.google.com", }, - }).(*models.OAuth2Application) + }).(*login.OAuth2Application) urlStr := fmt.Sprintf("/api/v1/user/applications/oauth2/%d?token=%s", existApp.ID, token) req := NewRequest(t, "GET", urlStr) @@ -125,19 +127,19 @@ func testAPIGetOAuth2Application(t *testing.T) { assert.Empty(t, expectedApp.ClientSecret) assert.Len(t, expectedApp.RedirectURIs, 1) assert.EqualValues(t, existApp.RedirectURIs[0], expectedApp.RedirectURIs[0]) - models.AssertExistsAndLoadBean(t, &models.OAuth2Application{ID: expectedApp.ID, Name: expectedApp.Name}) + db.AssertExistsAndLoadBean(t, &login.OAuth2Application{ID: expectedApp.ID, Name: expectedApp.Name}) } func testAPIUpdateOAuth2Application(t *testing.T) { - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - existApp := models.AssertExistsAndLoadBean(t, &models.OAuth2Application{ + existApp := db.AssertExistsAndLoadBean(t, &login.OAuth2Application{ UID: user.ID, Name: "test-app-1", RedirectURIs: []string{ "http://www.google.com", }, - }).(*models.OAuth2Application) + }).(*login.OAuth2Application) appBody := api.CreateOAuth2ApplicationOptions{ Name: "test-app-1", @@ -159,5 +161,5 @@ func testAPIUpdateOAuth2Application(t *testing.T) { assert.Len(t, expectedApp.RedirectURIs, 2) assert.EqualValues(t, expectedApp.RedirectURIs[0], appBody.RedirectURIs[0]) assert.EqualValues(t, expectedApp.RedirectURIs[1], appBody.RedirectURIs[1]) - models.AssertExistsAndLoadBean(t, &models.OAuth2Application{ID: expectedApp.ID, Name: expectedApp.Name}) + db.AssertExistsAndLoadBean(t, &login.OAuth2Application{ID: expectedApp.ID, Name: expectedApp.Name}) } diff --git a/integrations/api_org_test.go b/integrations/api_org_test.go index bc4428b99e150..6268fa4a10ef7 100644 --- a/integrations/api_org_test.go +++ b/integrations/api_org_test.go @@ -11,6 +11,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -43,7 +44,7 @@ func TestAPIOrgCreate(t *testing.T) { assert.Equal(t, org.Location, apiOrg.Location) assert.Equal(t, org.Visibility, apiOrg.Visibility) - models.AssertExistsAndLoadBean(t, &models.User{ + db.AssertExistsAndLoadBean(t, &models.User{ Name: org.UserName, LowerName: strings.ToLower(org.UserName), FullName: org.FullName, diff --git a/integrations/api_pull_commits_test.go b/integrations/api_pull_commits_test.go index 30682d9c147ec..615589fd12f1d 100644 --- a/integrations/api_pull_commits_test.go +++ b/integrations/api_pull_commits_test.go @@ -9,15 +9,16 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" ) func TestAPIPullCommits(t *testing.T) { defer prepareTestEnv(t)() - pullIssue := models.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 2}).(*models.PullRequest) + pullIssue := db.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 2}).(*models.PullRequest) assert.NoError(t, pullIssue.LoadIssue()) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue.HeadRepoID}).(*models.Repository) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue.HeadRepoID}).(*models.Repository) session := loginUser(t, "user2") req := NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/commits", repo.OwnerName, repo.Name, pullIssue.Index) diff --git a/integrations/api_pull_review_test.go b/integrations/api_pull_review_test.go index b0e380376bcb7..123ba0a2b5c45 100644 --- a/integrations/api_pull_review_test.go +++ b/integrations/api_pull_review_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" @@ -18,9 +19,9 @@ import ( func TestAPIPullReview(t *testing.T) { defer prepareTestEnv(t)() - pullIssue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 3}).(*models.Issue) + pullIssue := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 3}).(*models.Issue) assert.NoError(t, pullIssue.LoadAttributes()) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue.RepoID}).(*models.Repository) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue.RepoID}).(*models.Repository) // test ListPullReviews session := loginUser(t, "user2") @@ -62,7 +63,7 @@ func TestAPIPullReview(t *testing.T) { assert.EqualValues(t, *reviews[5], review) // test GetPullReviewComments - comment := models.AssertExistsAndLoadBean(t, &models.Comment{ID: 7}).(*models.Comment) + comment := db.AssertExistsAndLoadBean(t, &models.Comment{ID: 7}).(*models.Comment) req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d/comments?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, 10, token) resp = session.MakeRequest(t, req, http.StatusOK) var reviewComments []*api.PullReviewComment @@ -195,9 +196,9 @@ func TestAPIPullReview(t *testing.T) { // test get review requests // to make it simple, use same api with get review - pullIssue12 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 12}).(*models.Issue) + pullIssue12 := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 12}).(*models.Issue) assert.NoError(t, pullIssue12.LoadAttributes()) - repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue12.RepoID}).(*models.Repository) + repo3 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue12.RepoID}).(*models.Repository) req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token) resp = session.MakeRequest(t, req, http.StatusOK) @@ -219,9 +220,9 @@ func TestAPIPullReview(t *testing.T) { func TestAPIPullReviewRequest(t *testing.T) { defer prepareTestEnv(t)() - pullIssue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 3}).(*models.Issue) + pullIssue := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 3}).(*models.Issue) assert.NoError(t, pullIssue.LoadAttributes()) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue.RepoID}).(*models.Repository) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue.RepoID}).(*models.Repository) // Test add Review Request session := loginUser(t, "user2") @@ -264,9 +265,9 @@ func TestAPIPullReviewRequest(t *testing.T) { session.MakeRequest(t, req, http.StatusNoContent) // Test team review request - pullIssue12 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 12}).(*models.Issue) + pullIssue12 := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 12}).(*models.Issue) assert.NoError(t, pullIssue12.LoadAttributes()) - repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue12.RepoID}).(*models.Repository) + repo3 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue12.RepoID}).(*models.Repository) // Test add Team Review Request req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{ diff --git a/integrations/api_pull_test.go b/integrations/api_pull_test.go index e6a4aca1530ee..069650993dfab 100644 --- a/integrations/api_pull_test.go +++ b/integrations/api_pull_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/services/forms" @@ -20,8 +21,8 @@ import ( func TestAPIViewPulls(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, "user2") token := getTokenForLoggedInUser(t, session) @@ -30,16 +31,16 @@ func TestAPIViewPulls(t *testing.T) { var pulls []*api.PullRequest DecodeJSON(t, resp, &pulls) - expectedLen := models.GetCount(t, &models.Issue{RepoID: repo.ID}, models.Cond("is_pull = ?", true)) + expectedLen := db.GetCount(t, &models.Issue{RepoID: repo.ID}, db.Cond("is_pull = ?", true)) assert.Len(t, pulls, expectedLen) } // TestAPIMergePullWIP ensures that we can't merge a WIP pull request func TestAPIMergePullWIP(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) - pr := models.AssertExistsAndLoadBean(t, &models.PullRequest{Status: models.PullRequestStatusMergeable}, models.Cond("has_merged = ?", false)).(*models.PullRequest) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + pr := db.AssertExistsAndLoadBean(t, &models.PullRequest{Status: models.PullRequestStatusMergeable}, db.Cond("has_merged = ?", false)).(*models.PullRequest) pr.LoadIssue() issue_service.ChangeTitle(pr.Issue, owner, setting.Repository.PullRequest.WorkInProgressPrefixes[0]+" "+pr.Issue.Title) @@ -60,12 +61,12 @@ func TestAPIMergePullWIP(t *testing.T) { func TestAPICreatePullSuccess(t *testing.T) { defer prepareTestEnv(t)() - repo10 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository) + repo10 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository) // repo10 have code, pulls units. - repo11 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 11}).(*models.Repository) + repo11 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 11}).(*models.Repository) // repo11 only have code unit but should still create pulls - owner10 := models.AssertExistsAndLoadBean(t, &models.User{ID: repo10.OwnerID}).(*models.User) - owner11 := models.AssertExistsAndLoadBean(t, &models.User{ID: repo11.OwnerID}).(*models.User) + owner10 := db.AssertExistsAndLoadBean(t, &models.User{ID: repo10.OwnerID}).(*models.User) + owner11 := db.AssertExistsAndLoadBean(t, &models.User{ID: repo11.OwnerID}).(*models.User) session := loginUser(t, owner11.Name) token := getTokenForLoggedInUser(t, session) @@ -81,11 +82,11 @@ func TestAPICreatePullSuccess(t *testing.T) { func TestAPICreatePullWithFieldsSuccess(t *testing.T) { defer prepareTestEnv(t)() // repo10 have code, pulls units. - repo10 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository) - owner10 := models.AssertExistsAndLoadBean(t, &models.User{ID: repo10.OwnerID}).(*models.User) + repo10 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository) + owner10 := db.AssertExistsAndLoadBean(t, &models.User{ID: repo10.OwnerID}).(*models.User) // repo11 only have code unit but should still create pulls - repo11 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 11}).(*models.Repository) - owner11 := models.AssertExistsAndLoadBean(t, &models.User{ID: repo11.OwnerID}).(*models.User) + repo11 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 11}).(*models.Repository) + owner11 := db.AssertExistsAndLoadBean(t, &models.User{ID: repo11.OwnerID}).(*models.User) session := loginUser(t, owner11.Name) token := getTokenForLoggedInUser(t, session) @@ -118,11 +119,11 @@ func TestAPICreatePullWithFieldsSuccess(t *testing.T) { func TestAPICreatePullWithFieldsFailure(t *testing.T) { defer prepareTestEnv(t)() // repo10 have code, pulls units. - repo10 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository) - owner10 := models.AssertExistsAndLoadBean(t, &models.User{ID: repo10.OwnerID}).(*models.User) + repo10 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository) + owner10 := db.AssertExistsAndLoadBean(t, &models.User{ID: repo10.OwnerID}).(*models.User) // repo11 only have code unit but should still create pulls - repo11 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 11}).(*models.Repository) - owner11 := models.AssertExistsAndLoadBean(t, &models.User{ID: repo11.OwnerID}).(*models.User) + repo11 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 11}).(*models.Repository) + owner11 := db.AssertExistsAndLoadBean(t, &models.User{ID: repo11.OwnerID}).(*models.User) session := loginUser(t, owner11.Name) token := getTokenForLoggedInUser(t, session) @@ -151,8 +152,8 @@ func TestAPICreatePullWithFieldsFailure(t *testing.T) { func TestAPIEditPull(t *testing.T) { defer prepareTestEnv(t)() - repo10 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository) - owner10 := models.AssertExistsAndLoadBean(t, &models.User{ID: repo10.OwnerID}).(*models.User) + repo10 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository) + owner10 := db.AssertExistsAndLoadBean(t, &models.User{ID: repo10.OwnerID}).(*models.User) session := loginUser(t, owner10.Name) token := getTokenForLoggedInUser(t, session) diff --git a/integrations/api_releases_test.go b/integrations/api_releases_test.go index 027b282036f64..cb04c2a8d3f20 100644 --- a/integrations/api_releases_test.go +++ b/integrations/api_releases_test.go @@ -11,6 +11,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" api "code.gitea.io/gitea/modules/structs" @@ -20,8 +21,8 @@ import ( func TestAPIListReleases(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) session := loginUser(t, user2.LowerName) token := getTokenForLoggedInUser(t, session) @@ -84,7 +85,7 @@ func createNewReleaseUsingAPI(t *testing.T, session *TestSession, token string, var newRelease api.Release DecodeJSON(t, resp, &newRelease) - models.AssertExistsAndLoadBean(t, &models.Release{ + db.AssertExistsAndLoadBean(t, &models.Release{ ID: newRelease.ID, TagName: newRelease.TagName, Title: newRelease.Title, @@ -97,8 +98,8 @@ func createNewReleaseUsingAPI(t *testing.T, session *TestSession, token string, func TestAPICreateAndUpdateRelease(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, owner.LowerName) token := getTokenForLoggedInUser(t, session) @@ -137,7 +138,7 @@ func TestAPICreateAndUpdateRelease(t *testing.T) { resp = session.MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &newRelease) - models.AssertExistsAndLoadBean(t, &models.Release{ + db.AssertExistsAndLoadBean(t, &models.Release{ ID: newRelease.ID, TagName: newRelease.TagName, Title: newRelease.Title, @@ -148,8 +149,8 @@ func TestAPICreateAndUpdateRelease(t *testing.T) { func TestAPICreateReleaseToDefaultBranch(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, owner.LowerName) token := getTokenForLoggedInUser(t, session) @@ -159,8 +160,8 @@ func TestAPICreateReleaseToDefaultBranch(t *testing.T) { func TestAPICreateReleaseToDefaultBranchOnExistingTag(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, owner.LowerName) token := getTokenForLoggedInUser(t, session) @@ -177,8 +178,8 @@ func TestAPICreateReleaseToDefaultBranchOnExistingTag(t *testing.T) { func TestAPIGetReleaseByTag(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, owner.LowerName) tag := "v1.1" @@ -210,8 +211,8 @@ func TestAPIGetReleaseByTag(t *testing.T) { func TestAPIDeleteReleaseByTagName(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, owner.LowerName) token := getTokenForLoggedInUser(t, session) diff --git a/integrations/api_repo_edit_test.go b/integrations/api_repo_edit_test.go index 618c1f0ad09b0..e980357a0f815 100644 --- a/integrations/api_repo_edit_test.go +++ b/integrations/api_repo_edit_test.go @@ -11,6 +11,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -132,13 +133,13 @@ func TestAPIRepoEdit(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { bFalse, bTrue := false, true - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 - user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3, is an org - user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos - repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo - repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo - repo15 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 15}).(*models.Repository) // empty repo - repo16 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 + user3 := db.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3, is an org + user4 := db.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos + repo1 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo + repo3 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo + repo15 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 15}).(*models.Repository) // empty repo + repo16 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo // Get user2's token session := loginUser(t, user2.Name) @@ -164,7 +165,7 @@ func TestAPIRepoEdit(t *testing.T) { assert.Equal(t, *repoEditOption.Website, repo.Website) assert.Equal(t, *repoEditOption.Archived, repo.Archived) // check repo1 from database - repo1edited := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + repo1edited := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) repo1editedOption := getRepoEditOptionFromRepo(repo1edited) assert.Equal(t, *repoEditOption.Name, *repo1editedOption.Name) assert.Equal(t, *repoEditOption.Description, *repo1editedOption.Description) @@ -189,7 +190,7 @@ func TestAPIRepoEdit(t *testing.T) { DecodeJSON(t, resp, &repo) assert.NotNil(t, repo) // check repo1 was written to database - repo1edited = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + repo1edited = db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) repo1editedOption = getRepoEditOptionFromRepo(repo1edited) assert.Equal(t, *repo1editedOption.HasIssues, true) assert.Nil(t, repo1editedOption.ExternalTracker) @@ -211,7 +212,7 @@ func TestAPIRepoEdit(t *testing.T) { DecodeJSON(t, resp, &repo) assert.NotNil(t, repo) // check repo1 was written to database - repo1edited = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + repo1edited = db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) repo1editedOption = getRepoEditOptionFromRepo(repo1edited) assert.Equal(t, *repo1editedOption.HasIssues, true) assert.Equal(t, *repo1editedOption.ExternalTracker, *repoEditOption.ExternalTracker) @@ -242,7 +243,7 @@ func TestAPIRepoEdit(t *testing.T) { DecodeJSON(t, resp, &repo) assert.NotNil(t, repo) // check repo1 was written to database - repo1edited = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + repo1edited = db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) repo1editedOption = getRepoEditOptionFromRepo(repo1edited) assert.Equal(t, *repo1editedOption.Description, *repoEditOption.Description) assert.Equal(t, *repo1editedOption.HasIssues, true) @@ -287,7 +288,7 @@ func TestAPIRepoEdit(t *testing.T) { _ = session.MakeRequest(t, req, http.StatusOK) // Test making a repo public that is private - repo16 = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) + repo16 = db.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) assert.True(t, repo16.IsPrivate) repoEditOption = &api.EditRepoOption{ Private: &bFalse, @@ -295,7 +296,7 @@ func TestAPIRepoEdit(t *testing.T) { url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo16.Name, token2) req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) _ = session.MakeRequest(t, req, http.StatusOK) - repo16 = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) + repo16 = db.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) assert.False(t, repo16.IsPrivate) // Make it private again repoEditOption.Private = &bTrue @@ -309,7 +310,7 @@ func TestAPIRepoEdit(t *testing.T) { Archived: &bTrue, }) _ = session.MakeRequest(t, req, http.StatusOK) - repo15 = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 15}).(*models.Repository) + repo15 = db.AssertExistsAndLoadBean(t, &models.Repository{ID: 15}).(*models.Repository) assert.True(t, repo15.IsArchived) req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{ Archived: &bFalse, diff --git a/integrations/api_repo_file_create_test.go b/integrations/api_repo_file_create_test.go index 17ed6cb7cf4a4..5b0e3927f905c 100644 --- a/integrations/api_repo_file_create_test.go +++ b/integrations/api_repo_file_create_test.go @@ -14,6 +14,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" @@ -108,8 +109,8 @@ func getExpectedFileResponseForCreate(commitID, treePath string) *api.FileRespon func BenchmarkAPICreateFileSmall(b *testing.B) { onGiteaRunTB(b, func(t testing.TB, u *url.URL) { b := t.(*testing.B) - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 - repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 + repo1 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo for n := 0; n < b.N; n++ { treePath := fmt.Sprintf("update/file%d.txt", n) @@ -123,8 +124,8 @@ func BenchmarkAPICreateFileMedium(b *testing.B) { onGiteaRunTB(b, func(t testing.TB, u *url.URL) { b := t.(*testing.B) - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 - repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 + repo1 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo b.ResetTimer() for n := 0; n < b.N; n++ { @@ -137,12 +138,12 @@ func BenchmarkAPICreateFileMedium(b *testing.B) { func TestAPICreateFile(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 - user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3, is an org - user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos - repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo - repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo - repo16 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 + user3 := db.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3, is an org + user4 := db.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos + repo1 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo + repo3 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo + repo16 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo fileID := 0 // Get user2's token diff --git a/integrations/api_repo_file_delete_test.go b/integrations/api_repo_file_delete_test.go index 178aec6f5bc3e..69e24d29c3b90 100644 --- a/integrations/api_repo_file_delete_test.go +++ b/integrations/api_repo_file_delete_test.go @@ -11,6 +11,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -37,12 +38,12 @@ func getDeleteFileOptions() *api.DeleteFileOptions { func TestAPIDeleteFile(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 - user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3, is an org - user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos - repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo - repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo - repo16 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 + user3 := db.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3, is an org + user4 := db.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos + repo1 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo + repo3 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo + repo16 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo fileID := 0 // Get user2's token diff --git a/integrations/api_repo_file_update_test.go b/integrations/api_repo_file_update_test.go index 69a94637747f8..b6d9be24f5fe9 100644 --- a/integrations/api_repo_file_update_test.go +++ b/integrations/api_repo_file_update_test.go @@ -13,6 +13,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" @@ -103,12 +104,12 @@ func getExpectedFileResponseForUpdate(commitID, treePath string) *api.FileRespon func TestAPIUpdateFile(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 - user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3, is an org - user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos - repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo - repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo - repo16 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 + user3 := db.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3, is an org + user4 := db.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos + repo1 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo + repo3 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo + repo16 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo fileID := 0 // Get user2's token diff --git a/integrations/api_repo_get_contents_list_test.go b/integrations/api_repo_get_contents_list_test.go index ddbd877a40e80..625231a8b2272 100644 --- a/integrations/api_repo_get_contents_list_test.go +++ b/integrations/api_repo_get_contents_list_test.go @@ -11,6 +11,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" @@ -52,13 +53,13 @@ func TestAPIGetContentsList(t *testing.T) { func testAPIGetContentsList(t *testing.T, u *url.URL) { /*** SETUP ***/ - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 - user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3, is an org - user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos - repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo - repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo - repo16 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo - treePath := "" // root dir + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 + user3 := db.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3, is an org + user4 := db.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos + repo1 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo + repo3 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo + repo16 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo + treePath := "" // root dir // Get user2's token session := loginUser(t, user2.Name) diff --git a/integrations/api_repo_get_contents_test.go b/integrations/api_repo_get_contents_test.go index 3d4a31be81140..e096a718bdb67 100644 --- a/integrations/api_repo_get_contents_test.go +++ b/integrations/api_repo_get_contents_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" @@ -53,12 +54,12 @@ func TestAPIGetContents(t *testing.T) { func testAPIGetContents(t *testing.T, u *url.URL) { /*** SETUP ***/ - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 - user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3, is an org - user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos - repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo - repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo - repo16 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 + user3 := db.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3, is an org + user4 := db.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos + repo1 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo + repo3 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo + repo16 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo treePath := "README.md" // Get user2's token diff --git a/integrations/api_repo_git_blobs_test.go b/integrations/api_repo_git_blobs_test.go index 675916eeaaaae..5bbf13ac46b1e 100644 --- a/integrations/api_repo_git_blobs_test.go +++ b/integrations/api_repo_git_blobs_test.go @@ -9,6 +9,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -16,12 +17,12 @@ import ( func TestAPIReposGitBlobs(t *testing.T) { defer prepareTestEnv(t)() - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 - user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3 - user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos - repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo - repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo - repo16 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 + user3 := db.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3 + user4 := db.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos + repo1 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo + repo3 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo + repo16 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo repo1ReadmeSHA := "65f1bf27bc3bf70f64657658635e66094edbcb4d" repo3ReadmeSHA := "d56a3073c1dbb7b15963110a049d50cdb5db99fc" repo16ReadmeSHA := "f90451c72ef61a7645293d17b47be7a8e983da57" diff --git a/integrations/api_repo_git_commits_test.go b/integrations/api_repo_git_commits_test.go index d6bd5fc62e6bd..314416d2646e5 100644 --- a/integrations/api_repo_git_commits_test.go +++ b/integrations/api_repo_git_commits_test.go @@ -9,6 +9,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -24,7 +25,7 @@ func compareCommitFiles(t *testing.T, expect []string, files []*api.CommitAffect func TestAPIReposGitCommits(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // Login as User2. session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) @@ -52,7 +53,7 @@ func TestAPIReposGitCommits(t *testing.T) { func TestAPIReposGitCommitList(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // Login as User2. session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) @@ -75,7 +76,7 @@ func TestAPIReposGitCommitList(t *testing.T) { func TestAPIReposGitCommitListPage2Empty(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // Login as User2. session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) @@ -92,7 +93,7 @@ func TestAPIReposGitCommitListPage2Empty(t *testing.T) { func TestAPIReposGitCommitListDifferentBranch(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // Login as User2. session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) @@ -108,3 +109,26 @@ func TestAPIReposGitCommitListDifferentBranch(t *testing.T) { assert.Equal(t, "f27c2b2b03dcab38beaf89b0ab4ff61f6de63441", apiData[0].CommitMeta.SHA) compareCommitFiles(t, []string{"readme.md"}, apiData[0].Files) } + +func TestDownloadCommitDiffOrPatch(t *testing.T) { + defer prepareTestEnv(t)() + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + // Test getting diff + reqDiff := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/git/commits/f27c2b2b03dcab38beaf89b0ab4ff61f6de63441.diff?token="+token, user.Name) + resp := session.MakeRequest(t, reqDiff, http.StatusOK) + assert.EqualValues(t, + "commit f27c2b2b03dcab38beaf89b0ab4ff61f6de63441\nAuthor: User2 \nDate: Sun Aug 6 19:55:01 2017 +0200\n\n good signed commit\n\ndiff --git a/readme.md b/readme.md\nnew file mode 100644\nindex 0000000..458121c\n--- /dev/null\n+++ b/readme.md\n@@ -0,0 +1 @@\n+good sign\n", + resp.Body.String()) + + // Test getting patch + reqPatch := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/git/commits/f27c2b2b03dcab38beaf89b0ab4ff61f6de63441.patch?token="+token, user.Name) + resp = session.MakeRequest(t, reqPatch, http.StatusOK) + assert.EqualValues(t, + "From f27c2b2b03dcab38beaf89b0ab4ff61f6de63441 Mon Sep 17 00:00:00 2001\nFrom: User2 \nDate: Sun, 6 Aug 2017 19:55:01 +0200\nSubject: [PATCH] good signed commit\n\n---\n readme.md | 1 +\n 1 file changed, 1 insertion(+)\n create mode 100644 readme.md\n\ndiff --git a/readme.md b/readme.md\nnew file mode 100644\nindex 0000000..458121c\n--- /dev/null\n+++ b/readme.md\n@@ -0,0 +1 @@\n+good sign\n", + resp.Body.String()) + +} diff --git a/integrations/api_repo_git_hook_test.go b/integrations/api_repo_git_hook_test.go index f7e55d92ae899..490251b30b0b5 100644 --- a/integrations/api_repo_git_hook_test.go +++ b/integrations/api_repo_git_hook_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -23,8 +24,8 @@ echo Hello, World! func TestAPIListGitHooks(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 37}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 37}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) // user1 is an admin user session := loginUser(t, "user1") @@ -49,8 +50,8 @@ func TestAPIListGitHooks(t *testing.T) { func TestAPIListGitHooksNoHooks(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) // user1 is an admin user session := loginUser(t, "user1") @@ -70,8 +71,8 @@ func TestAPIListGitHooksNoHooks(t *testing.T) { func TestAPIListGitHooksNoAccess(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session) @@ -83,8 +84,8 @@ func TestAPIListGitHooksNoAccess(t *testing.T) { func TestAPIGetGitHook(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 37}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 37}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) // user1 is an admin user session := loginUser(t, "user1") @@ -101,8 +102,8 @@ func TestAPIGetGitHook(t *testing.T) { func TestAPIGetGitHookNoAccess(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session) @@ -114,8 +115,8 @@ func TestAPIGetGitHookNoAccess(t *testing.T) { func TestAPIEditGitHook(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) // user1 is an admin user session := loginUser(t, "user1") @@ -144,8 +145,8 @@ func TestAPIEditGitHook(t *testing.T) { func TestAPIEditGitHookNoAccess(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session) @@ -160,8 +161,8 @@ func TestAPIEditGitHookNoAccess(t *testing.T) { func TestAPIDeleteGitHook(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 37}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 37}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) // user1 is an admin user session := loginUser(t, "user1") @@ -183,8 +184,8 @@ func TestAPIDeleteGitHook(t *testing.T) { func TestAPIDeleteGitHookNoAccess(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session) diff --git a/integrations/api_repo_git_notes_test.go b/integrations/api_repo_git_notes_test.go index 6eae5e970d63b..02235f9d910c9 100644 --- a/integrations/api_repo_git_notes_test.go +++ b/integrations/api_repo_git_notes_test.go @@ -10,13 +10,14 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" ) func TestAPIReposGitNotes(t *testing.T) { onGiteaRun(t, func(*testing.T, *url.URL) { - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // Login as User2. session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) diff --git a/integrations/api_repo_git_ref_test.go b/integrations/api_repo_git_ref_test.go index 39237f59a257c..fcf5edc758480 100644 --- a/integrations/api_repo_git_ref_test.go +++ b/integrations/api_repo_git_ref_test.go @@ -9,11 +9,12 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) func TestAPIReposGitRefs(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // Login as User2. session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) diff --git a/integrations/api_repo_git_tags_test.go b/integrations/api_repo_git_tags_test.go index bf6fc7c858137..01e0450c6bd01 100644 --- a/integrations/api_repo_git_tags_test.go +++ b/integrations/api_repo_git_tags_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -19,8 +20,8 @@ import ( func TestAPIGitTags(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // Login as User2. session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) @@ -64,8 +65,8 @@ func TestAPIGitTags(t *testing.T) { func TestAPIDeleteTagByName(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, owner.LowerName) token := getTokenForLoggedInUser(t, session) diff --git a/integrations/api_repo_git_trees_test.go b/integrations/api_repo_git_trees_test.go index 6b4e120dec1e7..bf605d4d86010 100644 --- a/integrations/api_repo_git_trees_test.go +++ b/integrations/api_repo_git_trees_test.go @@ -9,16 +9,17 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) func TestAPIReposGitTrees(t *testing.T) { defer prepareTestEnv(t)() - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 - user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3 - user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos - repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo - repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo - repo16 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 + user3 := db.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3 + user4 := db.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos + repo1 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo + repo3 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo + repo16 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo repo1TreeSHA := "65f1bf27bc3bf70f64657658635e66094edbcb4d" repo3TreeSHA := "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6" repo16TreeSHA := "69554a64c1e6030f051e5c3f94bfbd773cd6a324" diff --git a/integrations/api_repo_lfs_locks_test.go b/integrations/api_repo_lfs_locks_test.go index 03549c11f4c20..b2d8f0e63179b 100644 --- a/integrations/api_repo_lfs_locks_test.go +++ b/integrations/api_repo_lfs_locks_test.go @@ -11,6 +11,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -21,8 +22,8 @@ import ( func TestAPILFSLocksNotStarted(t *testing.T) { defer prepareTestEnv(t)() setting.LFS.StartServer = false - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) req := NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks", user.Name, repo.Name) MakeRequest(t, req, http.StatusNotFound) @@ -37,8 +38,8 @@ func TestAPILFSLocksNotStarted(t *testing.T) { func TestAPILFSLocksNotLogin(t *testing.T) { defer prepareTestEnv(t)() setting.LFS.StartServer = true - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) req := NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks", user.Name, repo.Name) req.Header.Set("Accept", lfs.MediaType) @@ -51,11 +52,11 @@ func TestAPILFSLocksNotLogin(t *testing.T) { func TestAPILFSLocksLogged(t *testing.T) { defer prepareTestEnv(t)() setting.LFS.StartServer = true - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) //in org 3 - user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) //in org 3 + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) //in org 3 + user4 := db.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) //in org 3 - repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // own by org 3 + repo1 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + repo3 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // own by org 3 tests := []struct { user *models.User diff --git a/integrations/api_repo_lfs_migrate_test.go b/integrations/api_repo_lfs_migrate_test.go index 7280658b74a9c..9acc96e4f473a 100644 --- a/integrations/api_repo_lfs_migrate_test.go +++ b/integrations/api_repo_lfs_migrate_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -25,7 +26,7 @@ func TestAPIRepoLFSMigrateLocal(t *testing.T) { setting.ImportLocalPaths = true setting.Migrations.AllowLocalNetworks = true - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) diff --git a/integrations/api_repo_lfs_test.go b/integrations/api_repo_lfs_test.go index 56a6fa81fd86f..22c324f973fa4 100644 --- a/integrations/api_repo_lfs_test.go +++ b/integrations/api_repo_lfs_test.go @@ -13,6 +13,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/setting" @@ -25,8 +26,8 @@ func TestAPILFSNotStarted(t *testing.T) { setting.LFS.StartServer = false - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) req := NewRequestf(t, "POST", "/%s/%s.git/info/lfs/objects/batch", user.Name, repo.Name) MakeRequest(t, req, http.StatusNotFound) @@ -45,8 +46,8 @@ func TestAPILFSMediaType(t *testing.T) { setting.LFS.StartServer = true - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) req := NewRequestf(t, "POST", "/%s/%s.git/info/lfs/objects/batch", user.Name, repo.Name) MakeRequest(t, req, http.StatusUnsupportedMediaType) diff --git a/integrations/api_repo_raw_test.go b/integrations/api_repo_raw_test.go index 62b734fa060a2..77b33473c82dc 100644 --- a/integrations/api_repo_raw_test.go +++ b/integrations/api_repo_raw_test.go @@ -9,11 +9,12 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) func TestAPIReposRaw(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // Login as User2. session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) diff --git a/integrations/api_repo_tags_test.go b/integrations/api_repo_tags_test.go index 0bf54d3a9598c..8897bc44bc544 100644 --- a/integrations/api_repo_tags_test.go +++ b/integrations/api_repo_tags_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -18,7 +19,7 @@ import ( func TestAPIRepoTags(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // Login as User2. session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) diff --git a/integrations/api_repo_teams_test.go b/integrations/api_repo_teams_test.go index 4a155130b9354..e718d79cbe17a 100644 --- a/integrations/api_repo_teams_test.go +++ b/integrations/api_repo_teams_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -19,9 +20,9 @@ func TestAPIRepoTeams(t *testing.T) { defer prepareTestEnv(t)() // publicOrgRepo = user3/repo21 - publicOrgRepo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 32}).(*models.Repository) + publicOrgRepo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 32}).(*models.Repository) // user4 - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) @@ -61,7 +62,7 @@ func TestAPIRepoTeams(t *testing.T) { res = session.MakeRequest(t, req, http.StatusForbidden) // AddTeam with user2 - user = models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user = db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) session = loginUser(t, user.Name) token = getTokenForLoggedInUser(t, session) url = fmt.Sprintf("/api/v1/repos/%s/teams/%s?token=%s", publicOrgRepo.FullName(), "team1", token) diff --git a/integrations/api_repo_test.go b/integrations/api_repo_test.go index 3948489f56fcf..b93319be338cd 100644 --- a/integrations/api_repo_test.go +++ b/integrations/api_repo_test.go @@ -6,12 +6,13 @@ package integrations import ( "fmt" - "io/ioutil" "net/http" "net/url" + "os" "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -21,15 +22,15 @@ import ( func TestAPIUserReposNotLogin(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) req := NewRequestf(t, "GET", "/api/v1/users/%s/repos", user.Name) resp := MakeRequest(t, req, http.StatusOK) var apiRepos []api.Repository DecodeJSON(t, resp, &apiRepos) - expectedLen := models.GetCount(t, models.Repository{OwnerID: user.ID}, - models.Cond("is_private = ?", false)) + expectedLen := db.GetCount(t, models.Repository{OwnerID: user.ID}, + db.Cond("is_private = ?", false)) assert.Len(t, apiRepos, expectedLen) for _, repo := range apiRepos { assert.EqualValues(t, user.ID, repo.Owner.ID) @@ -52,11 +53,11 @@ func TestAPISearchRepo(t *testing.T) { assert.False(t, repo.Private) } - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 15}).(*models.User) - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 16}).(*models.User) - user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 18}).(*models.User) - user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 20}).(*models.User) - orgUser := models.AssertExistsAndLoadBean(t, &models.User{ID: 17}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 15}).(*models.User) + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 16}).(*models.User) + user3 := db.AssertExistsAndLoadBean(t, &models.User{ID: 18}).(*models.User) + user4 := db.AssertExistsAndLoadBean(t, &models.User{ID: 20}).(*models.User) + orgUser := db.AssertExistsAndLoadBean(t, &models.User{ID: 17}).(*models.User) oldAPIDefaultNum := setting.API.DefaultPagingNum defer func() { @@ -208,7 +209,7 @@ var repoCache = make(map[int64]*models.Repository) func getRepo(t *testing.T, repoID int64) *models.Repository { if _, ok := repoCache[repoID]; !ok { - repoCache[repoID] = models.AssertExistsAndLoadBean(t, &models.Repository{ID: repoID}).(*models.Repository) + repoCache[repoID] = db.AssertExistsAndLoadBean(t, &models.Repository{ID: repoID}).(*models.Repository) } return repoCache[repoID] } @@ -245,11 +246,11 @@ func TestAPIViewRepo(t *testing.T) { func TestAPIOrgRepos(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) - user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 5}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) + user3 := db.AssertExistsAndLoadBean(t, &models.User{ID: 5}).(*models.User) // User3 is an Org. Check their repos. - sourceOrg := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) + sourceOrg := db.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) expectedResults := map[*models.User]struct { count int @@ -291,7 +292,7 @@ func TestAPIOrgRepos(t *testing.T) { func TestAPIGetRepoByIDUnauthorized(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) req := NewRequestf(t, "GET", "/api/v1/repositories/2?token="+token) @@ -315,7 +316,7 @@ func TestAPIRepoMigrate(t *testing.T) { defer prepareTestEnv(t)() for _, testCase := range testCases { - user := models.AssertExistsAndLoadBean(t, &models.User{ID: testCase.ctxUserID}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: testCase.ctxUserID}).(*models.User) session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) req := NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate?token="+token, &api.MigrateRepoOptions{ @@ -355,7 +356,7 @@ func testAPIRepoMigrateConflict(t *testing.T, u *url.URL) { httpContext := baseAPITestContext httpContext.Reponame = "repo-tmp-17" - dstPath, err := ioutil.TempDir("", httpContext.Reponame) + dstPath, err := os.MkdirTemp("", httpContext.Reponame) assert.NoError(t, err) defer util.RemoveAll(dstPath) t.Run("CreateRepo", doAPICreateRepository(httpContext, false)) @@ -394,7 +395,7 @@ func TestAPIOrgRepoCreate(t *testing.T) { defer prepareTestEnv(t)() for _, testCase := range testCases { - user := models.AssertExistsAndLoadBean(t, &models.User{ID: testCase.ctxUserID}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: testCase.ctxUserID}).(*models.User) session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/org/%s/repos?token="+token, testCase.orgName), &api.CreateRepoOption{ @@ -418,7 +419,7 @@ func testAPIRepoCreateConflict(t *testing.T, u *url.URL) { httpContext := baseAPITestContext httpContext.Reponame = "repo-tmp-17" - dstPath, err := ioutil.TempDir("", httpContext.Reponame) + dstPath, err := os.MkdirTemp("", httpContext.Reponame) assert.NoError(t, err) defer util.RemoveAll(dstPath) t.Run("CreateRepo", doAPICreateRepository(httpContext, false)) @@ -462,7 +463,7 @@ func TestAPIRepoTransfer(t *testing.T) { defer prepareTestEnv(t)() //create repo to move - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) repoName := "moveME" @@ -479,8 +480,8 @@ func TestAPIRepoTransfer(t *testing.T) { //start testing for _, testCase := range testCases { - user = models.AssertExistsAndLoadBean(t, &models.User{ID: testCase.ctxUserID}).(*models.User) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: apiRepo.ID}).(*models.Repository) + user = db.AssertExistsAndLoadBean(t, &models.User{ID: testCase.ctxUserID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: apiRepo.ID}).(*models.Repository) session = loginUser(t, user.Name) token = getTokenForLoggedInUser(t, session) req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer?token=%s", repo.OwnerName, repo.Name, token), &api.TransferRepoOption{ @@ -491,18 +492,18 @@ func TestAPIRepoTransfer(t *testing.T) { } //cleanup - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: apiRepo.ID}).(*models.Repository) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: apiRepo.ID}).(*models.Repository) _ = models.DeleteRepository(user, repo.OwnerID, repo.ID) } func TestAPIGenerateRepo(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) - templateRepo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 44}).(*models.Repository) + templateRepo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 44}).(*models.Repository) // user repo := new(api.Repository) @@ -534,10 +535,10 @@ func TestAPIGenerateRepo(t *testing.T) { func TestAPIRepoGetReviewers(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/reviewers?token=%s", user.Name, repo.Name, token) resp := session.MakeRequest(t, req, http.StatusOK) @@ -548,10 +549,10 @@ func TestAPIRepoGetReviewers(t *testing.T) { func TestAPIRepoGetAssignees(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees?token=%s", user.Name, repo.Name, token) resp := session.MakeRequest(t, req, http.StatusOK) diff --git a/integrations/api_repo_topic_test.go b/integrations/api_repo_topic_test.go index bab17a68be5a5..cc76f188589bc 100644 --- a/integrations/api_repo_topic_test.go +++ b/integrations/api_repo_topic_test.go @@ -11,6 +11,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -50,11 +51,11 @@ func TestAPITopicSearch(t *testing.T) { func TestAPIRepoTopic(t *testing.T) { defer prepareTestEnv(t)() - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of repo2 - user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of repo3 - user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // write access to repo 3 - repo2 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) - repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of repo2 + user3 := db.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of repo3 + user4 := db.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // write access to repo 3 + repo2 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) + repo3 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // Get user2's token session := loginUser(t, user2.Name) diff --git a/integrations/api_team_test.go b/integrations/api_team_test.go index 0b77dc3be700b..0864500335258 100644 --- a/integrations/api_team_test.go +++ b/integrations/api_team_test.go @@ -11,6 +11,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" @@ -20,9 +21,9 @@ import ( func TestAPITeam(t *testing.T) { defer prepareTestEnv(t)() - teamUser := models.AssertExistsAndLoadBean(t, &models.TeamUser{}).(*models.TeamUser) - team := models.AssertExistsAndLoadBean(t, &models.Team{ID: teamUser.TeamID}).(*models.Team) - user := models.AssertExistsAndLoadBean(t, &models.User{ID: teamUser.UID}).(*models.User) + teamUser := db.AssertExistsAndLoadBean(t, &models.TeamUser{}).(*models.TeamUser) + team := db.AssertExistsAndLoadBean(t, &models.Team{ID: teamUser.TeamID}).(*models.Team) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: teamUser.UID}).(*models.User) session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) @@ -35,8 +36,8 @@ func TestAPITeam(t *testing.T) { assert.Equal(t, team.Name, apiTeam.Name) // non team member user will not access the teams details - teamUser2 := models.AssertExistsAndLoadBean(t, &models.TeamUser{ID: 3}).(*models.TeamUser) - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: teamUser2.UID}).(*models.User) + teamUser2 := db.AssertExistsAndLoadBean(t, &models.TeamUser{ID: 3}).(*models.TeamUser) + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: teamUser2.UID}).(*models.User) session = loginUser(t, user2.Name) token = getTokenForLoggedInUser(t, session) @@ -47,11 +48,11 @@ func TestAPITeam(t *testing.T) { _ = session.MakeRequest(t, req, http.StatusUnauthorized) // Get an admin user able to create, update and delete teams. - user = models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) + user = db.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) session = loginUser(t, user.Name) token = getTokenForLoggedInUser(t, session) - org := models.AssertExistsAndLoadBean(t, &models.User{ID: 6}).(*models.User) + org := db.AssertExistsAndLoadBean(t, &models.User{ID: 6}).(*models.User) // Create team. teamToCreate := &api.CreateTeamOption{ @@ -101,7 +102,7 @@ func TestAPITeam(t *testing.T) { teamToEdit.Permission, teamToEdit.Units) // Read team. - teamRead := models.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team) + teamRead := db.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team) req = NewRequestf(t, "GET", "/api/v1/teams/%d?token="+token, teamID) resp = session.MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiTeam) @@ -111,7 +112,7 @@ func TestAPITeam(t *testing.T) { // Delete team. req = NewRequestf(t, "DELETE", "/api/v1/teams/%d?token="+token, teamID) session.MakeRequest(t, req, http.StatusNoContent) - models.AssertNotExistsBean(t, &models.Team{ID: teamID}) + db.AssertNotExistsBean(t, &models.Team{ID: teamID}) } func checkTeamResponse(t *testing.T, apiTeam *api.Team, name, description string, includesAllRepositories bool, permission string, units []string) { @@ -125,7 +126,7 @@ func checkTeamResponse(t *testing.T, apiTeam *api.Team, name, description string } func checkTeamBean(t *testing.T, id int64, name, description string, includesAllRepositories bool, permission string, units []string) { - team := models.AssertExistsAndLoadBean(t, &models.Team{ID: id}).(*models.Team) + team := db.AssertExistsAndLoadBean(t, &models.Team{ID: id}).(*models.Team) assert.NoError(t, team.GetUnits(), "GetUnits") checkTeamResponse(t, convert.ToTeam(team), name, description, includesAllRepositories, permission, units) } @@ -138,8 +139,8 @@ type TeamSearchResults struct { func TestAPITeamSearch(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - org := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + org := db.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) var results TeamSearchResults @@ -154,7 +155,7 @@ func TestAPITeamSearch(t *testing.T) { assert.Equal(t, "test_team", results.Data[0].Name) // no access if not organization member - user5 := models.AssertExistsAndLoadBean(t, &models.User{ID: 5}).(*models.User) + user5 := db.AssertExistsAndLoadBean(t, &models.User{ID: 5}).(*models.User) session = loginUser(t, user5.Name) csrf = GetCSRF(t, session, "/"+org.Name) req = NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "team") diff --git a/integrations/api_team_user_test.go b/integrations/api_team_user_test.go index 5a8fba512ff3b..747227269cccd 100644 --- a/integrations/api_team_user_test.go +++ b/integrations/api_team_user_test.go @@ -10,6 +10,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -29,7 +30,7 @@ func TestAPITeamUser(t *testing.T) { var user2 *api.User DecodeJSON(t, resp, &user2) user2.Created = user2.Created.In(time.Local) - user := models.AssertExistsAndLoadBean(t, &models.User{Name: "user2"}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{Name: "user2"}).(*models.User) expectedUser := convert.ToUser(user, user) diff --git a/integrations/api_token_test.go b/integrations/api_token_test.go index 464b7ba38e2f8..1a6d53547bfc7 100644 --- a/integrations/api_token_test.go +++ b/integrations/api_token_test.go @@ -9,13 +9,14 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" ) // TestAPICreateAndDeleteToken tests that token that was just created can be deleted func TestAPICreateAndDeleteToken(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) req := NewRequestWithJSON(t, "POST", "/api/v1/users/user1/tokens", map[string]string{ "name": "test-key-1", @@ -25,7 +26,7 @@ func TestAPICreateAndDeleteToken(t *testing.T) { var newAccessToken api.AccessToken DecodeJSON(t, resp, &newAccessToken) - models.AssertExistsAndLoadBean(t, &models.AccessToken{ + db.AssertExistsAndLoadBean(t, &models.AccessToken{ ID: newAccessToken.ID, Name: newAccessToken.Name, Token: newAccessToken.Token, @@ -36,7 +37,7 @@ func TestAPICreateAndDeleteToken(t *testing.T) { req = AddBasicAuthHeader(req, user.Name) MakeRequest(t, req, http.StatusNoContent) - models.AssertNotExistsBean(t, &models.AccessToken{ID: newAccessToken.ID}) + db.AssertNotExistsBean(t, &models.AccessToken{ID: newAccessToken.ID}) req = NewRequestWithJSON(t, "POST", "/api/v1/users/user1/tokens", map[string]string{ "name": "test-key-2", @@ -49,15 +50,15 @@ func TestAPICreateAndDeleteToken(t *testing.T) { req = AddBasicAuthHeader(req, user.Name) MakeRequest(t, req, http.StatusNoContent) - models.AssertNotExistsBean(t, &models.AccessToken{ID: newAccessToken.ID}) + db.AssertNotExistsBean(t, &models.AccessToken{ID: newAccessToken.ID}) } // TestAPIDeleteMissingToken ensures that error is thrown when token not found func TestAPIDeleteMissingToken(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) - req := NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", models.NonexistentID) + req := NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", db.NonexistentID) req = AddBasicAuthHeader(req, user.Name) MakeRequest(t, req, http.StatusNotFound) } diff --git a/integrations/api_user_orgs_test.go b/integrations/api_user_orgs_test.go index c72ee76098d92..36b11335c054c 100644 --- a/integrations/api_user_orgs_test.go +++ b/integrations/api_user_orgs_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -24,7 +25,7 @@ func TestUserOrgs(t *testing.T) { orgs := getUserOrgs(t, adminUsername, normalUsername) - user3 := models.AssertExistsAndLoadBean(t, &models.User{Name: "user3"}).(*models.User) + user3 := db.AssertExistsAndLoadBean(t, &models.User{Name: "user3"}).(*models.User) assert.Equal(t, []*api.Organization{ { @@ -80,7 +81,7 @@ func TestMyOrgs(t *testing.T) { resp = session.MakeRequest(t, req, http.StatusOK) var orgs []*api.Organization DecodeJSON(t, resp, &orgs) - user3 := models.AssertExistsAndLoadBean(t, &models.User{Name: "user3"}).(*models.User) + user3 := db.AssertExistsAndLoadBean(t, &models.User{Name: "user3"}).(*models.User) assert.Equal(t, []*api.Organization{ { diff --git a/integrations/api_user_search_test.go b/integrations/api_user_search_test.go index f7349827e5800..d6144d9e87cb2 100644 --- a/integrations/api_user_search_test.go +++ b/integrations/api_user_search_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -51,7 +52,7 @@ func TestAPIUserSearchNotLoggedIn(t *testing.T) { var modelUser *models.User for _, user := range results.Data { assert.Contains(t, user.UserName, query) - modelUser = models.AssertExistsAndLoadBean(t, &models.User{ID: user.ID}).(*models.User) + modelUser = db.AssertExistsAndLoadBean(t, &models.User{ID: user.ID}).(*models.User) if modelUser.KeepEmailPrivate { assert.EqualValues(t, fmt.Sprintf("%s@%s", modelUser.LowerName, setting.Service.NoReplyAddress), user.Email) } else { diff --git a/integrations/benchmarks_test.go b/integrations/benchmarks_test.go index 841ecbf7eee39..5df0d7b749542 100644 --- a/integrations/benchmarks_test.go +++ b/integrations/benchmarks_test.go @@ -11,6 +11,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" ) @@ -32,7 +33,7 @@ func BenchmarkRepoBranchCommit(b *testing.B) { for _, repoID := range samples { b.StopTimer() - repo := models.AssertExistsAndLoadBean(b, &models.Repository{ID: repoID}).(*models.Repository) + repo := db.AssertExistsAndLoadBean(b, &models.Repository{ID: repoID}).(*models.Repository) b.StartTimer() b.Run(repo.Name, func(b *testing.B) { session := loginUser(b, "user2") diff --git a/integrations/change_default_branch_test.go b/integrations/change_default_branch_test.go index dcd6d9b08b271..d6a666435037c 100644 --- a/integrations/change_default_branch_test.go +++ b/integrations/change_default_branch_test.go @@ -10,12 +10,13 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) func TestChangeDefaultBranch(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) session := loginUser(t, owner.Name) branchesURL := fmt.Sprintf("/%s/%s/settings/branches", owner.Name, repo.Name) diff --git a/integrations/compare_test.go b/integrations/compare_test.go index 08468cbfd6e91..e8efe7cd09420 100644 --- a/integrations/compare_test.go +++ b/integrations/compare_test.go @@ -6,6 +6,7 @@ package integrations import ( "net/http" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -21,4 +22,8 @@ func TestCompareTag(t *testing.T) { selection := htmlDoc.doc.Find(".choose.branch .filter.dropdown") // A dropdown for both base and head. assert.Lenf(t, selection.Nodes, 2, "The template has changed") + + req = NewRequest(t, "GET", "/user2/repo1/compare/invalid") + resp = session.MakeRequest(t, req, http.StatusNotFound) + assert.False(t, strings.Contains(resp.Body.String(), "/assets/img/500.png"), "expect 404 page not 500") } diff --git a/integrations/create_no_session_test.go b/integrations/create_no_session_test.go index d6b363426821b..a76ff1eaafb3d 100644 --- a/integrations/create_no_session_test.go +++ b/integrations/create_no_session_test.go @@ -5,7 +5,6 @@ package integrations import ( - "io/ioutil" "net/http" "net/http/httptest" "os" @@ -69,7 +68,7 @@ func TestSessionFileCreation(t *testing.T) { config.Provider = "file" // Now create a temporaryDirectory - tmpDir, err := ioutil.TempDir("", "sessions") + tmpDir, err := os.MkdirTemp("", "sessions") assert.NoError(t, err) defer func() { if _, err := os.Stat(tmpDir); !os.IsNotExist(err) { diff --git a/integrations/delete_user_test.go b/integrations/delete_user_test.go index 86be6f22ad78b..f529734e1983b 100644 --- a/integrations/delete_user_test.go +++ b/integrations/delete_user_test.go @@ -10,18 +10,19 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) func assertUserDeleted(t *testing.T, userID int64) { - models.AssertNotExistsBean(t, &models.User{ID: userID}) - models.AssertNotExistsBean(t, &models.Follow{UserID: userID}) - models.AssertNotExistsBean(t, &models.Follow{FollowID: userID}) - models.AssertNotExistsBean(t, &models.Repository{OwnerID: userID}) - models.AssertNotExistsBean(t, &models.Access{UserID: userID}) - models.AssertNotExistsBean(t, &models.OrgUser{UID: userID}) - models.AssertNotExistsBean(t, &models.IssueUser{UID: userID}) - models.AssertNotExistsBean(t, &models.TeamUser{UID: userID}) - models.AssertNotExistsBean(t, &models.Star{UID: userID}) + db.AssertNotExistsBean(t, &models.User{ID: userID}) + db.AssertNotExistsBean(t, &models.Follow{UserID: userID}) + db.AssertNotExistsBean(t, &models.Follow{FollowID: userID}) + db.AssertNotExistsBean(t, &models.Repository{OwnerID: userID}) + db.AssertNotExistsBean(t, &models.Access{UserID: userID}) + db.AssertNotExistsBean(t, &models.OrgUser{UID: userID}) + db.AssertNotExistsBean(t, &models.IssueUser{UID: userID}) + db.AssertNotExistsBean(t, &models.TeamUser{UID: userID}) + db.AssertNotExistsBean(t, &models.Star{UID: userID}) } func TestUserDeleteAccount(t *testing.T) { @@ -51,5 +52,5 @@ func TestUserDeleteAccountStillOwnRepos(t *testing.T) { session.MakeRequest(t, req, http.StatusFound) // user should not have been deleted, because the user still owns repos - models.AssertExistsAndLoadBean(t, &models.User{ID: 2}) + db.AssertExistsAndLoadBean(t, &models.User{ID: 2}) } diff --git a/integrations/empty_repo_test.go b/integrations/empty_repo_test.go index 77b8adf686f1a..7346c34b27eba 100644 --- a/integrations/empty_repo_test.go +++ b/integrations/empty_repo_test.go @@ -9,6 +9,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) func TestEmptyRepo(t *testing.T) { @@ -19,8 +20,8 @@ func TestEmptyRepo(t *testing.T) { "commit/1ae57b34ccf7e18373", "graph", } - emptyRepo := models.AssertExistsAndLoadBean(t, &models.Repository{}, models.Cond("is_empty = ?", true)).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: emptyRepo.OwnerID}).(*models.User) + emptyRepo := db.AssertExistsAndLoadBean(t, &models.Repository{}, db.Cond("is_empty = ?", true)).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: emptyRepo.OwnerID}).(*models.User) for _, subpath := range subpaths { req := NewRequestf(t, "GET", "/%s/%s/%s", owner.Name, emptyRepo.Name, subpath) MakeRequest(t, req, http.StatusNotFound) diff --git a/integrations/eventsource_test.go b/integrations/eventsource_test.go index caaf8c2cefff8..17c5ff8ab439f 100644 --- a/integrations/eventsource_test.go +++ b/integrations/eventsource_test.go @@ -11,6 +11,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/eventsource" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -49,9 +50,9 @@ func TestEventSourceManagerRun(t *testing.T) { } } - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - thread5 := models.AssertExistsAndLoadBean(t, &models.Notification{ID: 5}).(*models.Notification) + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo1 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + thread5 := db.AssertExistsAndLoadBean(t, &models.Notification{ID: 5}).(*models.Notification) assert.NoError(t, thread5.LoadAttributes()) session := loginUser(t, user2.Name) token := getTokenForLoggedInUser(t, session) diff --git a/integrations/git_clone_wiki_test.go b/integrations/git_clone_wiki_test.go index 4e48df69f5701..2139ce5fe0dc2 100644 --- a/integrations/git_clone_wiki_test.go +++ b/integrations/git_clone_wiki_test.go @@ -7,8 +7,8 @@ package integrations import ( "context" "fmt" - "io/ioutil" "net/url" + "os" "path/filepath" "testing" @@ -24,7 +24,7 @@ func assertFileExist(t *testing.T, p string) { } func assertFileEqual(t *testing.T, p string, content []byte) { - bs, err := ioutil.ReadFile(p) + bs, err := os.ReadFile(p) assert.NoError(t, err) assert.EqualValues(t, content, bs) } @@ -33,7 +33,7 @@ func TestRepoCloneWiki(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { defer prepareTestEnv(t)() - dstPath, err := ioutil.TempDir("", "clone_wiki") + dstPath, err := os.MkdirTemp("", "clone_wiki") assert.NoError(t, err) r := fmt.Sprintf("%suser2/repo1.wiki.git", u.String()) diff --git a/integrations/git_helper_for_declarative_test.go b/integrations/git_helper_for_declarative_test.go index 5a5b1314c64dd..9da5c05f94ecb 100644 --- a/integrations/git_helper_for_declarative_test.go +++ b/integrations/git_helper_for_declarative_test.go @@ -7,7 +7,6 @@ package integrations import ( "context" "fmt" - "io/ioutil" "net" "net/http" "net/url" @@ -28,7 +27,7 @@ import ( func withKeyFile(t *testing.T, keyname string, callback func(string)) { - tmpDir, err := ioutil.TempDir("", "key-file") + tmpDir, err := os.MkdirTemp("", "key-file") assert.NoError(t, err) defer util.RemoveAll(tmpDir) @@ -39,7 +38,7 @@ func withKeyFile(t *testing.T, keyname string, callback func(string)) { err = ssh.GenKeyPair(keyFile) assert.NoError(t, err) - err = ioutil.WriteFile(path.Join(tmpDir, "ssh"), []byte("#!/bin/bash\n"+ + err = os.WriteFile(path.Join(tmpDir, "ssh"), []byte("#!/bin/bash\n"+ "ssh -o \"UserKnownHostsFile=/dev/null\" -o \"StrictHostKeyChecking=no\" -o \"IdentitiesOnly=yes\" -i \""+keyFile+"\" \"$@\""), 0700) assert.NoError(t, err) @@ -125,7 +124,7 @@ func doGitClone(dstLocalPath string, u *url.URL) func(*testing.T) { func doGitCloneFail(u *url.URL) func(*testing.T) { return func(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "doGitCloneFail") + tmpDir, err := os.MkdirTemp("", "doGitCloneFail") assert.NoError(t, err) defer util.RemoveAll(tmpDir) assert.Error(t, git.Clone(u.String(), tmpDir, git.CloneRepoOptions{})) @@ -139,7 +138,7 @@ func doGitInitTestRepository(dstPath string) func(*testing.T) { return func(t *testing.T) { // Init repository in dstPath assert.NoError(t, git.InitRepository(dstPath, false)) - assert.NoError(t, ioutil.WriteFile(filepath.Join(dstPath, "README.md"), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s", dstPath)), 0644)) + assert.NoError(t, os.WriteFile(filepath.Join(dstPath, "README.md"), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s", dstPath)), 0644)) assert.NoError(t, git.AddChanges(dstPath, true)) signature := git.Signature{ Email: "test@example.com", diff --git a/integrations/git_smart_http_test.go b/integrations/git_smart_http_test.go index 9a4e3689c1ce9..c049f71c303dc 100644 --- a/integrations/git_smart_http_test.go +++ b/integrations/git_smart_http_test.go @@ -5,7 +5,7 @@ package integrations import ( - "io/ioutil" + "io" "net/http" "net/url" "testing" @@ -62,7 +62,7 @@ func testGitSmartHTTP(t *testing.T, u *url.URL) { assert.NoError(t, err) defer resp.Body.Close() assert.EqualValues(t, kase.code, resp.StatusCode) - _, err = ioutil.ReadAll(resp.Body) + _, err = io.ReadAll(resp.Body) assert.NoError(t, err) }) } diff --git a/integrations/git_test.go b/integrations/git_test.go index 38d7b29b2b533..9a264c9448e0a 100644 --- a/integrations/git_test.go +++ b/integrations/git_test.go @@ -7,10 +7,10 @@ package integrations import ( "encoding/hex" "fmt" - "io/ioutil" "math/rand" "net/http" "net/url" + "os" "path" "path/filepath" "strconv" @@ -18,6 +18,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/setting" @@ -51,7 +52,7 @@ func testGit(t *testing.T, u *url.URL) { httpContext.Reponame = "repo-tmp-17" forkedUserCtx.Reponame = httpContext.Reponame - dstPath, err := ioutil.TempDir("", httpContext.Reponame) + dstPath, err := os.MkdirTemp("", httpContext.Reponame) assert.NoError(t, err) defer util.RemoveAll(dstPath) @@ -101,7 +102,7 @@ func testGit(t *testing.T, u *url.URL) { sshURL := createSSHUrl(sshContext.GitPath(), u) //Setup clone folder - dstPath, err := ioutil.TempDir("", sshContext.Reponame) + dstPath, err := os.MkdirTemp("", sshContext.Reponame) assert.NoError(t, err) defer util.RemoveAll(dstPath) @@ -127,7 +128,7 @@ func testGit(t *testing.T, u *url.URL) { } func ensureAnonymousClone(t *testing.T, u *url.URL) { - dstLocalPath, err := ioutil.TempDir("", "repo1") + dstLocalPath, err := os.MkdirTemp("", "repo1") assert.NoError(t, err) defer util.RemoveAll(dstLocalPath) t.Run("CloneAnonymous", doGitClone(dstLocalPath, u)) @@ -310,7 +311,7 @@ func generateCommitWithNewData(size int, repoPath, email, fullName, prefix strin buffer := make([]byte, bufSize) - tmpFile, err := ioutil.TempFile(repoPath, prefix) + tmpFile, err := os.CreateTemp(repoPath, prefix) if err != nil { return "", err } @@ -365,7 +366,7 @@ func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *tes t.Run("PushProtectedBranch", doGitPushTestRepository(dstPath, "origin", "protected")) ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame) - t.Run("ProtectProtectedBranchNoWhitelist", doProtectBranch(ctx, "protected", "")) + t.Run("ProtectProtectedBranchNoWhitelist", doProtectBranch(ctx, "protected", "", "")) t.Run("GenerateCommit", func(t *testing.T) { _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-") assert.NoError(t, err) @@ -391,7 +392,15 @@ func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *tes t.Run("MergePR2", doAPIMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr2.Index)) t.Run("MergePR", doAPIMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) t.Run("PullProtected", doGitPull(dstPath, "origin", "protected")) - t.Run("ProtectProtectedBranchWhitelist", doProtectBranch(ctx, "protected", baseCtx.Username)) + + t.Run("ProtectProtectedBranchUnprotectedFilePaths", doProtectBranch(ctx, "protected", "", "unprotected-file-*")) + t.Run("GenerateCommit", func(t *testing.T) { + _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "unprotected-file-") + assert.NoError(t, err) + }) + t.Run("PushUnprotectedFilesToProtectedBranch", doGitPushTestRepository(dstPath, "origin", "protected")) + + t.Run("ProtectProtectedBranchWhitelist", doProtectBranch(ctx, "protected", baseCtx.Username, "")) t.Run("CheckoutMaster", doGitCheckoutBranch(dstPath, "master")) t.Run("CreateBranchForced", doGitCreateBranch(dstPath, "toforce")) @@ -406,7 +415,7 @@ func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *tes } } -func doProtectBranch(ctx APITestContext, branch string, userToWhitelist string) func(t *testing.T) { +func doProtectBranch(ctx APITestContext, branch string, userToWhitelist string, unprotectedFilePatterns string) func(t *testing.T) { // We are going to just use the owner to set the protection. return func(t *testing.T) { csrf := GetCSRF(t, ctx.Session, fmt.Sprintf("/%s/%s/settings/branches", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame))) @@ -414,8 +423,9 @@ func doProtectBranch(ctx APITestContext, branch string, userToWhitelist string) if userToWhitelist == "" { // Change branch to protected req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/%s", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), url.PathEscape(branch)), map[string]string{ - "_csrf": csrf, - "protected": "on", + "_csrf": csrf, + "protected": "on", + "unprotected_file_patterns": unprotectedFilePatterns, }) ctx.Session.MakeRequest(t, req, http.StatusFound) } else { @@ -423,11 +433,12 @@ func doProtectBranch(ctx APITestContext, branch string, userToWhitelist string) assert.NoError(t, err) // Change branch to protected req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/%s", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), url.PathEscape(branch)), map[string]string{ - "_csrf": csrf, - "protected": "on", - "enable_push": "whitelist", - "enable_whitelist": "on", - "whitelist_users": strconv.FormatInt(user.ID, 10), + "_csrf": csrf, + "protected": "on", + "enable_push": "whitelist", + "enable_whitelist": "on", + "whitelist_users": strconv.FormatInt(user.ID, 10), + "unprotected_file_patterns": unprotectedFilePatterns, }) ctx.Session.MakeRequest(t, req, http.StatusFound) } @@ -547,7 +558,7 @@ func doPushCreate(ctx APITestContext, u *url.URL) func(t *testing.T) { u.Path = ctx.GitPath() // Create a temporary directory - tmpDir, err := ioutil.TempDir("", ctx.Reponame) + tmpDir, err := os.MkdirTemp("", ctx.Reponame) assert.NoError(t, err) defer util.RemoveAll(tmpDir) @@ -620,12 +631,12 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, baseBranch, headB return } - pullNum := models.GetCount(t, &models.PullRequest{}) + pullNum := db.GetCount(t, &models.PullRequest{}) t.Run("CreateHeadBranch", doGitCreateBranch(dstPath, headBranch)) t.Run("AddCommit", func(t *testing.T) { - err := ioutil.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content"), 0666) + err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content"), 0666) if !assert.NoError(t, err) { return } @@ -656,8 +667,8 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, baseBranch, headB if !assert.NoError(t, err) { return } - models.AssertCount(t, &models.PullRequest{}, pullNum+1) - pr1 = models.AssertExistsAndLoadBean(t, &models.PullRequest{ + db.AssertCount(t, &models.PullRequest{}, pullNum+1) + pr1 = db.AssertExistsAndLoadBean(t, &models.PullRequest{ HeadRepoID: repo.ID, Flow: models.PullRequestFlowAGit, }).(*models.PullRequest) @@ -677,8 +688,8 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, baseBranch, headB if !assert.NoError(t, err) { return } - models.AssertCount(t, &models.PullRequest{}, pullNum+2) - pr2 = models.AssertExistsAndLoadBean(t, &models.PullRequest{ + db.AssertCount(t, &models.PullRequest{}, pullNum+2) + pr2 = db.AssertExistsAndLoadBean(t, &models.PullRequest{ HeadRepoID: repo.ID, Index: pr1.Index + 1, Flow: models.PullRequestFlowAGit, @@ -699,7 +710,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, baseBranch, headB } t.Run("AddCommit2", func(t *testing.T) { - err := ioutil.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content \n ## test content 2"), 0666) + err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content \n ## test content 2"), 0666) if !assert.NoError(t, err) { return } @@ -730,7 +741,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, baseBranch, headB if !assert.NoError(t, err) { return } - models.AssertCount(t, &models.PullRequest{}, pullNum+2) + db.AssertCount(t, &models.PullRequest{}, pullNum+2) prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t) if !assert.NoError(t, err) { return @@ -742,7 +753,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, baseBranch, headB if !assert.NoError(t, err) { return } - models.AssertCount(t, &models.PullRequest{}, pullNum+2) + db.AssertCount(t, &models.PullRequest{}, pullNum+2) prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t) if !assert.NoError(t, err) { return diff --git a/integrations/gpg_git_test.go b/integrations/gpg_git_test.go index 8c78faf7d23c1..2cfb883fd9332 100644 --- a/integrations/gpg_git_test.go +++ b/integrations/gpg_git_test.go @@ -7,12 +7,12 @@ package integrations import ( "encoding/base64" "fmt" - "io/ioutil" "net/url" "os" "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -27,7 +27,7 @@ func TestGPGGit(t *testing.T) { username := "user2" // OK Set a new GPG home - tmpDir, err := ioutil.TempDir("", "temp-gpg") + tmpDir, err := os.MkdirTemp("", "temp-gpg") assert.NoError(t, err) defer util.RemoveAll(tmpDir) @@ -60,7 +60,7 @@ func TestGPGGit(t *testing.T) { setting.Repository.Signing.SigningKey = rootKeyID setting.Repository.Signing.SigningName = "gitea" setting.Repository.Signing.SigningEmail = "gitea@fake.local" - user := models.AssertExistsAndLoadBean(t, &models.User{Name: username}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{Name: username}).(*models.User) setting.Repository.Signing.InitialCommit = []string{"never"} setting.Repository.Signing.CRUDActions = []string{"never"} diff --git a/integrations/integration_test.go b/integrations/integration_test.go index 1003f136ddfd9..1429893270b0e 100644 --- a/integrations/integration_test.go +++ b/integrations/integration_test.go @@ -25,6 +25,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/graceful" @@ -111,8 +112,10 @@ func TestMain(m *testing.M) { } } - err := models.InitFixtures( - path.Join(filepath.Dir(setting.AppPath), "models/fixtures/"), + err := db.InitFixtures( + db.FixturesOptions{ + Dir: filepath.Join(filepath.Dir(setting.AppPath), "models/fixtures/"), + }, ) if err != nil { fmt.Printf("Error initializing test database: %v\n", err) @@ -247,7 +250,7 @@ func prepareTestEnv(t testing.TB, skip ...int) func() { ourSkip += skip[0] } deferFn := PrintCurrentTest(t, ourSkip) - assert.NoError(t, models.LoadFixtures()) + assert.NoError(t, db.LoadFixtures()) assert.NoError(t, util.RemoveAll(setting.RepoRootPath)) assert.NoError(t, util.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath)) @@ -524,7 +527,7 @@ func GetCSRF(t testing.TB, session *TestSession, urlStr string) string { // within a single test this is required func resetFixtures(t *testing.T) { assert.NoError(t, queue.GetManager().FlushAll(context.Background(), -1)) - assert.NoError(t, models.LoadFixtures()) + assert.NoError(t, db.LoadFixtures()) assert.NoError(t, util.RemoveAll(setting.RepoRootPath)) assert.NoError(t, util.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath)) } diff --git a/integrations/issue_test.go b/integrations/issue_test.go index 9b1447bd8de92..132f0822abc2d 100644 --- a/integrations/issue_test.go +++ b/integrations/issue_test.go @@ -14,6 +14,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/indexer/issues" "code.gitea.io/gitea/modules/references" "code.gitea.io/gitea/modules/setting" @@ -35,7 +36,7 @@ func getIssue(t *testing.T, repoID int64, issueSelection *goquery.Selection) *mo indexStr := href[strings.LastIndexByte(href, '/')+1:] index, err := strconv.Atoi(indexStr) assert.NoError(t, err, "Invalid issue href: %s", href) - return models.AssertExistsAndLoadBean(t, &models.Issue{RepoID: repoID, Index: int64(index)}).(*models.Issue) + return db.AssertExistsAndLoadBean(t, &models.Issue{RepoID: repoID, Index: int64(index)}).(*models.Issue) } func assertMatch(t testing.TB, issue *models.Issue, keyword string) { @@ -60,8 +61,8 @@ func TestNoLoginViewIssues(t *testing.T) { func TestViewIssuesSortByType(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) session := loginUser(t, user.Name) req := NewRequest(t, "GET", repo.RelLink()+"/issues?type=created_by") @@ -69,10 +70,10 @@ func TestViewIssuesSortByType(t *testing.T) { htmlDoc := NewHTMLParser(t, resp.Body) issuesSelection := getIssuesSelection(t, htmlDoc) - expectedNumIssues := models.GetCount(t, + expectedNumIssues := db.GetCount(t, &models.Issue{RepoID: repo.ID, PosterID: user.ID}, - models.Cond("is_closed=?", false), - models.Cond("is_pull=?", false), + db.Cond("is_closed=?", false), + db.Cond("is_pull=?", false), ) if expectedNumIssues > setting.UI.IssuePagingNum { expectedNumIssues = setting.UI.IssuePagingNum @@ -88,8 +89,8 @@ func TestViewIssuesSortByType(t *testing.T) { func TestViewIssuesKeyword(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - issue := models.AssertExistsAndLoadBean(t, &models.Issue{ + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + issue := db.AssertExistsAndLoadBean(t, &models.Issue{ RepoID: repo.ID, Index: 1, }).(*models.Issue) @@ -235,7 +236,7 @@ func TestIssueCrossReference(t *testing.T) { // Ref from issue title issueRefURL, issueRef := testIssueWithBean(t, "user2", 1, fmt.Sprintf("Title ref #%d", issueBase.Index), "Description") - models.AssertExistsAndLoadBean(t, &models.Comment{ + db.AssertExistsAndLoadBean(t, &models.Comment{ IssueID: issueBase.ID, RefRepoID: 1, RefIssueID: issueRef.ID, @@ -245,7 +246,7 @@ func TestIssueCrossReference(t *testing.T) { // Edit title, neuter ref testIssueChangeInfo(t, "user2", issueRefURL, "title", "Title no ref") - models.AssertExistsAndLoadBean(t, &models.Comment{ + db.AssertExistsAndLoadBean(t, &models.Comment{ IssueID: issueBase.ID, RefRepoID: 1, RefIssueID: issueRef.ID, @@ -255,7 +256,7 @@ func TestIssueCrossReference(t *testing.T) { // Ref from issue content issueRefURL, issueRef = testIssueWithBean(t, "user2", 1, "TitleXRef", fmt.Sprintf("Description ref #%d", issueBase.Index)) - models.AssertExistsAndLoadBean(t, &models.Comment{ + db.AssertExistsAndLoadBean(t, &models.Comment{ IssueID: issueBase.ID, RefRepoID: 1, RefIssueID: issueRef.ID, @@ -265,7 +266,7 @@ func TestIssueCrossReference(t *testing.T) { // Edit content, neuter ref testIssueChangeInfo(t, "user2", issueRefURL, "content", "Description no ref") - models.AssertExistsAndLoadBean(t, &models.Comment{ + db.AssertExistsAndLoadBean(t, &models.Comment{ IssueID: issueBase.ID, RefRepoID: 1, RefIssueID: issueRef.ID, @@ -283,11 +284,11 @@ func TestIssueCrossReference(t *testing.T) { RefCommentID: commentID, RefIsPull: false, RefAction: references.XRefActionNone} - models.AssertExistsAndLoadBean(t, comment) + db.AssertExistsAndLoadBean(t, comment) // Ref from a different repository issueRefURL, issueRef = testIssueWithBean(t, "user12", 10, "TitleXRef", fmt.Sprintf("Description ref user2/repo1#%d", issueBase.Index)) - models.AssertExistsAndLoadBean(t, &models.Comment{ + db.AssertExistsAndLoadBean(t, &models.Comment{ IssueID: issueBase.ID, RefRepoID: 10, RefIssueID: issueRef.ID, @@ -303,7 +304,7 @@ func testIssueWithBean(t *testing.T, user string, repoID int64, title, content s index, err := strconv.Atoi(indexStr) assert.NoError(t, err, "Invalid issue href: %s", issueURL) issue := &models.Issue{RepoID: repoID, Index: int64(index)} - models.AssertExistsAndLoadBean(t, issue) + db.AssertExistsAndLoadBean(t, issue) return issueURL, issue } diff --git a/integrations/lfs_getobject_test.go b/integrations/lfs_getobject_test.go index 063f72b25ce24..d179428c6db21 100644 --- a/integrations/lfs_getobject_test.go +++ b/integrations/lfs_getobject_test.go @@ -7,7 +7,7 @@ package integrations import ( "archive/zip" "bytes" - "io/ioutil" + "io" "net/http" "net/http/httptest" "testing" @@ -74,7 +74,7 @@ func checkResponseTestContentEncoding(t *testing.T, content *[]byte, resp *httpt assert.Contains(t, contentEncoding, "gzip") gzippReader, err := gzipp.NewReader(resp.Body) assert.NoError(t, err) - result, err := ioutil.ReadAll(gzippReader) + result, err := io.ReadAll(gzippReader) assert.NoError(t, err) assert.Equal(t, *content, result) } diff --git a/integrations/lfs_local_endpoint_test.go b/integrations/lfs_local_endpoint_test.go index eda418c429493..7e0166b381892 100644 --- a/integrations/lfs_local_endpoint_test.go +++ b/integrations/lfs_local_endpoint_test.go @@ -6,7 +6,6 @@ package integrations import ( "fmt" - "io/ioutil" "net/url" "os" "path/filepath" @@ -25,14 +24,14 @@ func str2url(raw string) *url.URL { func TestDetermineLocalEndpoint(t *testing.T) { defer prepareTestEnv(t)() - root, _ := ioutil.TempDir("", "lfs_test") + root, _ := os.MkdirTemp("", "lfs_test") defer os.RemoveAll(root) - rootdotgit, _ := ioutil.TempDir("", "lfs_test") + rootdotgit, _ := os.MkdirTemp("", "lfs_test") defer os.RemoveAll(rootdotgit) os.Mkdir(filepath.Join(rootdotgit, ".git"), 0700) - lfsroot, _ := ioutil.TempDir("", "lfs_test") + lfsroot, _ := os.MkdirTemp("", "lfs_test") defer os.RemoveAll(lfsroot) // Test cases diff --git a/integrations/migrate_test.go b/integrations/migrate_test.go index b0395fbc3d816..c0d5d4fc75a91 100644 --- a/integrations/migrate_test.go +++ b/integrations/migrate_test.go @@ -5,11 +5,11 @@ package integrations import ( - "io/ioutil" "os" "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/migrations" "code.gitea.io/gitea/modules/setting" @@ -17,21 +17,21 @@ import ( ) func TestMigrateLocalPath(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - adminUser := models.AssertExistsAndLoadBean(t, &models.User{Name: "user1"}).(*models.User) + adminUser := db.AssertExistsAndLoadBean(t, &models.User{Name: "user1"}).(*models.User) old := setting.ImportLocalPaths setting.ImportLocalPaths = true - lowercasePath, err := ioutil.TempDir("", "lowercase") // may not be lowercase because TempDir creates a random directory name which may be mixedcase + lowercasePath, err := os.MkdirTemp("", "lowercase") // may not be lowercase because MkdirTemp creates a random directory name which may be mixedcase assert.NoError(t, err) defer os.RemoveAll(lowercasePath) err = migrations.IsMigrateURLAllowed(lowercasePath, adminUser) assert.NoError(t, err, "case lowercase path") - mixedcasePath, err := ioutil.TempDir("", "mIxeDCaSe") + mixedcasePath, err := os.MkdirTemp("", "mIxeDCaSe") assert.NoError(t, err) defer os.RemoveAll(mixedcasePath) diff --git a/integrations/migration-test/migration_test.go b/integrations/migration-test/migration_test.go index 209ff5a058f43..43dc2a8348d2b 100644 --- a/integrations/migration-test/migration_test.go +++ b/integrations/migration-test/migration_test.go @@ -9,7 +9,7 @@ import ( "context" "database/sql" "fmt" - "io/ioutil" + "io" "os" "path" "path/filepath" @@ -19,7 +19,7 @@ import ( "testing" "code.gitea.io/gitea/integrations" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/migrations" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/charset" @@ -113,7 +113,7 @@ func readSQLFromFile(version string) (string, error) { } defer gr.Close() - bytes, err := ioutil.ReadAll(gr) + bytes, err := io.ReadAll(gr) if err != nil { return "", err } @@ -256,13 +256,13 @@ func doMigrationTest(t *testing.T, version string) { setting.NewXORMLogService(false) - err := models.NewEngine(context.Background(), wrappedMigrate) + err := db.NewEngine(context.Background(), wrappedMigrate) assert.NoError(t, err) currentEngine.Close() - beans, _ := models.NamesToBean() + beans, _ := db.NamesToBean() - err = models.NewEngine(context.Background(), func(x *xorm.Engine) error { + err = db.NewEngine(context.Background(), func(x *xorm.Engine) error { currentEngine = x return migrations.RecreateTables(beans...)(x) }) @@ -270,7 +270,7 @@ func doMigrationTest(t *testing.T, version string) { currentEngine.Close() // We do this a second time to ensure that there is not a problem with retained indices - err = models.NewEngine(context.Background(), func(x *xorm.Engine) error { + err = db.NewEngine(context.Background(), func(x *xorm.Engine) error { currentEngine = x return migrations.RecreateTables(beans...)(x) }) diff --git a/integrations/mirror_pull_test.go b/integrations/mirror_pull_test.go index 3908f355575db..f396aac4b445b 100644 --- a/integrations/mirror_pull_test.go +++ b/integrations/mirror_pull_test.go @@ -9,6 +9,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" migration "code.gitea.io/gitea/modules/migrations/base" "code.gitea.io/gitea/modules/repository" @@ -21,8 +22,8 @@ import ( func TestMirrorPull(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) repoPath := models.RepoPath(user.Name, repo.Name) opts := migration.MigrateOptions{ diff --git a/integrations/mirror_push_test.go b/integrations/mirror_push_test.go index 3191ef770444b..3d2966cfdcd44 100644 --- a/integrations/mirror_push_test.go +++ b/integrations/mirror_push_test.go @@ -12,6 +12,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" @@ -29,8 +30,8 @@ func testMirrorPush(t *testing.T, u *url.URL) { setting.Migrations.AllowLocalNetworks = true - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - srcRepo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + srcRepo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) mirrorRepo, err := repository.CreateRepository(user, user, models.CreateRepoOptions{ Name: "test-push-mirror", diff --git a/integrations/oauth_test.go b/integrations/oauth_test.go index c97db777be1ac..c36aab652b36f 100644 --- a/integrations/oauth_test.go +++ b/integrations/oauth_test.go @@ -6,7 +6,7 @@ package integrations import ( "bytes" - "io/ioutil" + "io" "testing" "code.gitea.io/gitea/modules/json" @@ -240,20 +240,20 @@ func TestRefreshTokenInvalidation(t *testing.T) { "refresh_token": parsed.RefreshToken, }) - bs, err := ioutil.ReadAll(refreshReq.Body) + bs, err := io.ReadAll(refreshReq.Body) assert.NoError(t, err) - refreshReq.Body = ioutil.NopCloser(bytes.NewReader(bs)) + refreshReq.Body = io.NopCloser(bytes.NewReader(bs)) MakeRequest(t, refreshReq, 200) - refreshReq.Body = ioutil.NopCloser(bytes.NewReader(bs)) + refreshReq.Body = io.NopCloser(bytes.NewReader(bs)) MakeRequest(t, refreshReq, 200) // test with invalidation setting.OAuth2.InvalidateRefreshTokens = true - refreshReq.Body = ioutil.NopCloser(bytes.NewReader(bs)) + refreshReq.Body = io.NopCloser(bytes.NewReader(bs)) MakeRequest(t, refreshReq, 200) - refreshReq.Body = ioutil.NopCloser(bytes.NewReader(bs)) + refreshReq.Body = io.NopCloser(bytes.NewReader(bs)) MakeRequest(t, refreshReq, 400) } diff --git a/integrations/org_count_test.go b/integrations/org_count_test.go index 20917dc17e0c5..bd64139b2fc2f 100644 --- a/integrations/org_count_test.go +++ b/integrations/org_count_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" ) @@ -110,7 +111,7 @@ func doCheckOrgCounts(username string, orgCounts map[string]int, strict bool, ca } return func(t *testing.T) { - user := models.AssertExistsAndLoadBean(t, &models.User{ + user := db.AssertExistsAndLoadBean(t, &models.User{ Name: username, }).(*models.User) diff --git a/integrations/privateactivity_test.go b/integrations/privateactivity_test.go index 23c6a7e8ccf4e..e7b618abd6aca 100644 --- a/integrations/privateactivity_test.go +++ b/integrations/privateactivity_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -24,8 +25,8 @@ const privateActivityTestOtherUser = "user4" // activity helpers func testPrivateActivityDoSomethingForActionEntries(t *testing.T) { - repoBefore := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repoBefore.OwnerID}).(*models.User) + repoBefore := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repoBefore.OwnerID}).(*models.User) session := loginUser(t, privateActivityTestUser) token := getTokenForLoggedInUser(t, session) diff --git a/integrations/pull_merge_test.go b/integrations/pull_merge_test.go index b4a3397a45f56..867817c2c25c7 100644 --- a/integrations/pull_merge_test.go +++ b/integrations/pull_merge_test.go @@ -17,6 +17,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" @@ -219,15 +220,15 @@ func TestCantMergeConflict(t *testing.T) { session.MakeRequest(t, req, 201) // Now this PR will be marked conflict - or at least a race will do - so drop down to pure code at this point... - user1 := models.AssertExistsAndLoadBean(t, &models.User{ + user1 := db.AssertExistsAndLoadBean(t, &models.User{ Name: "user1", }).(*models.User) - repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ + repo1 := db.AssertExistsAndLoadBean(t, &models.Repository{ OwnerID: user1.ID, Name: "repo1", }).(*models.Repository) - pr := models.AssertExistsAndLoadBean(t, &models.PullRequest{ + pr := db.AssertExistsAndLoadBean(t, &models.PullRequest{ HeadRepoID: repo1.ID, BaseRepoID: repo1.ID, HeadBranch: "conflict", @@ -256,10 +257,10 @@ func TestCantMergeUnrelated(t *testing.T) { // Now we want to create a commit on a branch that is totally unrelated to our current head // Drop down to pure code at this point - user1 := models.AssertExistsAndLoadBean(t, &models.User{ + user1 := db.AssertExistsAndLoadBean(t, &models.User{ Name: "user1", }).(*models.User) - repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ + repo1 := db.AssertExistsAndLoadBean(t, &models.Repository{ OwnerID: user1.ID, Name: "repo1", }).(*models.Repository) @@ -318,7 +319,7 @@ func TestCantMergeUnrelated(t *testing.T) { // Now this PR could be marked conflict - or at least a race may occur - so drop down to pure code at this point... gitRepo, err := git.OpenRepository(path) assert.NoError(t, err) - pr := models.AssertExistsAndLoadBean(t, &models.PullRequest{ + pr := db.AssertExistsAndLoadBean(t, &models.PullRequest{ HeadRepoID: repo1.ID, BaseRepoID: repo1.ID, HeadBranch: "unrelated", diff --git a/integrations/pull_update_test.go b/integrations/pull_update_test.go index 3e3b987b643f1..ea3a4794a6213 100644 --- a/integrations/pull_update_test.go +++ b/integrations/pull_update_test.go @@ -11,6 +11,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/repofiles" repo_module "code.gitea.io/gitea/modules/repository" pull_service "code.gitea.io/gitea/services/pull" @@ -22,8 +23,8 @@ import ( func TestAPIPullUpdate(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { //Create PR to test - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - org26 := models.AssertExistsAndLoadBean(t, &models.User{ID: 26}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + org26 := db.AssertExistsAndLoadBean(t, &models.User{ID: 26}).(*models.User) pr := createOutdatedPR(t, user, org26) //Test GetDiverging @@ -50,8 +51,8 @@ func TestAPIPullUpdate(t *testing.T) { func TestAPIPullUpdateByRebase(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { //Create PR to test - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - org26 := models.AssertExistsAndLoadBean(t, &models.User{ID: 26}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + org26 := db.AssertExistsAndLoadBean(t, &models.User{ID: 26}).(*models.User) pr := createOutdatedPR(t, user, org26) //Test GetDiverging @@ -162,7 +163,7 @@ func createOutdatedPR(t *testing.T, actor, forkOrg *models.User) *models.PullReq err = pull_service.NewPullRequest(baseRepo, pullIssue, nil, nil, pullRequest, nil) assert.NoError(t, err) - issue := models.AssertExistsAndLoadBean(t, &models.Issue{Title: "Test Pull -to-update-"}).(*models.Issue) + issue := db.AssertExistsAndLoadBean(t, &models.Issue{Title: "Test Pull -to-update-"}).(*models.Issue) pr, err := models.GetPullRequestByIssueID(issue.ID) assert.NoError(t, err) diff --git a/integrations/release_test.go b/integrations/release_test.go index 4458387ef75dd..2e9de316997dd 100644 --- a/integrations/release_test.go +++ b/integrations/release_test.go @@ -11,6 +11,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" @@ -133,7 +134,7 @@ func TestCreateReleasePaging(t *testing.T) { func TestViewReleaseListNoLogin(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) link := repo.Link() + "/releases" @@ -159,7 +160,7 @@ func TestViewReleaseListNoLogin(t *testing.T) { func TestViewReleaseListLogin(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) link := repo.Link() + "/releases" @@ -190,7 +191,7 @@ func TestViewReleaseListLogin(t *testing.T) { func TestViewTagsList(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) link := repo.Link() + "/tags" diff --git a/integrations/repo_fork_test.go b/integrations/repo_fork_test.go index d0542c1a72c19..f9a994fd8ef91 100644 --- a/integrations/repo_fork_test.go +++ b/integrations/repo_fork_test.go @@ -11,12 +11,13 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func testRepoFork(t *testing.T, session *TestSession, ownerName, repoName, forkOwnerName, forkRepoName string) *httptest.ResponseRecorder { - forkOwner := models.AssertExistsAndLoadBean(t, &models.User{Name: forkOwnerName}).(*models.User) + forkOwner := db.AssertExistsAndLoadBean(t, &models.User{Name: forkOwnerName}).(*models.User) // Step0: check the existence of the to-fork repo req := NewRequestf(t, "GET", "/%s/%s", forkOwnerName, forkRepoName) diff --git a/integrations/repo_generate_test.go b/integrations/repo_generate_test.go index 1aa91236ea351..7afce7474a764 100644 --- a/integrations/repo_generate_test.go +++ b/integrations/repo_generate_test.go @@ -11,12 +11,13 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func testRepoGenerate(t *testing.T, session *TestSession, templateOwnerName, templateRepoName, generateOwnerName, generateRepoName string) *httptest.ResponseRecorder { - generateOwner := models.AssertExistsAndLoadBean(t, &models.User{Name: generateOwnerName}).(*models.User) + generateOwner := db.AssertExistsAndLoadBean(t, &models.User{Name: generateOwnerName}).(*models.User) // Step0: check the existence of the generated repo req := NewRequestf(t, "GET", "/%s/%s", generateOwnerName, generateRepoName) diff --git a/integrations/repo_tag_test.go b/integrations/repo_tag_test.go index eb3f2b47fb9e2..abbc2c02fcb8a 100644 --- a/integrations/repo_tag_test.go +++ b/integrations/repo_tag_test.go @@ -5,11 +5,12 @@ package integrations import ( - "io/ioutil" "net/url" + "os" "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/release" @@ -20,8 +21,8 @@ import ( func TestCreateNewTagProtected(t *testing.T) { defer prepareTestEnv(t)() - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) t.Run("API", func(t *testing.T) { defer PrintCurrentTest(t)() @@ -54,7 +55,7 @@ func TestCreateNewTagProtected(t *testing.T) { username := "user2" httpContext := NewAPITestContext(t, username, "repo1") - dstPath, err := ioutil.TempDir("", httpContext.Reponame) + dstPath, err := os.MkdirTemp("", httpContext.Reponame) assert.NoError(t, err) defer util.RemoveAll(dstPath) diff --git a/integrations/repo_watch_test.go b/integrations/repo_watch_test.go index d96b014a73618..7ae9f1fe7fa9f 100644 --- a/integrations/repo_watch_test.go +++ b/integrations/repo_watch_test.go @@ -9,6 +9,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" ) @@ -17,8 +18,8 @@ func TestRepoWatch(t *testing.T) { // Test round-trip auto-watch setting.Service.AutoWatchOnChanges = true session := loginUser(t, "user2") - models.AssertNotExistsBean(t, &models.Watch{UserID: 2, RepoID: 3}) + db.AssertNotExistsBean(t, &models.Watch{UserID: 2, RepoID: 3}) testEditFile(t, session, "user3", "repo3", "master", "README.md", "Hello, World (Edited for watch)\n") - models.AssertExistsAndLoadBean(t, &models.Watch{UserID: 2, RepoID: 3, Mode: models.RepoWatchModeAuto}) + db.AssertExistsAndLoadBean(t, &models.Watch{UserID: 2, RepoID: 3, Mode: models.RepoWatchModeAuto}) }) } diff --git a/integrations/repofiles_delete_test.go b/integrations/repofiles_delete_test.go index 1fa316c41edf9..22fef4b03ef60 100644 --- a/integrations/repofiles_delete_test.go +++ b/integrations/repofiles_delete_test.go @@ -9,6 +9,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/repofiles" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" @@ -66,7 +67,7 @@ func TestDeleteRepoFile(t *testing.T) { func testDeleteRepoFile(t *testing.T, u *url.URL) { // setup - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1") ctx.SetParams(":id", "1") test.LoadRepo(t, ctx, 1) @@ -105,7 +106,7 @@ func TestDeleteRepoFileWithoutBranchNames(t *testing.T) { func testDeleteRepoFileWithoutBranchNames(t *testing.T, u *url.URL) { // setup - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1") ctx.SetParams(":id", "1") test.LoadRepo(t, ctx, 1) @@ -135,7 +136,7 @@ func testDeleteRepoFileWithoutBranchNames(t *testing.T, u *url.URL) { func TestDeleteRepoFileErrors(t *testing.T) { // setup - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1") ctx.SetParams(":id", "1") test.LoadRepo(t, ctx, 1) diff --git a/integrations/signin_test.go b/integrations/signin_test.go index 390bf6408bfb2..081f87f0bd518 100644 --- a/integrations/signin_test.go +++ b/integrations/signin_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" "github.com/unknwon/i18n" @@ -33,13 +34,13 @@ func testLoginFailed(t *testing.T, username, password, message string) { func TestSignin(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // add new user with user2's email user.Name = "testuser" user.LowerName = strings.ToLower(user.Name) user.ID = 0 - models.AssertSuccessfulInsert(t, user) + db.AssertSuccessfulInsert(t, user) samples := []struct { username string diff --git a/integrations/signup_test.go b/integrations/signup_test.go index 66ff8ac2d72ff..dccb1f6767b70 100644 --- a/integrations/signup_test.go +++ b/integrations/signup_test.go @@ -11,6 +11,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" "github.com/unknwon/i18n" @@ -52,7 +53,7 @@ func TestSignupAsRestricted(t *testing.T) { req = NewRequest(t, "GET", "/restrictedUser") MakeRequest(t, req, http.StatusOK) - user2 := models.AssertExistsAndLoadBean(t, &models.User{Name: "restrictedUser"}).(*models.User) + user2 := db.AssertExistsAndLoadBean(t, &models.User{Name: "restrictedUser"}).(*models.User) assert.True(t, user2.IsRestricted) } diff --git a/integrations/ssh_key_test.go b/integrations/ssh_key_test.go index 91774f633594c..f9c807413ad4b 100644 --- a/integrations/ssh_key_test.go +++ b/integrations/ssh_key_test.go @@ -6,9 +6,9 @@ package integrations import ( "fmt" - "io/ioutil" "net/http" "net/url" + "os" "path/filepath" "testing" "time" @@ -28,7 +28,7 @@ func doCheckRepositoryEmptyStatus(ctx APITestContext, isEmpty bool) func(*testin func doAddChangesToCheckout(dstPath, filename string) func(*testing.T) { return func(t *testing.T) { - assert.NoError(t, ioutil.WriteFile(filepath.Join(dstPath, filename), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s at time: %v", dstPath, time.Now())), 0644)) + assert.NoError(t, os.WriteFile(filepath.Join(dstPath, filename), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s at time: %v", dstPath, time.Now())), 0644)) assert.NoError(t, git.AddChanges(dstPath, true)) signature := git.Signature{ Email: "test@example.com", @@ -61,7 +61,7 @@ func testPushDeployKeyOnEmptyRepo(t *testing.T, u *url.URL) { t.Run("CreatePushDeployKey", doAPICreateDeployKey(ctx, keyname, keyFile, false)) // Setup the testing repository - dstPath, err := ioutil.TempDir("", "repo-tmp-deploy-key-empty-repo-1") + dstPath, err := os.MkdirTemp("", "repo-tmp-deploy-key-empty-repo-1") assert.NoError(t, err) defer util.RemoveAll(dstPath) @@ -107,7 +107,7 @@ func testKeyOnlyOneType(t *testing.T, u *url.URL) { withKeyFile(t, keyname, func(keyFile string) { var userKeyPublicKeyID int64 t.Run("KeyCanOnlyBeUser", func(t *testing.T) { - dstPath, err := ioutil.TempDir("", ctx.Reponame) + dstPath, err := os.MkdirTemp("", ctx.Reponame) assert.NoError(t, err) defer util.RemoveAll(dstPath) @@ -133,7 +133,7 @@ func testKeyOnlyOneType(t *testing.T, u *url.URL) { }) t.Run("KeyCanBeAnyDeployButNotUserAswell", func(t *testing.T) { - dstPath, err := ioutil.TempDir("", ctx.Reponame) + dstPath, err := os.MkdirTemp("", ctx.Reponame) assert.NoError(t, err) defer util.RemoveAll(dstPath) @@ -151,7 +151,7 @@ func testKeyOnlyOneType(t *testing.T, u *url.URL) { t.Run("FailToPush", doGitPushTestRepositoryFail(dstPath, "origin", "master")) otherSSHURL := createSSHUrl(otherCtx.GitPath(), u) - dstOtherPath, err := ioutil.TempDir("", otherCtx.Reponame) + dstOtherPath, err := os.MkdirTemp("", otherCtx.Reponame) assert.NoError(t, err) defer util.RemoveAll(dstOtherPath) @@ -168,7 +168,7 @@ func testKeyOnlyOneType(t *testing.T, u *url.URL) { t.Run("DeleteRepositoryShouldReleaseKey", func(t *testing.T) { otherSSHURL := createSSHUrl(otherCtx.GitPath(), u) - dstOtherPath, err := ioutil.TempDir("", otherCtx.Reponame) + dstOtherPath, err := os.MkdirTemp("", otherCtx.Reponame) assert.NoError(t, err) defer util.RemoveAll(dstOtherPath) @@ -190,7 +190,7 @@ func testKeyOnlyOneType(t *testing.T, u *url.URL) { userKeyPublicKeyID = publicKey.ID })) - dstPath, err := ioutil.TempDir("", ctx.Reponame) + dstPath, err := os.MkdirTemp("", ctx.Reponame) assert.NoError(t, err) defer util.RemoveAll(dstPath) diff --git a/integrations/user_avatar_test.go b/integrations/user_avatar_test.go index 1a3a851281e90..6de2914f7f2e2 100644 --- a/integrations/user_avatar_test.go +++ b/integrations/user_avatar_test.go @@ -11,17 +11,17 @@ import ( "mime/multipart" "net/http" "net/url" - "strings" "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/avatar" "github.com/stretchr/testify/assert" ) func TestUserAvatar(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo3, is an org + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo3, is an org seed := user2.Email if len(seed) == 0 { @@ -71,17 +71,11 @@ func TestUserAvatar(t *testing.T) { session.MakeRequest(t, req, http.StatusFound) - user2 = models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo3, is an org + user2 = db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo3, is an org req = NewRequest(t, "GET", user2.AvatarLink()) - resp := session.MakeRequest(t, req, http.StatusFound) - location := resp.Header().Get("Location") - if !strings.HasPrefix(location, "/avatars") { - assert.Fail(t, "Avatar location is not local: %s", location) - } - req = NewRequest(t, "GET", location) - session.MakeRequest(t, req, http.StatusOK) + _ = session.MakeRequest(t, req, http.StatusOK) - // Can't test if the response matches because the image is regened on upload but checking that this at least doesn't give a 404 should be enough. + // Can't test if the response matches because the image is re-generated on upload but checking that this at least doesn't give a 404 should be enough. }) } diff --git a/integrations/user_test.go b/integrations/user_test.go index 199d5e242f822..f9e507afbea10 100644 --- a/integrations/user_test.go +++ b/integrations/user_test.go @@ -9,6 +9,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/test" "github.com/stretchr/testify/assert" @@ -34,8 +35,8 @@ func TestRenameUsername(t *testing.T) { }) session.MakeRequest(t, req, http.StatusFound) - models.AssertExistsAndLoadBean(t, &models.User{Name: "newUsername"}) - models.AssertNotExistsBean(t, &models.User{Name: "user2"}) + db.AssertExistsAndLoadBean(t, &models.User{Name: "newUsername"}) + db.AssertNotExistsBean(t, &models.User{Name: "user2"}) } func TestRenameInvalidUsername(t *testing.T) { @@ -66,7 +67,7 @@ func TestRenameInvalidUsername(t *testing.T) { i18n.Tr("en", "form.alpha_dash_dot_error"), ) - models.AssertNotExistsBean(t, &models.User{Name: invalidUsername}) + db.AssertNotExistsBean(t, &models.User{Name: invalidUsername}) } } @@ -112,7 +113,7 @@ func TestRenameReservedUsername(t *testing.T) { i18n.Tr("en", "user.form.name_reserved", reservedUsername), ) - models.AssertNotExistsBean(t, &models.User{Name: reservedUsername}) + db.AssertNotExistsBean(t, &models.User{Name: reservedUsername}) } } diff --git a/integrations/xss_test.go b/integrations/xss_test.go index 2d83e1e106007..427c97de265b6 100644 --- a/integrations/xss_test.go +++ b/integrations/xss_test.go @@ -9,13 +9,14 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestXSSUserFullName(t *testing.T) { defer prepareTestEnv(t)() - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) const fullName = `name & ` session := loginUser(t, user.Name) diff --git a/models/access.go b/models/access.go index 5d0b0b06cfe18..7bbc04bd45610 100644 --- a/models/access.go +++ b/models/access.go @@ -8,6 +8,7 @@ package models import ( "fmt" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" ) @@ -71,7 +72,11 @@ type Access struct { Mode AccessMode } -func accessLevel(e Engine, user *User, repo *Repository) (AccessMode, error) { +func init() { + db.RegisterModel(new(Access)) +} + +func accessLevel(e db.Engine, user *User, repo *Repository) (AccessMode, error) { mode := AccessModeNone var userID int64 restricted := false @@ -111,7 +116,7 @@ func (repoAccess) TableName() string { // GetRepositoryAccesses finds all repositories with their access mode where a user has access but does not own. func (user *User) GetRepositoryAccesses() (map[*Repository]AccessMode, error) { - rows, err := x. + rows, err := db.GetEngine(db.DefaultContext). Join("INNER", "repository", "repository.id = access.repo_id"). Where("access.user_id = ?", user.ID). And("repository.owner_id <> ?", user.ID). @@ -146,7 +151,7 @@ func (user *User) GetRepositoryAccesses() (map[*Repository]AccessMode, error) { // GetAccessibleRepositories finds repositories which the user has access but does not own. // If limit is smaller than 1 means returns all found results. func (user *User) GetAccessibleRepositories(limit int) (repos []*Repository, _ error) { - sess := x. + sess := db.GetEngine(db.DefaultContext). Where("owner_id !=? ", user.ID). Desc("updated_unix") if limit > 0 { @@ -185,7 +190,7 @@ func updateUserAccess(accessMap map[int64]*userAccess, user *User, mode AccessMo } // FIXME: do cross-comparison so reduce deletions and additions to the minimum? -func (repo *Repository) refreshAccesses(e Engine, accessMap map[int64]*userAccess) (err error) { +func (repo *Repository) refreshAccesses(e db.Engine, accessMap map[int64]*userAccess) (err error) { minMode := AccessModeRead if !repo.IsPrivate { minMode = AccessModeWrite @@ -219,12 +224,15 @@ func (repo *Repository) refreshAccesses(e Engine, accessMap map[int64]*userAcces } // refreshCollaboratorAccesses retrieves repository collaborations with their access modes. -func (repo *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int64]*userAccess) error { - collaborators, err := repo.getCollaborators(e, ListOptions{}) +func (repo *Repository) refreshCollaboratorAccesses(e db.Engine, accessMap map[int64]*userAccess) error { + collaborators, err := repo.getCollaborators(e, db.ListOptions{}) if err != nil { return fmt.Errorf("getCollaborations: %v", err) } for _, c := range collaborators { + if c.User.IsGhost() { + continue + } updateUserAccess(accessMap, c.User, c.Collaboration.Mode) } return nil @@ -233,7 +241,7 @@ func (repo *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int6 // recalculateTeamAccesses recalculates new accesses for teams of an organization // except the team whose ID is given. It is used to assign a team ID when // remove repository from that team. -func (repo *Repository) recalculateTeamAccesses(e Engine, ignTeamID int64) (err error) { +func (repo *Repository) recalculateTeamAccesses(e db.Engine, ignTeamID int64) (err error) { accessMap := make(map[int64]*userAccess, 20) if err = repo.getOwner(e); err != nil { @@ -276,7 +284,7 @@ func (repo *Repository) recalculateTeamAccesses(e Engine, ignTeamID int64) (err // recalculateUserAccess recalculates new access for a single user // Usable if we know access only affected one user -func (repo *Repository) recalculateUserAccess(e Engine, uid int64) (err error) { +func (repo *Repository) recalculateUserAccess(e db.Engine, uid int64) (err error) { minMode := AccessModeRead if !repo.IsPrivate { minMode = AccessModeWrite @@ -323,7 +331,7 @@ func (repo *Repository) recalculateUserAccess(e Engine, uid int64) (err error) { return nil } -func (repo *Repository) recalculateAccesses(e Engine) error { +func (repo *Repository) recalculateAccesses(e db.Engine) error { if repo.Owner.IsOrganization() { return repo.recalculateTeamAccesses(e, 0) } @@ -337,5 +345,5 @@ func (repo *Repository) recalculateAccesses(e Engine) error { // RecalculateAccesses recalculates all accesses for repository. func (repo *Repository) RecalculateAccesses() error { - return repo.recalculateAccesses(x) + return repo.recalculateAccesses(db.GetEngine(db.DefaultContext)) } diff --git a/models/access_test.go b/models/access_test.go index c134ab98d8dd0..2f641bb9b5732 100644 --- a/models/access_test.go +++ b/models/access_test.go @@ -7,27 +7,28 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestAccessLevel(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - user5 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) - user29 := AssertExistsAndLoadBean(t, &User{ID: 29}).(*User) + user2 := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + user5 := db.AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) + user29 := db.AssertExistsAndLoadBean(t, &User{ID: 29}).(*User) // A public repository owned by User 2 - repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + repo1 := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) assert.False(t, repo1.IsPrivate) // A private repository owned by Org 3 - repo3 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) + repo3 := db.AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) assert.True(t, repo3.IsPrivate) // Another public repository - repo4 := AssertExistsAndLoadBean(t, &Repository{ID: 4}).(*Repository) + repo4 := db.AssertExistsAndLoadBean(t, &Repository{ID: 4}).(*Repository) assert.False(t, repo4.IsPrivate) // org. owned private repo - repo24 := AssertExistsAndLoadBean(t, &Repository{ID: 24}).(*Repository) + repo24 := db.AssertExistsAndLoadBean(t, &Repository{ID: 24}).(*Repository) level, err := AccessLevel(user2, repo1) assert.NoError(t, err) @@ -62,15 +63,15 @@ func TestAccessLevel(t *testing.T) { } func TestHasAccess(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) + user1 := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + user2 := db.AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) // A public repository owned by User 2 - repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + repo1 := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) assert.False(t, repo1.IsPrivate) // A private repository owned by Org 3 - repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) + repo2 := db.AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) assert.True(t, repo2.IsPrivate) has, err := HasAccess(user1.ID, repo1) @@ -88,33 +89,33 @@ func TestHasAccess(t *testing.T) { } func TestUser_GetRepositoryAccesses(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user1 := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + user1 := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) accesses, err := user1.GetRepositoryAccesses() assert.NoError(t, err) assert.Len(t, accesses, 0) - user29 := AssertExistsAndLoadBean(t, &User{ID: 29}).(*User) + user29 := db.AssertExistsAndLoadBean(t, &User{ID: 29}).(*User) accesses, err = user29.GetRepositoryAccesses() assert.NoError(t, err) assert.Len(t, accesses, 2) } func TestUser_GetAccessibleRepositories(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user1 := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + user1 := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) repos, err := user1.GetAccessibleRepositories(0) assert.NoError(t, err) assert.Len(t, repos, 0) - user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + user2 := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) repos, err = user2.GetAccessibleRepositories(0) assert.NoError(t, err) assert.Len(t, repos, 4) - user29 := AssertExistsAndLoadBean(t, &User{ID: 29}).(*User) + user29 := db.AssertExistsAndLoadBean(t, &User{ID: 29}).(*User) repos, err = user29.GetAccessibleRepositories(0) assert.NoError(t, err) assert.Len(t, repos, 2) @@ -122,16 +123,16 @@ func TestUser_GetAccessibleRepositories(t *testing.T) { func TestRepository_RecalculateAccesses(t *testing.T) { // test with organization repo - assert.NoError(t, PrepareTestDatabase()) - repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) + assert.NoError(t, db.PrepareTestDatabase()) + repo1 := db.AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) assert.NoError(t, repo1.GetOwner()) - _, err := x.Delete(&Collaboration{UserID: 2, RepoID: 3}) + _, err := db.GetEngine(db.DefaultContext).Delete(&Collaboration{UserID: 2, RepoID: 3}) assert.NoError(t, err) assert.NoError(t, repo1.RecalculateAccesses()) access := &Access{UserID: 2, RepoID: 3} - has, err := x.Get(access) + has, err := db.GetEngine(db.DefaultContext).Get(access) assert.NoError(t, err) assert.True(t, has) assert.Equal(t, AccessModeOwner, access.Mode) @@ -139,25 +140,25 @@ func TestRepository_RecalculateAccesses(t *testing.T) { func TestRepository_RecalculateAccesses2(t *testing.T) { // test with non-organization repo - assert.NoError(t, PrepareTestDatabase()) - repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 4}).(*Repository) + assert.NoError(t, db.PrepareTestDatabase()) + repo1 := db.AssertExistsAndLoadBean(t, &Repository{ID: 4}).(*Repository) assert.NoError(t, repo1.GetOwner()) - _, err := x.Delete(&Collaboration{UserID: 4, RepoID: 4}) + _, err := db.GetEngine(db.DefaultContext).Delete(&Collaboration{UserID: 4, RepoID: 4}) assert.NoError(t, err) assert.NoError(t, repo1.RecalculateAccesses()) - has, err := x.Get(&Access{UserID: 4, RepoID: 4}) + has, err := db.GetEngine(db.DefaultContext).Get(&Access{UserID: 4, RepoID: 4}) assert.NoError(t, err) assert.False(t, has) } func TestRepository_RecalculateAccesses3(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - team5 := AssertExistsAndLoadBean(t, &Team{ID: 5}).(*Team) - user29 := AssertExistsAndLoadBean(t, &User{ID: 29}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + team5 := db.AssertExistsAndLoadBean(t, &Team{ID: 5}).(*Team) + user29 := db.AssertExistsAndLoadBean(t, &User{ID: 29}).(*User) - has, err := x.Get(&Access{UserID: 29, RepoID: 23}) + has, err := db.GetEngine(db.DefaultContext).Get(&Access{UserID: 29, RepoID: 23}) assert.NoError(t, err) assert.False(t, has) @@ -165,7 +166,7 @@ func TestRepository_RecalculateAccesses3(t *testing.T) { // even though repo 23 is public assert.NoError(t, AddTeamMember(team5, user29.ID)) - has, err = x.Get(&Access{UserID: 29, RepoID: 23}) + has, err = db.GetEngine(db.DefaultContext).Get(&Access{UserID: 29, RepoID: 23}) assert.NoError(t, err) assert.True(t, has) } diff --git a/models/action.go b/models/action.go index 3462a5e3f009f..7c970e1fdb6f5 100644 --- a/models/action.go +++ b/models/action.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" @@ -74,6 +75,10 @@ type Action struct { CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` } +func init() { + db.RegisterModel(new(Action)) +} + // GetOpType gets the ActionType of this action. func (a *Action) GetOpType() ActionType { return a.OpType @@ -203,10 +208,10 @@ func GetRepositoryFromMatch(ownerName, repoName string) (*Repository, error) { // GetCommentLink returns link to action comment. func (a *Action) GetCommentLink() string { - return a.getCommentLink(x) + return a.getCommentLink(db.GetEngine(db.DefaultContext)) } -func (a *Action) getCommentLink(e Engine) string { +func (a *Action) getCommentLink(e db.Engine) string { if a == nil { return "#" } @@ -312,7 +317,7 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) { actions := make([]*Action, 0, setting.UI.FeedPagingNum) - if err := x.Limit(setting.UI.FeedPagingNum).Desc("id").Where(cond).Find(&actions); err != nil { + if err := db.GetEngine(db.DefaultContext).Limit(setting.UI.FeedPagingNum).Desc("created_unix").Where(cond).Find(&actions); err != nil { return nil, fmt.Errorf("Find: %v", err) } @@ -403,6 +408,6 @@ func DeleteOldActions(olderThan time.Duration) (err error) { return nil } - _, err = x.Where("created_unix < ?", time.Now().Add(-olderThan).Unix()).Delete(&Action{}) + _, err = db.GetEngine(db.DefaultContext).Where("created_unix < ?", time.Now().Add(-olderThan).Unix()).Delete(&Action{}) return } diff --git a/models/action_list.go b/models/action_list.go index 6f726f4b34c08..69e6aa73121fa 100644 --- a/models/action_list.go +++ b/models/action_list.go @@ -4,7 +4,11 @@ package models -import "fmt" +import ( + "fmt" + + "code.gitea.io/gitea/models/db" +) // ActionList defines a list of actions type ActionList []*Action @@ -19,7 +23,7 @@ func (actions ActionList) getUserIDs() []int64 { return keysInt64(userIDs) } -func (actions ActionList) loadUsers(e Engine) ([]*User, error) { +func (actions ActionList) loadUsers(e db.Engine) ([]*User, error) { if len(actions) == 0 { return nil, nil } @@ -41,7 +45,7 @@ func (actions ActionList) loadUsers(e Engine) ([]*User, error) { // LoadUsers loads actions' all users func (actions ActionList) LoadUsers() ([]*User, error) { - return actions.loadUsers(x) + return actions.loadUsers(db.GetEngine(db.DefaultContext)) } func (actions ActionList) getRepoIDs() []int64 { @@ -54,7 +58,7 @@ func (actions ActionList) getRepoIDs() []int64 { return keysInt64(repoIDs) } -func (actions ActionList) loadRepositories(e Engine) ([]*Repository, error) { +func (actions ActionList) loadRepositories(e db.Engine) ([]*Repository, error) { if len(actions) == 0 { return nil, nil } @@ -76,11 +80,11 @@ func (actions ActionList) loadRepositories(e Engine) ([]*Repository, error) { // LoadRepositories loads actions' all repositories func (actions ActionList) LoadRepositories() ([]*Repository, error) { - return actions.loadRepositories(x) + return actions.loadRepositories(db.GetEngine(db.DefaultContext)) } // loadAttributes loads all attributes -func (actions ActionList) loadAttributes(e Engine) (err error) { +func (actions ActionList) loadAttributes(e db.Engine) (err error) { if _, err = actions.loadUsers(e); err != nil { return } @@ -94,5 +98,5 @@ func (actions ActionList) loadAttributes(e Engine) (err error) { // LoadAttributes loads attributes of the actions func (actions ActionList) LoadAttributes() error { - return actions.loadAttributes(x) + return actions.loadAttributes(db.GetEngine(db.DefaultContext)) } diff --git a/models/action_test.go b/models/action_test.go index 6dab676a706b6..78090e3aadbb5 100644 --- a/models/action_test.go +++ b/models/action_test.go @@ -8,23 +8,24 @@ import ( "path" "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" ) func TestAction_GetRepoPath(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{}).(*Repository) - owner := AssertExistsAndLoadBean(t, &User{ID: repo.OwnerID}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + repo := db.AssertExistsAndLoadBean(t, &Repository{}).(*Repository) + owner := db.AssertExistsAndLoadBean(t, &User{ID: repo.OwnerID}).(*User) action := &Action{RepoID: repo.ID} assert.Equal(t, path.Join(owner.Name, repo.Name), action.GetRepoPath()) } func TestAction_GetRepoLink(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{}).(*Repository) - owner := AssertExistsAndLoadBean(t, &User{ID: repo.OwnerID}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + repo := db.AssertExistsAndLoadBean(t, &Repository{}).(*Repository) + owner := db.AssertExistsAndLoadBean(t, &User{ID: repo.OwnerID}).(*User) action := &Action{RepoID: repo.ID} setting.AppSubURL = "/suburl" expected := path.Join(setting.AppSubURL, owner.Name, repo.Name) @@ -33,8 +34,8 @@ func TestAction_GetRepoLink(t *testing.T) { func TestGetFeeds(t *testing.T) { // test with an individual user - assert.NoError(t, PrepareTestDatabase()) - user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + user := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) actions, err := GetFeeds(GetFeedsOptions{ RequestedUser: user, @@ -61,9 +62,9 @@ func TestGetFeeds(t *testing.T) { func TestGetFeeds2(t *testing.T) { // test with an organization user - assert.NoError(t, PrepareTestDatabase()) - org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) - user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + org := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + user := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) actions, err := GetFeeds(GetFeedsOptions{ RequestedUser: org, diff --git a/models/admin.go b/models/admin.go index 3a784d66964ab..a003aff7e6207 100644 --- a/models/admin.go +++ b/models/admin.go @@ -8,6 +8,7 @@ package models import ( "fmt" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/timeutil" @@ -32,6 +33,10 @@ type Notice struct { CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` } +func init() { + db.RegisterModel(new(Notice)) +} + // TrStr returns a translation format string. func (n *Notice) TrStr() string { return fmt.Sprintf("admin.notices.type_%d", n.Type) @@ -39,10 +44,10 @@ func (n *Notice) TrStr() string { // CreateNotice creates new system notice. func CreateNotice(tp NoticeType, desc string, args ...interface{}) error { - return createNotice(x, tp, desc, args...) + return createNotice(db.GetEngine(db.DefaultContext), tp, desc, args...) } -func createNotice(e Engine, tp NoticeType, desc string, args ...interface{}) error { +func createNotice(e db.Engine, tp NoticeType, desc string, args ...interface{}) error { if len(args) > 0 { desc = fmt.Sprintf(desc, args...) } @@ -56,22 +61,22 @@ func createNotice(e Engine, tp NoticeType, desc string, args ...interface{}) err // CreateRepositoryNotice creates new system notice with type NoticeRepository. func CreateRepositoryNotice(desc string, args ...interface{}) error { - return createNotice(x, NoticeRepository, desc, args...) + return createNotice(db.GetEngine(db.DefaultContext), NoticeRepository, desc, args...) } // RemoveAllWithNotice removes all directories in given path and // creates a system notice when error occurs. func RemoveAllWithNotice(title, path string) { - removeAllWithNotice(x, title, path) + removeAllWithNotice(db.GetEngine(db.DefaultContext), title, path) } // RemoveStorageWithNotice removes a file from the storage and // creates a system notice when error occurs. func RemoveStorageWithNotice(bucket storage.ObjectStorage, title, path string) { - removeStorageWithNotice(x, bucket, title, path) + removeStorageWithNotice(db.GetEngine(db.DefaultContext), bucket, title, path) } -func removeStorageWithNotice(e Engine, bucket storage.ObjectStorage, title, path string) { +func removeStorageWithNotice(e db.Engine, bucket storage.ObjectStorage, title, path string) { if err := bucket.Delete(path); err != nil { desc := fmt.Sprintf("%s [%s]: %v", title, path, err) log.Warn(title+" [%s]: %v", path, err) @@ -81,7 +86,7 @@ func removeStorageWithNotice(e Engine, bucket storage.ObjectStorage, title, path } } -func removeAllWithNotice(e Engine, title, path string) { +func removeAllWithNotice(e db.Engine, title, path string) { if err := util.RemoveAll(path); err != nil { desc := fmt.Sprintf("%s [%s]: %v", title, path, err) log.Warn(title+" [%s]: %v", path, err) @@ -93,33 +98,33 @@ func removeAllWithNotice(e Engine, title, path string) { // CountNotices returns number of notices. func CountNotices() int64 { - count, _ := x.Count(new(Notice)) + count, _ := db.GetEngine(db.DefaultContext).Count(new(Notice)) return count } // Notices returns notices in given page. func Notices(page, pageSize int) ([]*Notice, error) { notices := make([]*Notice, 0, pageSize) - return notices, x. + return notices, db.GetEngine(db.DefaultContext). Limit(pageSize, (page-1)*pageSize). - Desc("id"). + Desc("created_unix"). Find(¬ices) } // DeleteNotice deletes a system notice by given ID. func DeleteNotice(id int64) error { - _, err := x.ID(id).Delete(new(Notice)) + _, err := db.GetEngine(db.DefaultContext).ID(id).Delete(new(Notice)) return err } // DeleteNotices deletes all notices with ID from start to end (inclusive). func DeleteNotices(start, end int64) error { if start == 0 && end == 0 { - _, err := x.Exec("DELETE FROM notice") + _, err := db.GetEngine(db.DefaultContext).Exec("DELETE FROM notice") return err } - sess := x.Where("id >= ?", start) + sess := db.GetEngine(db.DefaultContext).Where("id >= ?", start) if end > 0 { sess.And("id <= ?", end) } @@ -132,7 +137,7 @@ func DeleteNoticesByIDs(ids []int64) error { if len(ids) == 0 { return nil } - _, err := x. + _, err := db.GetEngine(db.DefaultContext). In("id", ids). Delete(new(Notice)) return err @@ -141,7 +146,7 @@ func DeleteNoticesByIDs(ids []int64) error { // GetAdminUser returns the first administrator func GetAdminUser() (*User, error) { var admin User - has, err := x.Where("is_admin=?", true).Get(&admin) + has, err := db.GetEngine(db.DefaultContext).Where("is_admin=?", true).Get(&admin) if err != nil { return nil, err } else if !has { diff --git a/models/admin_test.go b/models/admin_test.go index b4383cb2d313e..316ed5cb683d8 100644 --- a/models/admin_test.go +++ b/models/admin_test.go @@ -7,6 +7,7 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) @@ -19,38 +20,38 @@ func TestNotice_TrStr(t *testing.T) { } func TestCreateNotice(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) noticeBean := &Notice{ Type: NoticeRepository, Description: "test description", } - AssertNotExistsBean(t, noticeBean) + db.AssertNotExistsBean(t, noticeBean) assert.NoError(t, CreateNotice(noticeBean.Type, noticeBean.Description)) - AssertExistsAndLoadBean(t, noticeBean) + db.AssertExistsAndLoadBean(t, noticeBean) } func TestCreateRepositoryNotice(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) noticeBean := &Notice{ Type: NoticeRepository, Description: "test description", } - AssertNotExistsBean(t, noticeBean) + db.AssertNotExistsBean(t, noticeBean) assert.NoError(t, CreateRepositoryNotice(noticeBean.Description)) - AssertExistsAndLoadBean(t, noticeBean) + db.AssertExistsAndLoadBean(t, noticeBean) } // TODO TestRemoveAllWithNotice func TestCountNotices(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) assert.Equal(t, int64(3), CountNotices()) } func TestNotices(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) notices, err := Notices(1, 2) assert.NoError(t, err) @@ -67,47 +68,47 @@ func TestNotices(t *testing.T) { } func TestDeleteNotice(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - AssertExistsAndLoadBean(t, &Notice{ID: 3}) + db.AssertExistsAndLoadBean(t, &Notice{ID: 3}) assert.NoError(t, DeleteNotice(3)) - AssertNotExistsBean(t, &Notice{ID: 3}) + db.AssertNotExistsBean(t, &Notice{ID: 3}) } func TestDeleteNotices(t *testing.T) { // delete a non-empty range - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - AssertExistsAndLoadBean(t, &Notice{ID: 1}) - AssertExistsAndLoadBean(t, &Notice{ID: 2}) - AssertExistsAndLoadBean(t, &Notice{ID: 3}) + db.AssertExistsAndLoadBean(t, &Notice{ID: 1}) + db.AssertExistsAndLoadBean(t, &Notice{ID: 2}) + db.AssertExistsAndLoadBean(t, &Notice{ID: 3}) assert.NoError(t, DeleteNotices(1, 2)) - AssertNotExistsBean(t, &Notice{ID: 1}) - AssertNotExistsBean(t, &Notice{ID: 2}) - AssertExistsAndLoadBean(t, &Notice{ID: 3}) + db.AssertNotExistsBean(t, &Notice{ID: 1}) + db.AssertNotExistsBean(t, &Notice{ID: 2}) + db.AssertExistsAndLoadBean(t, &Notice{ID: 3}) } func TestDeleteNotices2(t *testing.T) { // delete an empty range - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - AssertExistsAndLoadBean(t, &Notice{ID: 1}) - AssertExistsAndLoadBean(t, &Notice{ID: 2}) - AssertExistsAndLoadBean(t, &Notice{ID: 3}) + db.AssertExistsAndLoadBean(t, &Notice{ID: 1}) + db.AssertExistsAndLoadBean(t, &Notice{ID: 2}) + db.AssertExistsAndLoadBean(t, &Notice{ID: 3}) assert.NoError(t, DeleteNotices(3, 2)) - AssertExistsAndLoadBean(t, &Notice{ID: 1}) - AssertExistsAndLoadBean(t, &Notice{ID: 2}) - AssertExistsAndLoadBean(t, &Notice{ID: 3}) + db.AssertExistsAndLoadBean(t, &Notice{ID: 1}) + db.AssertExistsAndLoadBean(t, &Notice{ID: 2}) + db.AssertExistsAndLoadBean(t, &Notice{ID: 3}) } func TestDeleteNoticesByIDs(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - AssertExistsAndLoadBean(t, &Notice{ID: 1}) - AssertExistsAndLoadBean(t, &Notice{ID: 2}) - AssertExistsAndLoadBean(t, &Notice{ID: 3}) + db.AssertExistsAndLoadBean(t, &Notice{ID: 1}) + db.AssertExistsAndLoadBean(t, &Notice{ID: 2}) + db.AssertExistsAndLoadBean(t, &Notice{ID: 3}) assert.NoError(t, DeleteNoticesByIDs([]int64{1, 3})) - AssertNotExistsBean(t, &Notice{ID: 1}) - AssertExistsAndLoadBean(t, &Notice{ID: 2}) - AssertNotExistsBean(t, &Notice{ID: 3}) + db.AssertNotExistsBean(t, &Notice{ID: 1}) + db.AssertExistsAndLoadBean(t, &Notice{ID: 2}) + db.AssertNotExistsBean(t, &Notice{ID: 3}) } diff --git a/models/attachment.go b/models/attachment.go index 330e965bb11c9..f06b389dc697c 100644 --- a/models/attachment.go +++ b/models/attachment.go @@ -5,9 +5,11 @@ package models import ( + "context" "fmt" "path" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/timeutil" @@ -30,10 +32,14 @@ type Attachment struct { CreatedUnix timeutil.TimeStamp `xorm:"created"` } +func init() { + db.RegisterModel(new(Attachment)) +} + // IncreaseDownloadCount is update download count + 1 func (a *Attachment) IncreaseDownloadCount() error { // Update download count. - if _, err := x.Exec("UPDATE `attachment` SET download_count=download_count+1 WHERE id=?", a.ID); err != nil { + if _, err := db.GetEngine(db.DefaultContext).Exec("UPDATE `attachment` SET download_count=download_count+1 WHERE id=?", a.ID); err != nil { return fmt.Errorf("increase attachment count: %v", err) } @@ -81,10 +87,10 @@ func (a *Attachment) LinkedRepository() (*Repository, UnitType, error) { // GetAttachmentByID returns attachment by given id func GetAttachmentByID(id int64) (*Attachment, error) { - return getAttachmentByID(x, id) + return getAttachmentByID(db.GetEngine(db.DefaultContext), id) } -func getAttachmentByID(e Engine, id int64) (*Attachment, error) { +func getAttachmentByID(e db.Engine, id int64) (*Attachment, error) { attach := &Attachment{} if has, err := e.ID(id).Get(attach); err != nil { return nil, err @@ -94,7 +100,7 @@ func getAttachmentByID(e Engine, id int64) (*Attachment, error) { return attach, nil } -func getAttachmentByUUID(e Engine, uuid string) (*Attachment, error) { +func getAttachmentByUUID(e db.Engine, uuid string) (*Attachment, error) { attach := &Attachment{} has, err := e.Where("uuid=?", uuid).Get(attach) if err != nil { @@ -106,11 +112,11 @@ func getAttachmentByUUID(e Engine, uuid string) (*Attachment, error) { } // GetAttachmentsByUUIDs returns attachment by given UUID list. -func GetAttachmentsByUUIDs(ctx DBContext, uuids []string) ([]*Attachment, error) { - return getAttachmentsByUUIDs(ctx.e, uuids) +func GetAttachmentsByUUIDs(ctx context.Context, uuids []string) ([]*Attachment, error) { + return getAttachmentsByUUIDs(db.GetEngine(ctx), uuids) } -func getAttachmentsByUUIDs(e Engine, uuids []string) ([]*Attachment, error) { +func getAttachmentsByUUIDs(e db.Engine, uuids []string) ([]*Attachment, error) { if len(uuids) == 0 { return []*Attachment{}, nil } @@ -122,41 +128,41 @@ func getAttachmentsByUUIDs(e Engine, uuids []string) ([]*Attachment, error) { // GetAttachmentByUUID returns attachment by given UUID. func GetAttachmentByUUID(uuid string) (*Attachment, error) { - return getAttachmentByUUID(x, uuid) + return getAttachmentByUUID(db.GetEngine(db.DefaultContext), uuid) } // ExistAttachmentsByUUID returns true if attachment is exist by given UUID func ExistAttachmentsByUUID(uuid string) (bool, error) { - return x.Where("`uuid`=?", uuid).Exist(new(Attachment)) + return db.GetEngine(db.DefaultContext).Where("`uuid`=?", uuid).Exist(new(Attachment)) } // GetAttachmentByReleaseIDFileName returns attachment by given releaseId and fileName. func GetAttachmentByReleaseIDFileName(releaseID int64, fileName string) (*Attachment, error) { - return getAttachmentByReleaseIDFileName(x, releaseID, fileName) + return getAttachmentByReleaseIDFileName(db.GetEngine(db.DefaultContext), releaseID, fileName) } -func getAttachmentsByIssueID(e Engine, issueID int64) ([]*Attachment, error) { +func getAttachmentsByIssueID(e db.Engine, issueID int64) ([]*Attachment, error) { attachments := make([]*Attachment, 0, 10) return attachments, e.Where("issue_id = ? AND comment_id = 0", issueID).Find(&attachments) } // GetAttachmentsByIssueID returns all attachments of an issue. func GetAttachmentsByIssueID(issueID int64) ([]*Attachment, error) { - return getAttachmentsByIssueID(x, issueID) + return getAttachmentsByIssueID(db.GetEngine(db.DefaultContext), issueID) } // GetAttachmentsByCommentID returns all attachments if comment by given ID. func GetAttachmentsByCommentID(commentID int64) ([]*Attachment, error) { - return getAttachmentsByCommentID(x, commentID) + return getAttachmentsByCommentID(db.GetEngine(db.DefaultContext), commentID) } -func getAttachmentsByCommentID(e Engine, commentID int64) ([]*Attachment, error) { +func getAttachmentsByCommentID(e db.Engine, commentID int64) ([]*Attachment, error) { attachments := make([]*Attachment, 0, 10) return attachments, e.Where("comment_id=?", commentID).Find(&attachments) } // getAttachmentByReleaseIDFileName return a file based on the the following infos: -func getAttachmentByReleaseIDFileName(e Engine, releaseID int64, fileName string) (*Attachment, error) { +func getAttachmentByReleaseIDFileName(e db.Engine, releaseID int64, fileName string) (*Attachment, error) { attach := &Attachment{ReleaseID: releaseID, Name: fileName} has, err := e.Get(attach) if err != nil { @@ -169,12 +175,12 @@ func getAttachmentByReleaseIDFileName(e Engine, releaseID int64, fileName string // DeleteAttachment deletes the given attachment and optionally the associated file. func DeleteAttachment(a *Attachment, remove bool) error { - _, err := DeleteAttachments(DefaultDBContext(), []*Attachment{a}, remove) + _, err := DeleteAttachments(db.DefaultContext, []*Attachment{a}, remove) return err } // DeleteAttachments deletes the given attachments and optionally the associated files. -func DeleteAttachments(ctx DBContext, attachments []*Attachment, remove bool) (int, error) { +func DeleteAttachments(ctx context.Context, attachments []*Attachment, remove bool) (int, error) { if len(attachments) == 0 { return 0, nil } @@ -184,7 +190,7 @@ func DeleteAttachments(ctx DBContext, attachments []*Attachment, remove bool) (i ids = append(ids, a.ID) } - cnt, err := ctx.e.In("id", ids).NoAutoCondition().Delete(attachments[0]) + cnt, err := db.GetEngine(ctx).In("id", ids).NoAutoCondition().Delete(attachments[0]) if err != nil { return 0, err } @@ -206,7 +212,7 @@ func DeleteAttachmentsByIssue(issueID int64, remove bool) (int, error) { return 0, err } - return DeleteAttachments(DefaultDBContext(), attachments, remove) + return DeleteAttachments(db.DefaultContext, attachments, remove) } // DeleteAttachmentsByComment deletes all attachments associated with the given comment. @@ -216,24 +222,24 @@ func DeleteAttachmentsByComment(commentID int64, remove bool) (int, error) { return 0, err } - return DeleteAttachments(DefaultDBContext(), attachments, remove) + return DeleteAttachments(db.DefaultContext, attachments, remove) } // UpdateAttachment updates the given attachment in database func UpdateAttachment(atta *Attachment) error { - return updateAttachment(x, atta) + return updateAttachment(db.GetEngine(db.DefaultContext), atta) } // UpdateAttachmentByUUID Updates attachment via uuid -func UpdateAttachmentByUUID(ctx DBContext, attach *Attachment, cols ...string) error { +func UpdateAttachmentByUUID(ctx context.Context, attach *Attachment, cols ...string) error { if attach.UUID == "" { - return fmt.Errorf("Attachement uuid should not blank") + return fmt.Errorf("attachment uuid should be not blank") } - _, err := ctx.e.Where("uuid=?", attach.UUID).Cols(cols...).Update(attach) + _, err := db.GetEngine(ctx).Where("uuid=?", attach.UUID).Cols(cols...).Update(attach) return err } -func updateAttachment(e Engine, atta *Attachment) error { +func updateAttachment(e db.Engine, atta *Attachment) error { var sess *xorm.Session if atta.ID != 0 && atta.UUID == "" { sess = e.ID(atta.ID) @@ -247,7 +253,7 @@ func updateAttachment(e Engine, atta *Attachment) error { // DeleteAttachmentsByRelease deletes all attachments associated with the given release. func DeleteAttachmentsByRelease(releaseID int64) error { - _, err := x.Where("release_id = ?", releaseID).Delete(&Attachment{}) + _, err := db.GetEngine(db.DefaultContext).Where("release_id = ?", releaseID).Delete(&Attachment{}) return err } @@ -257,7 +263,7 @@ func IterateAttachment(f func(attach *Attachment) error) error { const batchSize = 100 for { attachments := make([]*Attachment, 0, batchSize) - if err := x.Limit(batchSize, start).Find(&attachments); err != nil { + if err := db.GetEngine(db.DefaultContext).Limit(batchSize, start).Find(&attachments); err != nil { return err } if len(attachments) == 0 { @@ -272,3 +278,16 @@ func IterateAttachment(f func(attach *Attachment) error) error { } } } + +// CountOrphanedAttachments returns the number of bad attachments +func CountOrphanedAttachments() (int64, error) { + return db.GetEngine(db.DefaultContext).Where("(issue_id > 0 and issue_id not in (select id from issue)) or (release_id > 0 and release_id not in (select id from `release`))"). + Count(new(Attachment)) +} + +// DeleteOrphanedAttachments delete all bad attachments +func DeleteOrphanedAttachments() error { + _, err := db.GetEngine(db.DefaultContext).Where("(issue_id > 0 and issue_id not in (select id from issue)) or (release_id > 0 and release_id not in (select id from `release`))"). + Delete(new(Attachment)) + return err +} diff --git a/models/attachment_test.go b/models/attachment_test.go index 4f6eb0a5edd21..725d5a40c0b29 100644 --- a/models/attachment_test.go +++ b/models/attachment_test.go @@ -7,11 +7,12 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestIncreaseDownloadCount(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) attachment, err := GetAttachmentByUUID("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11") assert.NoError(t, err) @@ -27,7 +28,7 @@ func TestIncreaseDownloadCount(t *testing.T) { } func TestGetByCommentOrIssueID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // count of attachments from issue ID attachments, err := GetAttachmentsByIssueID(1) @@ -40,7 +41,7 @@ func TestGetByCommentOrIssueID(t *testing.T) { } func TestDeleteAttachments(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) count, err := DeleteAttachmentsByIssue(4, false) assert.NoError(t, err) @@ -60,7 +61,7 @@ func TestDeleteAttachments(t *testing.T) { } func TestGetAttachmentByID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) attach, err := GetAttachmentByID(1) assert.NoError(t, err) @@ -76,7 +77,7 @@ func TestAttachment_DownloadURL(t *testing.T) { } func TestUpdateAttachment(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) attach, err := GetAttachmentByID(1) assert.NoError(t, err) @@ -85,13 +86,13 @@ func TestUpdateAttachment(t *testing.T) { attach.Name = "new_name" assert.NoError(t, UpdateAttachment(attach)) - AssertExistsAndLoadBean(t, &Attachment{Name: "new_name"}) + db.AssertExistsAndLoadBean(t, &Attachment{Name: "new_name"}) } func TestGetAttachmentsByUUIDs(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - attachList, err := GetAttachmentsByUUIDs(DefaultDBContext(), []string{"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", "not-existing-uuid"}) + attachList, err := GetAttachmentsByUUIDs(db.DefaultContext, []string{"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", "not-existing-uuid"}) assert.NoError(t, err) assert.Len(t, attachList, 2) assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attachList[0].UUID) @@ -101,7 +102,7 @@ func TestGetAttachmentsByUUIDs(t *testing.T) { } func TestLinkedRepository(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testCases := []struct { name string attachID int64 diff --git a/models/avatar.go b/models/avatar.go deleted file mode 100644 index b4c078f8cf9ff..0000000000000 --- a/models/avatar.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2020 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 models - -import ( - "crypto/md5" - "fmt" - "net/url" - "path" - "strconv" - "strings" - - "code.gitea.io/gitea/modules/base" - "code.gitea.io/gitea/modules/cache" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" -) - -// EmailHash represents a pre-generated hash map -type EmailHash struct { - Hash string `xorm:"pk varchar(32)"` - Email string `xorm:"UNIQUE NOT NULL"` -} - -// DefaultAvatarLink the default avatar link -func DefaultAvatarLink() string { - u, err := url.Parse(setting.AppSubURL) - if err != nil { - log.Error("GetUserByEmail: %v", err) - return "" - } - - u.Path = path.Join(u.Path, "/assets/img/avatar_default.png") - return u.String() -} - -// DefaultAvatarSize is a sentinel value for the default avatar size, as -// determined by the avatar-hosting service. -const DefaultAvatarSize = -1 - -// DefaultAvatarPixelSize is the default size in pixels of a rendered avatar -const DefaultAvatarPixelSize = 28 - -// AvatarRenderedSizeFactor is the factor by which the default size is increased for finer rendering -const AvatarRenderedSizeFactor = 4 - -// HashEmail hashes email address to MD5 string. -// https://en.gravatar.com/site/implement/hash/ -func HashEmail(email string) string { - return base.EncodeMD5(strings.ToLower(strings.TrimSpace(email))) -} - -// GetEmailForHash converts a provided md5sum to the email -func GetEmailForHash(md5Sum string) (string, error) { - return cache.GetString("Avatar:"+md5Sum, func() (string, error) { - emailHash := EmailHash{ - Hash: strings.ToLower(strings.TrimSpace(md5Sum)), - } - - _, err := x.Get(&emailHash) - return emailHash.Email, err - }) -} - -// LibravatarURL returns the URL for the given email. This function should only -// be called if a federated avatar service is enabled. -func LibravatarURL(email string) (*url.URL, error) { - urlStr, err := setting.LibravatarService.FromEmail(email) - if err != nil { - log.Error("LibravatarService.FromEmail(email=%s): error %v", email, err) - return nil, err - } - u, err := url.Parse(urlStr) - if err != nil { - log.Error("Failed to parse libravatar url(%s): error %v", urlStr, err) - return nil, err - } - return u, nil -} - -// HashedAvatarLink returns an avatar link for a provided email -func HashedAvatarLink(email string, size int) string { - lowerEmail := strings.ToLower(strings.TrimSpace(email)) - sum := fmt.Sprintf("%x", md5.Sum([]byte(lowerEmail))) - _, _ = cache.GetString("Avatar:"+sum, func() (string, error) { - emailHash := &EmailHash{ - Email: lowerEmail, - Hash: sum, - } - // OK we're going to open a session just because I think that that might hide away any problems with postgres reporting errors - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { - // we don't care about any DB problem just return the lowerEmail - return lowerEmail, nil - } - has, err := sess.Where("email = ? AND hash = ?", emailHash.Email, emailHash.Hash).Get(new(EmailHash)) - if has || err != nil { - // Seriously we don't care about any DB problems just return the lowerEmail - we expect the transaction to fail most of the time - return lowerEmail, nil - } - _, _ = sess.Insert(emailHash) - if err := sess.Commit(); err != nil { - // Seriously we don't care about any DB problems just return the lowerEmail - we expect the transaction to fail most of the time - return lowerEmail, nil - } - return lowerEmail, nil - }) - if size > 0 { - return setting.AppSubURL + "/avatar/" + url.PathEscape(sum) + "?size=" + strconv.Itoa(size) - } - return setting.AppSubURL + "/avatar/" + url.PathEscape(sum) -} - -// MakeFinalAvatarURL constructs the final avatar URL string -func MakeFinalAvatarURL(u *url.URL, size int) string { - vals := u.Query() - vals.Set("d", "identicon") - if size != DefaultAvatarSize { - vals.Set("s", strconv.Itoa(size)) - } - u.RawQuery = vals.Encode() - return u.String() -} - -// SizedAvatarLink returns a sized link to the avatar for the given email address. -func SizedAvatarLink(email string, size int) string { - var avatarURL *url.URL - if setting.EnableFederatedAvatar && setting.LibravatarService != nil { - // This is the slow path that would need to call LibravatarURL() which - // does DNS lookups. Avoid it by issuing a redirect so we don't block - // the template render with network requests. - return HashedAvatarLink(email, size) - } else if !setting.DisableGravatar { - // copy GravatarSourceURL, because we will modify its Path. - copyOfGravatarSourceURL := *setting.GravatarSourceURL - avatarURL = ©OfGravatarSourceURL - avatarURL.Path = path.Join(avatarURL.Path, HashEmail(email)) - } else { - return DefaultAvatarLink() - } - - return MakeFinalAvatarURL(avatarURL, size) -} diff --git a/models/avatars/avatar.go b/models/avatars/avatar.go new file mode 100644 index 0000000000000..0a1445d2f2d09 --- /dev/null +++ b/models/avatars/avatar.go @@ -0,0 +1,180 @@ +// Copyright 2021 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 avatars + +import ( + "context" + "net/url" + "path" + "strconv" + "strings" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/cache" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" +) + +// DefaultAvatarPixelSize is the default size in pixels of a rendered avatar +const DefaultAvatarPixelSize = 28 + +// AvatarRenderedSizeFactor is the factor by which the default size is increased for finer rendering +const AvatarRenderedSizeFactor = 4 + +// EmailHash represents a pre-generated hash map (mainly used by LibravatarURL, it queries email server's DNS records) +type EmailHash struct { + Hash string `xorm:"pk varchar(32)"` + Email string `xorm:"UNIQUE NOT NULL"` +} + +func init() { + db.RegisterModel(new(EmailHash)) +} + +// DefaultAvatarLink the default avatar link +func DefaultAvatarLink() string { + u, err := url.Parse(setting.AppSubURL) + if err != nil { + log.Error("GetUserByEmail: %v", err) + return "" + } + + u.Path = path.Join(u.Path, "/assets/img/avatar_default.png") + return u.String() +} + +// HashEmail hashes email address to MD5 string. https://en.gravatar.com/site/implement/hash/ +func HashEmail(email string) string { + return base.EncodeMD5(strings.ToLower(strings.TrimSpace(email))) +} + +// GetEmailForHash converts a provided md5sum to the email +func GetEmailForHash(md5Sum string) (string, error) { + return cache.GetString("Avatar:"+md5Sum, func() (string, error) { + emailHash := EmailHash{ + Hash: strings.ToLower(strings.TrimSpace(md5Sum)), + } + + _, err := db.GetEngine(db.DefaultContext).Get(&emailHash) + return emailHash.Email, err + }) +} + +// LibravatarURL returns the URL for the given email. Slow due to the DNS lookup. +// This function should only be called if a federated avatar service is enabled. +func LibravatarURL(email string) (*url.URL, error) { + urlStr, err := setting.LibravatarService.FromEmail(email) + if err != nil { + log.Error("LibravatarService.FromEmail(email=%s): error %v", email, err) + return nil, err + } + u, err := url.Parse(urlStr) + if err != nil { + log.Error("Failed to parse libravatar url(%s): error %v", urlStr, err) + return nil, err + } + return u, nil +} + +// saveEmailHash returns an avatar link for a provided email, +// the email and hash are saved into database, which will be used by GetEmailForHash later +func saveEmailHash(email string) string { + lowerEmail := strings.ToLower(strings.TrimSpace(email)) + emailHash := HashEmail(lowerEmail) + _, _ = cache.GetString("Avatar:"+emailHash, func() (string, error) { + emailHash := &EmailHash{ + Email: lowerEmail, + Hash: emailHash, + } + // OK we're going to open a session just because I think that that might hide away any problems with postgres reporting errors + if err := db.WithTx(func(ctx context.Context) error { + has, err := db.GetEngine(ctx).Where("email = ? AND hash = ?", emailHash.Email, emailHash.Hash).Get(new(EmailHash)) + if has || err != nil { + // Seriously we don't care about any DB problems just return the lowerEmail - we expect the transaction to fail most of the time + return nil + } + _, _ = db.GetEngine(ctx).Insert(emailHash) + return nil + }); err != nil { + // Seriously we don't care about any DB problems just return the lowerEmail - we expect the transaction to fail most of the time + return lowerEmail, nil + } + return lowerEmail, nil + }) + return emailHash +} + +// GenerateUserAvatarFastLink returns a fast link (302) to the user's avatar: "/user/avatar/${User.Name}/${size}" +func GenerateUserAvatarFastLink(userName string, size int) string { + if size < 0 { + size = 0 + } + return setting.AppSubURL + "/user/avatar/" + userName + "/" + strconv.Itoa(size) +} + +// GenerateUserAvatarImageLink returns a link for `User.Avatar` image file: "/avatars/${User.Avatar}" +func GenerateUserAvatarImageLink(userAvatar string, size int) string { + if size > 0 { + return setting.AppSubURL + "/avatars/" + userAvatar + "?size=" + strconv.Itoa(size) + } + return setting.AppSubURL + "/avatars/" + userAvatar +} + +// generateRecognizedAvatarURL generate a recognized avatar (Gravatar/Libravatar) URL, it modifies the URL so the parameter is passed by a copy +func generateRecognizedAvatarURL(u url.URL, size int) string { + urlQuery := u.Query() + urlQuery.Set("d", "identicon") + if size > 0 { + urlQuery.Set("s", strconv.Itoa(size)) + } + u.RawQuery = urlQuery.Encode() + return u.String() +} + +// generateEmailAvatarLink returns a email avatar link. +// if final is true, it may use a slow path (eg: query DNS). +// if final is false, it always uses a fast path. +func generateEmailAvatarLink(email string, size int, final bool) string { + email = strings.TrimSpace(email) + if email == "" { + return DefaultAvatarLink() + } + + var err error + if setting.EnableFederatedAvatar && setting.LibravatarService != nil { + emailHash := saveEmailHash(email) + if final { + // for final link, we can spend more time on slow external query + var avatarURL *url.URL + if avatarURL, err = LibravatarURL(email); err != nil { + return DefaultAvatarLink() + } + return generateRecognizedAvatarURL(*avatarURL, size) + } + // for non-final link, we should return fast (use a 302 redirection link) + urlStr := setting.AppSubURL + "/avatar/" + emailHash + if size > 0 { + urlStr += "?size=" + strconv.Itoa(size) + } + return urlStr + } else if !setting.DisableGravatar { + // copy GravatarSourceURL, because we will modify its Path. + avatarURLCopy := *setting.GravatarSourceURL + avatarURLCopy.Path = path.Join(avatarURLCopy.Path, HashEmail(email)) + return generateRecognizedAvatarURL(avatarURLCopy, size) + } + return DefaultAvatarLink() +} + +//GenerateEmailAvatarFastLink returns a avatar link (fast, the link may be a delegated one: "/avatar/${hash}") +func GenerateEmailAvatarFastLink(email string, size int) string { + return generateEmailAvatarLink(email, size, false) +} + +//GenerateEmailAvatarFinalLink returns a avatar final link (maybe slow) +func GenerateEmailAvatarFinalLink(email string, size int) string { + return generateEmailAvatarLink(email, size, true) +} diff --git a/models/avatar_test.go b/models/avatars/avatar_test.go similarity index 89% rename from models/avatar_test.go rename to models/avatars/avatar_test.go index 09f1a8066da58..4d6255ca5fefb 100644 --- a/models/avatar_test.go +++ b/models/avatars/avatar_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package avatars import ( "net/url" @@ -44,11 +44,11 @@ func TestSizedAvatarLink(t *testing.T) { disableGravatar() assert.Equal(t, "/testsuburl/assets/img/avatar_default.png", - SizedAvatarLink("gitea@example.com", 100)) + GenerateEmailAvatarFastLink("gitea@example.com", 100)) enableGravatar(t) assert.Equal(t, "https://secure.gravatar.com/avatar/353cbad9b58e69c96154ad99f92bedc7?d=identicon&s=100", - SizedAvatarLink("gitea@example.com", 100), + GenerateEmailAvatarFastLink("gitea@example.com", 100), ) } diff --git a/models/branches.go b/models/branches.go index e13d84ee0524c..8eaa4b6fd74b6 100644 --- a/models/branches.go +++ b/models/branches.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" @@ -43,11 +44,17 @@ type ProtectedBranch struct { DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"` RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"` ProtectedFilePatterns string `xorm:"TEXT"` + UnprotectedFilePatterns string `xorm:"TEXT"` CreatedUnix timeutil.TimeStamp `xorm:"created"` UpdatedUnix timeutil.TimeStamp `xorm:"updated"` } +func init() { + db.RegisterModel(new(ProtectedBranch)) + db.RegisterModel(new(DeletedBranch)) +} + // IsProtected returns if the branch is protected func (protectBranch *ProtectedBranch) IsProtected() bool { return protectBranch.ID > 0 @@ -115,10 +122,10 @@ func (protectBranch *ProtectedBranch) IsUserMergeWhitelisted(userID int64, permi // IsUserOfficialReviewer check if user is official reviewer for the branch (counts towards required approvals) func (protectBranch *ProtectedBranch) IsUserOfficialReviewer(user *User) (bool, error) { - return protectBranch.isUserOfficialReviewer(x, user) + return protectBranch.isUserOfficialReviewer(db.GetEngine(db.DefaultContext), user) } -func (protectBranch *ProtectedBranch) isUserOfficialReviewer(e Engine, user *User) (bool, error) { +func (protectBranch *ProtectedBranch) isUserOfficialReviewer(e db.Engine, user *User) (bool, error) { repo, err := getRepositoryByID(e, protectBranch.RepoID) if err != nil { return false, err @@ -155,7 +162,7 @@ func (protectBranch *ProtectedBranch) HasEnoughApprovals(pr *PullRequest) bool { // 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 := x.Where("issue_id = ?", pr.IssueID). + sess := db.GetEngine(db.DefaultContext).Where("issue_id = ?", pr.IssueID). And("type = ?", ReviewTypeApprove). And("official = ?", true). And("dismissed = ?", false) @@ -176,7 +183,7 @@ func (protectBranch *ProtectedBranch) MergeBlockedByRejectedReview(pr *PullReque if !protectBranch.BlockOnRejectedReviews { return false } - rejectExist, err := x.Where("issue_id = ?", pr.IssueID). + rejectExist, err := db.GetEngine(db.DefaultContext).Where("issue_id = ?", pr.IssueID). And("type = ?", ReviewTypeReject). And("official = ?", true). And("dismissed = ?", false). @@ -195,7 +202,7 @@ func (protectBranch *ProtectedBranch) MergeBlockedByOfficialReviewRequests(pr *P if !protectBranch.BlockOnOfficialReviewRequests { return false } - has, err := x.Where("issue_id = ?", pr.IssueID). + has, err := db.GetEngine(db.DefaultContext).Where("issue_id = ?", pr.IssueID). And("type = ?", ReviewTypeRequest). And("official = ?", true). Exist(new(Review)) @@ -214,8 +221,17 @@ func (protectBranch *ProtectedBranch) MergeBlockedByOutdatedBranch(pr *PullReque // GetProtectedFilePatterns parses a semicolon separated list of protected file patterns and returns a glob.Glob slice func (protectBranch *ProtectedBranch) GetProtectedFilePatterns() []glob.Glob { + return getFilePatterns(protectBranch.ProtectedFilePatterns) +} + +// GetUnprotectedFilePatterns parses a semicolon separated list of unprotected file patterns and returns a glob.Glob slice +func (protectBranch *ProtectedBranch) GetUnprotectedFilePatterns() []glob.Glob { + return getFilePatterns(protectBranch.UnprotectedFilePatterns) +} + +func getFilePatterns(filePatterns string) []glob.Glob { extarr := make([]glob.Glob, 0, 10) - for _, expr := range strings.Split(strings.ToLower(protectBranch.ProtectedFilePatterns), ";") { + for _, expr := range strings.Split(strings.ToLower(filePatterns), ";") { expr = strings.TrimSpace(expr) if expr != "" { if g, err := glob.Compile(expr, '.', '/'); err != nil { @@ -260,12 +276,34 @@ func (protectBranch *ProtectedBranch) IsProtectedFile(patterns []glob.Glob, path return r } +// IsUnprotectedFile return if path is unprotected +func (protectBranch *ProtectedBranch) IsUnprotectedFile(patterns []glob.Glob, path string) bool { + if len(patterns) == 0 { + patterns = protectBranch.GetUnprotectedFilePatterns() + if len(patterns) == 0 { + return false + } + } + + lpath := strings.ToLower(strings.TrimSpace(path)) + + r := false + for _, pat := range patterns { + if pat.Match(lpath) { + r = true + break + } + } + + return r +} + // GetProtectedBranchBy getting protected branch by ID/Name func GetProtectedBranchBy(repoID int64, branchName string) (*ProtectedBranch, error) { - return getProtectedBranchBy(x, repoID, branchName) + return getProtectedBranchBy(db.GetEngine(db.DefaultContext), repoID, branchName) } -func getProtectedBranchBy(e Engine, repoID int64, branchName string) (*ProtectedBranch, error) { +func getProtectedBranchBy(e db.Engine, repoID int64, branchName string) (*ProtectedBranch, error) { rel := &ProtectedBranch{RepoID: repoID, BranchName: branchName} has, err := e.Get(rel) if err != nil { @@ -337,13 +375,13 @@ func UpdateProtectBranch(repo *Repository, protectBranch *ProtectedBranch, opts // Make sure protectBranch.ID is not 0 for whitelists if protectBranch.ID == 0 { - if _, err = x.Insert(protectBranch); err != nil { + if _, err = db.GetEngine(db.DefaultContext).Insert(protectBranch); err != nil { return fmt.Errorf("Insert: %v", err) } return nil } - if _, err = x.ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil { + if _, err = db.GetEngine(db.DefaultContext).ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil { return fmt.Errorf("Update: %v", err) } @@ -353,7 +391,7 @@ func UpdateProtectBranch(repo *Repository, protectBranch *ProtectedBranch, opts // GetProtectedBranches get all protected branches func (repo *Repository) GetProtectedBranches() ([]*ProtectedBranch, error) { protectedBranches := make([]*ProtectedBranch, 0) - return protectedBranches, x.Find(&protectedBranches, &ProtectedBranch{RepoID: repo.ID}) + return protectedBranches, db.GetEngine(db.DefaultContext).Find(&protectedBranches, &ProtectedBranch{RepoID: repo.ID}) } // GetBranchProtection get the branch protection of a branch @@ -368,7 +406,7 @@ func (repo *Repository) IsProtectedBranch(branchName string) (bool, error) { BranchName: branchName, } - has, err := x.Exist(protectedBranch) + has, err := db.GetEngine(db.DefaultContext).Exist(protectedBranch) if err != nil { return true, err } @@ -455,19 +493,13 @@ func (repo *Repository) DeleteProtectedBranch(id int64) (err error) { ID: id, } - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if affected, err := sess.Delete(protectedBranch); err != nil { + if affected, err := db.GetEngine(db.DefaultContext).Delete(protectedBranch); err != nil { return err } else if affected != 1 { return fmt.Errorf("delete protected branch ID(%v) failed", id) } - return sess.Commit() + return nil } // DeletedBranch struct @@ -490,29 +522,20 @@ func (repo *Repository) AddDeletedBranch(branchName, commit string, deletedByID DeletedByID: deletedByID, } - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { - return err - } - - if _, err := sess.InsertOne(deletedBranch); err != nil { - return err - } - - return sess.Commit() + _, err := db.GetEngine(db.DefaultContext).InsertOne(deletedBranch) + return err } // GetDeletedBranches returns all the deleted branches func (repo *Repository) GetDeletedBranches() ([]*DeletedBranch, error) { deletedBranches := make([]*DeletedBranch, 0) - return deletedBranches, x.Where("repo_id = ?", repo.ID).Desc("deleted_unix").Find(&deletedBranches) + return deletedBranches, db.GetEngine(db.DefaultContext).Where("repo_id = ?", repo.ID).Desc("deleted_unix").Find(&deletedBranches) } // GetDeletedBranchByID get a deleted branch by its ID func (repo *Repository) GetDeletedBranchByID(id int64) (*DeletedBranch, error) { deletedBranch := &DeletedBranch{} - has, err := x.ID(id).Get(deletedBranch) + has, err := db.GetEngine(db.DefaultContext).ID(id).Get(deletedBranch) if err != nil { return nil, err } @@ -529,19 +552,13 @@ func (repo *Repository) RemoveDeletedBranch(id int64) (err error) { ID: id, } - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if affected, err := sess.Delete(deletedBranch); err != nil { + if affected, err := db.GetEngine(db.DefaultContext).Delete(deletedBranch); err != nil { return err } else if affected != 1 { return fmt.Errorf("remove deleted branch ID(%v) failed", id) } - return sess.Commit() + return nil } // LoadUser loads the user that deleted the branch @@ -556,7 +573,7 @@ func (deletedBranch *DeletedBranch) LoadUser() { // RemoveDeletedBranch removes all deleted branches func RemoveDeletedBranch(repoID int64, branch string) error { - _, err := x.Where("repo_id=? AND name=?", repoID, branch).Delete(new(DeletedBranch)) + _, err := db.GetEngine(db.DefaultContext).Where("repo_id=? AND name=?", repoID, branch).Delete(new(DeletedBranch)) return err } @@ -566,7 +583,7 @@ func RemoveOldDeletedBranches(ctx context.Context, olderThan time.Duration) { log.Trace("Doing: DeletedBranchesCleanup") deleteBefore := time.Now().Add(-olderThan) - _, err := x.Where("deleted_unix < ?", deleteBefore.Unix()).Delete(new(DeletedBranch)) + _, err := db.GetEngine(db.DefaultContext).Where("deleted_unix < ?", deleteBefore.Unix()).Delete(new(DeletedBranch)) if err != nil { log.Error("DeletedBranchesCleanup: %v", err) } diff --git a/models/branches_test.go b/models/branches_test.go index b7984331edb12..02a9e81a7efed 100644 --- a/models/branches_test.go +++ b/models/branches_test.go @@ -7,21 +7,22 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestAddDeletedBranch(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - firstBranch := AssertExistsAndLoadBean(t, &DeletedBranch{ID: 1}).(*DeletedBranch) + assert.NoError(t, db.PrepareTestDatabase()) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + firstBranch := db.AssertExistsAndLoadBean(t, &DeletedBranch{ID: 1}).(*DeletedBranch) assert.Error(t, repo.AddDeletedBranch(firstBranch.Name, firstBranch.Commit, firstBranch.DeletedByID)) assert.NoError(t, repo.AddDeletedBranch("test", "5655464564554545466464656", int64(1))) } func TestGetDeletedBranches(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + assert.NoError(t, db.PrepareTestDatabase()) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) branches, err := repo.GetDeletedBranches() assert.NoError(t, err) @@ -29,17 +30,17 @@ func TestGetDeletedBranches(t *testing.T) { } func TestGetDeletedBranch(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - firstBranch := AssertExistsAndLoadBean(t, &DeletedBranch{ID: 1}).(*DeletedBranch) + assert.NoError(t, db.PrepareTestDatabase()) + firstBranch := db.AssertExistsAndLoadBean(t, &DeletedBranch{ID: 1}).(*DeletedBranch) assert.NotNil(t, getDeletedBranch(t, firstBranch)) } func TestDeletedBranchLoadUser(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - firstBranch := AssertExistsAndLoadBean(t, &DeletedBranch{ID: 1}).(*DeletedBranch) - secondBranch := AssertExistsAndLoadBean(t, &DeletedBranch{ID: 2}).(*DeletedBranch) + firstBranch := db.AssertExistsAndLoadBean(t, &DeletedBranch{ID: 1}).(*DeletedBranch) + secondBranch := db.AssertExistsAndLoadBean(t, &DeletedBranch{ID: 2}).(*DeletedBranch) branch := getDeletedBranch(t, firstBranch) assert.Nil(t, branch.DeletedBy) @@ -55,19 +56,19 @@ func TestDeletedBranchLoadUser(t *testing.T) { } func TestRemoveDeletedBranch(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + assert.NoError(t, db.PrepareTestDatabase()) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - firstBranch := AssertExistsAndLoadBean(t, &DeletedBranch{ID: 1}).(*DeletedBranch) + firstBranch := db.AssertExistsAndLoadBean(t, &DeletedBranch{ID: 1}).(*DeletedBranch) err := repo.RemoveDeletedBranch(1) assert.NoError(t, err) - AssertNotExistsBean(t, firstBranch) - AssertExistsAndLoadBean(t, &DeletedBranch{ID: 2}) + db.AssertNotExistsBean(t, firstBranch) + db.AssertExistsAndLoadBean(t, &DeletedBranch{ID: 2}) } func getDeletedBranch(t *testing.T, branch *DeletedBranch) *DeletedBranch { - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) deletedBranch, err := repo.GetDeletedBranchByID(branch.ID) assert.NoError(t, err) diff --git a/models/commit_status.go b/models/commit_status.go index 1ee3ead21709f..a6ded049c31cd 100644 --- a/models/commit_status.go +++ b/models/commit_status.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -37,7 +38,87 @@ type CommitStatus struct { UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } -func (status *CommitStatus) loadAttributes(e Engine) (err error) { +func init() { + db.RegisterModel(new(CommitStatus)) + db.RegisterModel(new(CommitStatusIndex)) +} + +// upsertCommitStatusIndex the function will not return until it acquires the lock or receives an error. +func upsertCommitStatusIndex(e db.Engine, repoID int64, sha string) (err error) { + // An atomic UPSERT operation (INSERT/UPDATE) is the only operation + // that ensures that the key is actually locked. + switch { + case setting.Database.UseSQLite3 || setting.Database.UsePostgreSQL: + _, err = e.Exec("INSERT INTO `commit_status_index` (repo_id, sha, max_index) "+ + "VALUES (?,?,1) ON CONFLICT (repo_id,sha) DO UPDATE SET max_index = `commit_status_index`.max_index+1", + repoID, sha) + case setting.Database.UseMySQL: + _, err = e.Exec("INSERT INTO `commit_status_index` (repo_id, sha, max_index) "+ + "VALUES (?,?,1) ON DUPLICATE KEY UPDATE max_index = max_index+1", + repoID, sha) + case setting.Database.UseMSSQL: + // https://weblogs.sqlteam.com/dang/2009/01/31/upsert-race-condition-with-merge/ + _, err = e.Exec("MERGE `commit_status_index` WITH (HOLDLOCK) as target "+ + "USING (SELECT ? AS repo_id, ? AS sha) AS src "+ + "ON src.repo_id = target.repo_id AND src.sha = target.sha "+ + "WHEN MATCHED THEN UPDATE SET target.max_index = target.max_index+1 "+ + "WHEN NOT MATCHED THEN INSERT (repo_id, sha, max_index) "+ + "VALUES (src.repo_id, src.sha, 1);", + repoID, sha) + default: + return fmt.Errorf("database type not supported") + } + return +} + +// GetNextCommitStatusIndex retried 3 times to generate a resource index +func GetNextCommitStatusIndex(repoID int64, sha string) (int64, error) { + for i := 0; i < db.MaxDupIndexAttempts; i++ { + idx, err := getNextCommitStatusIndex(repoID, sha) + if err == db.ErrResouceOutdated { + continue + } + if err != nil { + return 0, err + } + return idx, nil + } + return 0, db.ErrGetResourceIndexFailed +} + +// getNextCommitStatusIndex return the next index +func getNextCommitStatusIndex(repoID int64, sha string) (int64, error) { + ctx, commiter, err := db.TxContext() + if err != nil { + return 0, err + } + defer commiter.Close() + + var preIdx int64 + _, err = ctx.Engine().SQL("SELECT max_index FROM `commit_status_index` WHERE repo_id = ? AND sha = ?", repoID, sha).Get(&preIdx) + if err != nil { + return 0, err + } + + if err := upsertCommitStatusIndex(ctx.Engine(), repoID, sha); err != nil { + return 0, err + } + + var curIdx int64 + has, err := ctx.Engine().SQL("SELECT max_index FROM `commit_status_index` WHERE repo_id = ? AND sha = ? AND max_index=?", repoID, sha, preIdx+1).Get(&curIdx) + if err != nil { + return 0, err + } + if !has { + return 0, db.ErrResouceOutdated + } + if err := commiter.Commit(); err != nil { + return 0, err + } + return curIdx, nil +} + +func (status *CommitStatus) loadAttributes(e db.Engine) (err error) { if status.Repo == nil { status.Repo, err = getRepositoryByID(e, status.RepoID) if err != nil { @@ -55,7 +136,7 @@ func (status *CommitStatus) loadAttributes(e Engine) (err error) { // APIURL returns the absolute APIURL to this commit-status. func (status *CommitStatus) APIURL() string { - _ = status.loadAttributes(x) + _ = status.loadAttributes(db.GetEngine(db.DefaultContext)) return fmt.Sprintf("%sapi/v1/repos/%s/statuses/%s", setting.AppURL, status.Repo.FullName(), status.SHA) } @@ -82,7 +163,7 @@ func CalcCommitStatus(statuses []*CommitStatus) *CommitStatus { // CommitStatusOptions holds the options for query commit statuses type CommitStatusOptions struct { - ListOptions + db.ListOptions State string SortType string } @@ -97,7 +178,7 @@ func GetCommitStatuses(repo *Repository, sha string, opts *CommitStatusOptions) } countSession := listCommitStatusesStatement(repo, sha, opts) - countSession = opts.setSessionPagination(countSession) + countSession = db.SetSessionPagination(countSession, opts) maxResults, err := countSession.Count(new(CommitStatus)) if err != nil { log.Error("Count PRs: %v", err) @@ -106,13 +187,13 @@ func GetCommitStatuses(repo *Repository, sha string, opts *CommitStatusOptions) statuses := make([]*CommitStatus, 0, opts.PageSize) findSession := listCommitStatusesStatement(repo, sha, opts) - findSession = opts.setSessionPagination(findSession) + findSession = db.SetSessionPagination(findSession, opts) sortCommitStatusesSession(findSession, opts.SortType) return statuses, maxResults, findSession.Find(&statuses) } func listCommitStatusesStatement(repo *Repository, sha string, opts *CommitStatusOptions) *xorm.Session { - sess := x.Where("repo_id = ?", repo.ID).And("sha = ?", sha) + sess := db.GetEngine(db.DefaultContext).Where("repo_id = ?", repo.ID).And("sha = ?", sha) switch opts.State { case "pending", "success", "error", "failure", "warning": sess.And("state = ?", opts.State) @@ -137,19 +218,27 @@ func sortCommitStatusesSession(sess *xorm.Session, sortType string) { } } +// CommitStatusIndex represents a table for commit status index +type CommitStatusIndex struct { + ID int64 + RepoID int64 `xorm:"unique(repo_sha)"` + SHA string `xorm:"unique(repo_sha)"` + MaxIndex int64 `xorm:"index"` +} + // GetLatestCommitStatus returns all statuses with a unique context for a given commit. -func GetLatestCommitStatus(repoID int64, sha string, listOptions ListOptions) ([]*CommitStatus, error) { - return getLatestCommitStatus(x, repoID, sha, listOptions) +func GetLatestCommitStatus(repoID int64, sha string, listOptions db.ListOptions) ([]*CommitStatus, error) { + return getLatestCommitStatus(db.GetEngine(db.DefaultContext), repoID, sha, listOptions) } -func getLatestCommitStatus(e Engine, repoID int64, sha string, listOptions ListOptions) ([]*CommitStatus, error) { +func getLatestCommitStatus(e db.Engine, repoID int64, sha string, listOptions db.ListOptions) ([]*CommitStatus, error) { ids := make([]int64, 0, 10) sess := e.Table(&CommitStatus{}). Where("repo_id = ?", repoID).And("sha = ?", sha). Select("max( id ) as id"). GroupBy("context_hash").OrderBy("max( id ) desc") - sess = listOptions.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, &listOptions) err := sess.Find(&ids) if err != nil { @@ -166,7 +255,7 @@ func getLatestCommitStatus(e Engine, repoID int64, sha string, listOptions ListO func FindRepoRecentCommitStatusContexts(repoID int64, before time.Duration) ([]string, error) { start := timeutil.TimeStampNow().AddDuration(-before) ids := make([]int64, 0, 10) - if err := x.Table("commit_status"). + if err := db.GetEngine(db.DefaultContext).Table("commit_status"). Where("repo_id = ?", repoID). And("updated_unix >= ?", start). Select("max( id ) as id"). @@ -179,7 +268,7 @@ func FindRepoRecentCommitStatusContexts(repoID int64, before time.Duration) ([]s if len(ids) == 0 { return contexts, nil } - return contexts, x.Select("context").Table("commit_status").In("id", ids).Find(&contexts) + return contexts, db.GetEngine(db.DefaultContext).Select("context").Table("commit_status").In("id", ids).Find(&contexts) } // NewCommitStatusOptions holds options for creating a CommitStatus @@ -201,12 +290,17 @@ func NewCommitStatus(opts NewCommitStatusOptions) error { return fmt.Errorf("NewCommitStatus[%s, %s]: no user specified", repoPath, opts.SHA) } - sess := x.NewSession() - defer sess.Close() + // Get the next Status Index + idx, err := GetNextCommitStatusIndex(opts.Repo.ID, opts.SHA) + if err != nil { + return fmt.Errorf("generate commit status index failed: %v", err) + } - if err := sess.Begin(); err != nil { + ctx, committer, err := db.TxContext() + if err != nil { return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %v", opts.Repo.ID, opts.Creator.ID, opts.SHA, err) } + defer committer.Close() opts.CommitStatus.Description = strings.TrimSpace(opts.CommitStatus.Description) opts.CommitStatus.Context = strings.TrimSpace(opts.CommitStatus.Context) @@ -214,38 +308,17 @@ func NewCommitStatus(opts NewCommitStatusOptions) error { opts.CommitStatus.SHA = opts.SHA opts.CommitStatus.CreatorID = opts.Creator.ID opts.CommitStatus.RepoID = opts.Repo.ID - - // Get the next Status Index - var nextIndex int64 - lastCommitStatus := &CommitStatus{ - SHA: opts.SHA, - RepoID: opts.Repo.ID, - } - has, err := sess.Desc("index").Limit(1).Get(lastCommitStatus) - if err != nil { - if err := sess.Rollback(); err != nil { - log.Error("NewCommitStatus: sess.Rollback: %v", err) - } - return fmt.Errorf("NewCommitStatus[%s, %s]: %v", repoPath, opts.SHA, err) - } - if has { - log.Debug("NewCommitStatus[%s, %s]: found", repoPath, opts.SHA) - nextIndex = lastCommitStatus.Index - } - opts.CommitStatus.Index = nextIndex + 1 + opts.CommitStatus.Index = idx log.Debug("NewCommitStatus[%s, %s]: %d", repoPath, opts.SHA, opts.CommitStatus.Index) opts.CommitStatus.ContextHash = hashCommitStatusContext(opts.CommitStatus.Context) // Insert new CommitStatus - if _, err = sess.Insert(opts.CommitStatus); err != nil { - if err := sess.Rollback(); err != nil { - log.Error("Insert CommitStatus: sess.Rollback: %v", err) - } + if _, err = db.GetEngine(ctx).Insert(opts.CommitStatus); err != nil { return fmt.Errorf("Insert CommitStatus[%s, %s]: %v", repoPath, opts.SHA, err) } - return sess.Commit() + return committer.Commit() } // SignCommitWithStatuses represents a commit with validation of signature and status state. @@ -263,7 +336,7 @@ func ParseCommitsWithStatus(oldCommits []*SignCommit, repo *Repository) []*SignC commit := &SignCommitWithStatuses{ SignCommit: c, } - statuses, err := GetLatestCommitStatus(repo.ID, commit.ID.String(), ListOptions{}) + statuses, err := GetLatestCommitStatus(repo.ID, commit.ID.String(), db.ListOptions{}) if err != nil { log.Error("GetLatestCommitStatus: %v", err) } else { diff --git a/models/commit_status_test.go b/models/commit_status_test.go index 57b97f66063ef..7f4709144ceb3 100644 --- a/models/commit_status_test.go +++ b/models/commit_status_test.go @@ -7,18 +7,19 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" ) func TestGetCommitStatuses(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + repo1 := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) sha1 := "1234123412341234123412341234123412341234" - statuses, maxResults, err := GetCommitStatuses(repo1, sha1, &CommitStatusOptions{ListOptions: ListOptions{Page: 1, PageSize: 50}}) + statuses, maxResults, err := GetCommitStatuses(repo1, sha1, &CommitStatusOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 50}}) assert.NoError(t, err) assert.Equal(t, int(maxResults), 5) assert.Len(t, statuses, 5) diff --git a/models/consistency.go b/models/consistency.go index f037b0515704e..8af884365ebcb 100644 --- a/models/consistency.go +++ b/models/consistency.go @@ -5,13 +5,11 @@ package models import ( - "fmt" "reflect" - "regexp" "strings" "testing" - "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" "xorm.io/builder" ) @@ -43,7 +41,7 @@ func CheckConsistencyFor(t *testing.T, beansToCheck ...interface{}) { ptrToSliceValue := reflect.New(sliceType) ptrToSliceValue.Elem().Set(sliceValue) - assert.NoError(t, x.Table(bean).Find(ptrToSliceValue.Interface())) + assert.NoError(t, db.GetEngine(db.DefaultContext).Table(bean).Find(ptrToSliceValue.Interface())) sliceValue = ptrToSliceValue.Elem() for i := 0; i < sliceValue.Len(); i++ { @@ -60,7 +58,7 @@ func CheckConsistencyFor(t *testing.T, beansToCheck ...interface{}) { } // getCount get the count of database entries matching bean -func getCount(t *testing.T, e Engine, bean interface{}) int64 { +func getCount(t *testing.T, e db.Engine, bean interface{}) int64 { count, err := e.Count(bean) assert.NoError(t, err) return count @@ -68,7 +66,7 @@ func getCount(t *testing.T, e Engine, bean interface{}) int64 { // assertCount test the count of database entries matching bean func assertCount(t *testing.T, bean interface{}, expected int) { - assert.EqualValues(t, expected, getCount(t, x, bean), + assert.EqualValues(t, expected, getCount(t, db.GetEngine(db.DefaultContext), bean), "Failed consistency test, the counted bean (of type %T) was %+v", bean, bean) } @@ -91,46 +89,46 @@ func (repo *Repository) checkForConsistency(t *testing.T) { assertCount(t, &Milestone{RepoID: repo.ID}, repo.NumMilestones) assertCount(t, &Repository{ForkID: repo.ID}, repo.NumForks) if repo.IsFork { - AssertExistsAndLoadBean(t, &Repository{ID: repo.ForkID}) + db.AssertExistsAndLoadBean(t, &Repository{ID: repo.ForkID}) } - actual := getCount(t, x.Where("Mode<>?", RepoWatchModeDont), &Watch{RepoID: repo.ID}) + actual := getCount(t, db.GetEngine(db.DefaultContext).Where("Mode<>?", RepoWatchModeDont), &Watch{RepoID: repo.ID}) assert.EqualValues(t, repo.NumWatches, actual, "Unexpected number of watches for repo %+v", repo) - actual = getCount(t, x.Where("is_pull=?", false), &Issue{RepoID: repo.ID}) + actual = getCount(t, db.GetEngine(db.DefaultContext).Where("is_pull=?", false), &Issue{RepoID: repo.ID}) assert.EqualValues(t, repo.NumIssues, actual, "Unexpected number of issues for repo %+v", repo) - actual = getCount(t, x.Where("is_pull=? AND is_closed=?", false, true), &Issue{RepoID: repo.ID}) + actual = getCount(t, db.GetEngine(db.DefaultContext).Where("is_pull=? AND is_closed=?", false, true), &Issue{RepoID: repo.ID}) assert.EqualValues(t, repo.NumClosedIssues, actual, "Unexpected number of closed issues for repo %+v", repo) - actual = getCount(t, x.Where("is_pull=?", true), &Issue{RepoID: repo.ID}) + actual = getCount(t, db.GetEngine(db.DefaultContext).Where("is_pull=?", true), &Issue{RepoID: repo.ID}) assert.EqualValues(t, repo.NumPulls, actual, "Unexpected number of pulls for repo %+v", repo) - actual = getCount(t, x.Where("is_pull=? AND is_closed=?", true, true), &Issue{RepoID: repo.ID}) + actual = getCount(t, db.GetEngine(db.DefaultContext).Where("is_pull=? AND is_closed=?", true, true), &Issue{RepoID: repo.ID}) assert.EqualValues(t, repo.NumClosedPulls, actual, "Unexpected number of closed pulls for repo %+v", repo) - actual = getCount(t, x.Where("is_closed=?", true), &Milestone{RepoID: repo.ID}) + actual = getCount(t, db.GetEngine(db.DefaultContext).Where("is_closed=?", true), &Milestone{RepoID: repo.ID}) assert.EqualValues(t, repo.NumClosedMilestones, actual, "Unexpected number of closed milestones for repo %+v", repo) } func (issue *Issue) checkForConsistency(t *testing.T) { - actual := getCount(t, x.Where("type=?", CommentTypeComment), &Comment{IssueID: issue.ID}) + actual := getCount(t, db.GetEngine(db.DefaultContext).Where("type=?", CommentTypeComment), &Comment{IssueID: issue.ID}) assert.EqualValues(t, issue.NumComments, actual, "Unexpected number of comments for issue %+v", issue) if issue.IsPull { - pr := AssertExistsAndLoadBean(t, &PullRequest{IssueID: issue.ID}).(*PullRequest) + pr := db.AssertExistsAndLoadBean(t, &PullRequest{IssueID: issue.ID}).(*PullRequest) assert.EqualValues(t, pr.Index, issue.Index) } } func (pr *PullRequest) checkForConsistency(t *testing.T) { - issue := AssertExistsAndLoadBean(t, &Issue{ID: pr.IssueID}).(*Issue) + issue := db.AssertExistsAndLoadBean(t, &Issue{ID: pr.IssueID}).(*Issue) assert.True(t, issue.IsPull) assert.EqualValues(t, issue.Index, pr.Index) } @@ -138,7 +136,7 @@ func (pr *PullRequest) checkForConsistency(t *testing.T) { func (milestone *Milestone) checkForConsistency(t *testing.T) { assertCount(t, &Issue{MilestoneID: milestone.ID}, milestone.NumIssues) - actual := getCount(t, x.Where("is_closed=?", true), &Issue{MilestoneID: milestone.ID}) + actual := getCount(t, db.GetEngine(db.DefaultContext).Where("is_closed=?", true), &Issue{MilestoneID: milestone.ID}) assert.EqualValues(t, milestone.NumClosedIssues, actual, "Unexpected number of closed issues for milestone %+v", milestone) @@ -151,7 +149,7 @@ func (milestone *Milestone) checkForConsistency(t *testing.T) { func (label *Label) checkForConsistency(t *testing.T) { issueLabels := make([]*IssueLabel, 0, 10) - assert.NoError(t, x.Find(&issueLabels, &IssueLabel{LabelID: label.ID})) + assert.NoError(t, db.GetEngine(db.DefaultContext).Find(&issueLabels, &IssueLabel{LabelID: label.ID})) assert.EqualValues(t, label.NumIssues, len(issueLabels), "Unexpected number of issue for label %+v", label) @@ -162,7 +160,7 @@ func (label *Label) checkForConsistency(t *testing.T) { expected := int64(0) if len(issueIDs) > 0 { - expected = getCount(t, x.In("id", issueIDs).Where("is_closed=?", true), &Issue{}) + expected = getCount(t, db.GetEngine(db.DefaultContext).In("id", issueIDs).Where("is_closed=?", true), &Issue{}) } assert.EqualValues(t, expected, label.NumClosedIssues, "Unexpected number of closed issues for label %+v", label) @@ -174,18 +172,18 @@ func (team *Team) checkForConsistency(t *testing.T) { } func (action *Action) checkForConsistency(t *testing.T) { - repo := AssertExistsAndLoadBean(t, &Repository{ID: action.RepoID}).(*Repository) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: action.RepoID}).(*Repository) assert.Equal(t, repo.IsPrivate, action.IsPrivate, "action: %+v", action) } // CountOrphanedLabels return count of labels witch are broken and not accessible via ui anymore func CountOrphanedLabels() (int64, error) { - noref, err := x.Table("label").Where("repo_id=? AND org_id=?", 0, 0).Count("label.id") + noref, err := db.GetEngine(db.DefaultContext).Table("label").Where("repo_id=? AND org_id=?", 0, 0).Count("label.id") if err != nil { return 0, err } - norepo, err := x.Table("label"). + norepo, err := db.GetEngine(db.DefaultContext).Table("label"). Where(builder.And( builder.Gt{"repo_id": 0}, builder.NotIn("repo_id", builder.Select("id").From("repository")), @@ -195,7 +193,7 @@ func CountOrphanedLabels() (int64, error) { return 0, err } - noorg, err := x.Table("label"). + noorg, err := db.GetEngine(db.DefaultContext).Table("label"). Where(builder.And( builder.Gt{"org_id": 0}, builder.NotIn("org_id", builder.Select("id").From("user")), @@ -211,12 +209,12 @@ func CountOrphanedLabels() (int64, error) { // DeleteOrphanedLabels delete labels witch are broken and not accessible via ui anymore func DeleteOrphanedLabels() error { // delete labels with no reference - if _, err := x.Table("label").Where("repo_id=? AND org_id=?", 0, 0).Delete(new(Label)); err != nil { + if _, err := db.GetEngine(db.DefaultContext).Table("label").Where("repo_id=? AND org_id=?", 0, 0).Delete(new(Label)); err != nil { return err } // delete labels with none existing repos - if _, err := x. + if _, err := db.GetEngine(db.DefaultContext). Where(builder.And( builder.Gt{"repo_id": 0}, builder.NotIn("repo_id", builder.Select("id").From("repository")), @@ -226,7 +224,7 @@ func DeleteOrphanedLabels() error { } // delete labels with none existing orgs - if _, err := x. + if _, err := db.GetEngine(db.DefaultContext). Where(builder.And( builder.Gt{"org_id": 0}, builder.NotIn("org_id", builder.Select("id").From("user")), @@ -240,14 +238,14 @@ func DeleteOrphanedLabels() error { // CountOrphanedIssueLabels return count of IssueLabels witch have no label behind anymore func CountOrphanedIssueLabels() (int64, error) { - return x.Table("issue_label"). + return db.GetEngine(db.DefaultContext).Table("issue_label"). NotIn("label_id", builder.Select("id").From("label")). Count() } // DeleteOrphanedIssueLabels delete IssueLabels witch have no label behind anymore func DeleteOrphanedIssueLabels() error { - _, err := x. + _, err := db.GetEngine(db.DefaultContext). NotIn("label_id", builder.Select("id").From("label")). Delete(IssueLabel{}) @@ -256,7 +254,7 @@ func DeleteOrphanedIssueLabels() error { // CountOrphanedIssues count issues without a repo func CountOrphanedIssues() (int64, error) { - return x.Table("issue"). + return db.GetEngine(db.DefaultContext).Table("issue"). Join("LEFT", "repository", "issue.repo_id=repository.id"). Where(builder.IsNull{"repository.id"}). Count("id") @@ -264,15 +262,15 @@ func CountOrphanedIssues() (int64, error) { // DeleteOrphanedIssues delete issues without a repo func DeleteOrphanedIssues() error { - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { + ctx, committer, err := db.TxContext() + if err != nil { return err } + defer committer.Close() var ids []int64 - if err := sess.Table("issue").Distinct("issue.repo_id"). + if err := db.GetEngine(ctx).Table("issue").Distinct("issue.repo_id"). Join("LEFT", "repository", "issue.repo_id=repository.id"). Where(builder.IsNull{"repository.id"}).GroupBy("issue.repo_id"). Find(&ids); err != nil { @@ -281,27 +279,28 @@ func DeleteOrphanedIssues() error { var attachmentPaths []string for i := range ids { - paths, err := deleteIssuesByRepoID(sess, ids[i]) + paths, err := deleteIssuesByRepoID(db.GetEngine(ctx), ids[i]) if err != nil { return err } attachmentPaths = append(attachmentPaths, paths...) } - if err := sess.Commit(); err != nil { + if err := committer.Commit(); err != nil { return err } + committer.Close() // Remove issue attachment files. for i := range attachmentPaths { - removeAllWithNotice(x, "Delete issue attachment", attachmentPaths[i]) + removeAllWithNotice(db.GetEngine(db.DefaultContext), "Delete issue attachment", attachmentPaths[i]) } return nil } // CountOrphanedObjects count subjects with have no existing refobject anymore func CountOrphanedObjects(subject, refobject, joinCond string) (int64, error) { - return x.Table("`"+subject+"`"). + return db.GetEngine(db.DefaultContext).Table("`"+subject+"`"). Join("LEFT", refobject, joinCond). Where(builder.IsNull{"`" + refobject + "`.id"}). Count("id") @@ -317,45 +316,45 @@ func DeleteOrphanedObjects(subject, refobject, joinCond string) error { if err != nil { return err } - _, err = x.Exec(append([]interface{}{sql}, args...)...) + _, err = db.GetEngine(db.DefaultContext).Exec(append([]interface{}{sql}, args...)...) return err } // CountNullArchivedRepository counts the number of repositories with is_archived is null func CountNullArchivedRepository() (int64, error) { - return x.Where(builder.IsNull{"is_archived"}).Count(new(Repository)) + return db.GetEngine(db.DefaultContext).Where(builder.IsNull{"is_archived"}).Count(new(Repository)) } // FixNullArchivedRepository sets is_archived to false where it is null func FixNullArchivedRepository() (int64, error) { - return x.Where(builder.IsNull{"is_archived"}).Cols("is_archived").NoAutoTime().Update(&Repository{ + return db.GetEngine(db.DefaultContext).Where(builder.IsNull{"is_archived"}).Cols("is_archived").NoAutoTime().Update(&Repository{ IsArchived: false, }) } // CountWrongUserType count OrgUser who have wrong type func CountWrongUserType() (int64, error) { - return x.Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Count(new(User)) + return db.GetEngine(db.DefaultContext).Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Count(new(User)) } // FixWrongUserType fix OrgUser who have wrong type func FixWrongUserType() (int64, error) { - return x.Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Cols("type").NoAutoTime().Update(&User{Type: 1}) + return db.GetEngine(db.DefaultContext).Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Cols("type").NoAutoTime().Update(&User{Type: 1}) } // CountCommentTypeLabelWithEmptyLabel count label comments with empty label func CountCommentTypeLabelWithEmptyLabel() (int64, error) { - return x.Where(builder.Eq{"type": CommentTypeLabel, "label_id": 0}).Count(new(Comment)) + return db.GetEngine(db.DefaultContext).Where(builder.Eq{"type": CommentTypeLabel, "label_id": 0}).Count(new(Comment)) } // FixCommentTypeLabelWithEmptyLabel count label comments with empty label func FixCommentTypeLabelWithEmptyLabel() (int64, error) { - return x.Where(builder.Eq{"type": CommentTypeLabel, "label_id": 0}).Delete(new(Comment)) + return db.GetEngine(db.DefaultContext).Where(builder.Eq{"type": CommentTypeLabel, "label_id": 0}).Delete(new(Comment)) } // CountCommentTypeLabelWithOutsideLabels count label comments with outside label func CountCommentTypeLabelWithOutsideLabels() (int64, error) { - return x.Where("comment.type = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id))", CommentTypeLabel). + return db.GetEngine(db.DefaultContext).Where("comment.type = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id))", CommentTypeLabel). Table("comment"). Join("inner", "label", "label.id = comment.label_id"). Join("inner", "issue", "issue.id = comment.issue_id "). @@ -365,7 +364,7 @@ func CountCommentTypeLabelWithOutsideLabels() (int64, error) { // FixCommentTypeLabelWithOutsideLabels count label comments with outside label func FixCommentTypeLabelWithOutsideLabels() (int64, error) { - res, err := x.Exec(`DELETE FROM comment WHERE comment.id IN ( + res, err := db.GetEngine(db.DefaultContext).Exec(`DELETE FROM comment WHERE comment.id IN ( SELECT il_too.id FROM ( SELECT com.id FROM comment AS com @@ -384,7 +383,7 @@ func FixCommentTypeLabelWithOutsideLabels() (int64, error) { // CountIssueLabelWithOutsideLabels count label comments with outside label func CountIssueLabelWithOutsideLabels() (int64, error) { - return x.Where(builder.Expr("(label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)")). + return db.GetEngine(db.DefaultContext).Where(builder.Expr("(label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)")). Table("issue_label"). Join("inner", "label", "issue_label.label_id = label.id "). Join("inner", "issue", "issue.id = issue_label.issue_id "). @@ -394,7 +393,7 @@ func CountIssueLabelWithOutsideLabels() (int64, error) { // FixIssueLabelWithOutsideLabels fix label comments with outside label func FixIssueLabelWithOutsideLabels() (int64, error) { - res, err := x.Exec(`DELETE FROM issue_label WHERE issue_label.id IN ( + res, err := db.GetEngine(db.DefaultContext).Exec(`DELETE FROM issue_label WHERE issue_label.id IN ( SELECT il_too.id FROM ( SELECT il_too_too.id FROM issue_label AS il_too_too @@ -411,61 +410,3 @@ func FixIssueLabelWithOutsideLabels() (int64, error) { return res.RowsAffected() } - -// CountBadSequences looks for broken sequences from recreate-table mistakes -func CountBadSequences() (int64, error) { - if !setting.Database.UsePostgreSQL { - return 0, nil - } - - sess := x.NewSession() - defer sess.Close() - - var sequences []string - schema := sess.Engine().Dialect().URI().Schema - - sess.Engine().SetSchema("") - if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE 'tmp_recreate__%_id_seq%' AND sequence_catalog = ?", setting.Database.Name).Find(&sequences); err != nil { - return 0, err - } - sess.Engine().SetSchema(schema) - - return int64(len(sequences)), nil -} - -// FixBadSequences fixes for broken sequences from recreate-table mistakes -func FixBadSequences() error { - if !setting.Database.UsePostgreSQL { - return nil - } - - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { - return err - } - - var sequences []string - schema := sess.Engine().Dialect().URI().Schema - - sess.Engine().SetSchema("") - if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE 'tmp_recreate__%_id_seq%' AND sequence_catalog = ?", setting.Database.Name).Find(&sequences); err != nil { - return err - } - sess.Engine().SetSchema(schema) - - sequenceRegexp := regexp.MustCompile(`tmp_recreate__(\w+)_id_seq.*`) - - for _, sequence := range sequences { - tableName := sequenceRegexp.FindStringSubmatch(sequence)[1] - newSequenceName := tableName + "_id_seq" - if _, err := sess.Exec(fmt.Sprintf("ALTER SEQUENCE `%s` RENAME TO `%s`", sequence, newSequenceName)); err != nil { - return err - } - if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', COALESCE((SELECT MAX(id)+1 FROM `%s`), 1), false)", newSequenceName, tableName)); err != nil { - return err - } - } - - return sess.Commit() -} diff --git a/models/consistency_test.go b/models/consistency_test.go index 0a91d9f3dae6e..8332b5d76191e 100644 --- a/models/consistency_test.go +++ b/models/consistency_test.go @@ -7,16 +7,17 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestDeleteOrphanedObjects(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - countBefore, err := x.Count(&PullRequest{}) + countBefore, err := db.GetEngine(db.DefaultContext).Count(&PullRequest{}) assert.NoError(t, err) - _, err = x.Insert(&PullRequest{IssueID: 1000}, &PullRequest{IssueID: 1001}, &PullRequest{IssueID: 1003}) + _, err = db.GetEngine(db.DefaultContext).Insert(&PullRequest{IssueID: 1000}, &PullRequest{IssueID: 1001}, &PullRequest{IssueID: 1003}) assert.NoError(t, err) orphaned, err := CountOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id") @@ -26,7 +27,7 @@ func TestDeleteOrphanedObjects(t *testing.T) { err = DeleteOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id") assert.NoError(t, err) - countAfter, err := x.Count(&PullRequest{}) + countAfter, err := db.GetEngine(db.DefaultContext).Count(&PullRequest{}) assert.NoError(t, err) assert.EqualValues(t, countBefore, countAfter) } diff --git a/models/context.go b/models/context.go deleted file mode 100644 index a074d068343f1..0000000000000 --- a/models/context.go +++ /dev/null @@ -1,74 +0,0 @@ -// 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 models - -import ( - "code.gitea.io/gitea/modules/setting" - - "xorm.io/builder" -) - -// DBContext represents a db context -type DBContext struct { - e Engine -} - -// DefaultDBContext represents a DBContext with default Engine -func DefaultDBContext() DBContext { - return DBContext{x} -} - -// Committer represents an interface to Commit or Close the dbcontext -type Committer interface { - Commit() error - Close() error -} - -// TxDBContext represents a transaction DBContext -func TxDBContext() (DBContext, Committer, error) { - sess := x.NewSession() - if err := sess.Begin(); err != nil { - sess.Close() - return DBContext{}, nil, err - } - - return DBContext{sess}, sess, nil -} - -// WithContext represents executing database operations -func WithContext(f func(ctx DBContext) error) error { - return f(DBContext{x}) -} - -// WithTx represents executing database operations on a transaction -func WithTx(f func(ctx DBContext) error) error { - sess := x.NewSession() - if err := sess.Begin(); err != nil { - sess.Close() - return err - } - - if err := f(DBContext{sess}); err != nil { - sess.Close() - return err - } - - err := sess.Commit() - sess.Close() - return err -} - -// Iterate iterates the databases and doing something -func Iterate(ctx DBContext, tableBean interface{}, cond builder.Cond, fun func(idx int, bean interface{}) error) error { - return ctx.e.Where(cond). - BufferSize(setting.Database.IterateBufferSize). - Iterate(tableBean, fun) -} - -// Insert inserts records into database -func Insert(ctx DBContext, beans ...interface{}) error { - _, err := ctx.e.Insert(beans...) - return err -} diff --git a/models/db/context.go b/models/db/context.go new file mode 100644 index 0000000000000..0037bb198ddee --- /dev/null +++ b/models/db/context.go @@ -0,0 +1,149 @@ +// 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 db + +import ( + "context" + + "code.gitea.io/gitea/modules/setting" + + "xorm.io/builder" + "xorm.io/xorm" +) + +// DefaultContext is the default context to run xorm queries in +// will be overwritten by Init with HammerContext +var DefaultContext context.Context + +// contextKey is a value for use with context.WithValue. +type contextKey struct { + name string +} + +// EnginedContextKey is a context key. It is used with context.Value() to get the current Engined for the context +var EnginedContextKey = &contextKey{"engined"} + +// Context represents a db context +type Context struct { + context.Context + e Engine +} + +// Engine returns db engine +func (ctx *Context) Engine() Engine { + return ctx.e +} + +// NewSession returns a new session +func (ctx *Context) NewSession() *xorm.Session { + e, ok := ctx.e.(*xorm.Engine) + if ok { + return e.NewSession() + } + return nil +} + +// Value shadows Value for context.Context but allows us to get ourselves and an Engined object +func (ctx *Context) Value(key interface{}) interface{} { + if key == EnginedContextKey { + return ctx + } + return ctx.Context.Value(key) +} + +// Engined structs provide an Engine +type Engined interface { + Engine() Engine + NewSession() *xorm.Session +} + +// GetEngine will get a db Engine from this context or return an Engine restricted to this context +func GetEngine(ctx context.Context) Engine { + if engined, ok := ctx.(Engined); ok { + return engined.Engine() + } + enginedInterface := ctx.Value(EnginedContextKey) + if enginedInterface != nil { + return enginedInterface.(Engined).Engine() + } + return x.Context(ctx) +} + +// NewSession will get a db Session from this context or return a session restricted to this context +func NewSession(ctx context.Context) *xorm.Session { + if engined, ok := ctx.(Engined); ok { + return engined.NewSession() + } + + enginedInterface := ctx.Value(EnginedContextKey) + if enginedInterface != nil { + sess := enginedInterface.(Engined).NewSession() + if sess != nil { + return sess.Context(ctx) + } + return nil + } + + return x.NewSession().Context(ctx) +} + +// Committer represents an interface to Commit or Close the Context +type Committer interface { + Commit() error + Close() error +} + +// TxContext represents a transaction Context +func TxContext() (*Context, Committer, error) { + sess := x.NewSession() + if err := sess.Begin(); err != nil { + sess.Close() + return nil, nil, err + } + + return &Context{ + Context: DefaultContext, + e: sess, + }, sess, nil +} + +// WithContext represents executing database operations +func WithContext(f func(ctx *Context) error) error { + return f(&Context{ + Context: DefaultContext, + e: x, + }) +} + +// WithTx represents executing database operations on a transaction +func WithTx(f func(ctx context.Context) error) error { + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + + if err := f(&Context{ + Context: DefaultContext, + e: sess, + }); err != nil { + return err + } + + return sess.Commit() +} + +// Iterate iterates the databases and doing something +func Iterate(ctx context.Context, tableBean interface{}, cond builder.Cond, fun func(idx int, bean interface{}) error) error { + return GetEngine(ctx).Where(cond). + BufferSize(setting.Database.IterateBufferSize). + Iterate(tableBean, fun) +} + +// Insert inserts records into database +func Insert(ctx context.Context, beans ...interface{}) error { + _, err := GetEngine(ctx).Insert(beans...) + return err +} diff --git a/models/convert.go b/models/db/convert.go similarity index 98% rename from models/convert.go rename to models/db/convert.go index 1deb7c66fbbda..bf9a74a9a4ba3 100644 --- a/models/convert.go +++ b/models/db/convert.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package db import ( "fmt" diff --git a/models/models.go b/models/db/engine.go similarity index 66% rename from models/models.go rename to models/db/engine.go index 4e1448241af86..256eb2f3fc725 100755 --- a/models/models.go +++ b/models/db/engine.go @@ -3,13 +3,14 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package db import ( "context" "database/sql" "errors" "fmt" + "io" "reflect" "strings" @@ -17,7 +18,6 @@ import ( // Needed for the MySQL driver _ "github.com/go-sql-driver/mysql" - lru "github.com/hashicorp/golang-lru" "xorm.io/xorm" "xorm.io/xorm/names" "xorm.io/xorm/schemas" @@ -29,6 +29,15 @@ import ( _ "github.com/denisenkom/go-mssqldb" ) +var ( + x *xorm.Engine + tables []interface{} + initFuncs []func() error + + // HasEngine specifies if we have a xorm.Engine + HasEngine bool +) + // Engine represents a xorm engine or session. type Engine interface { Table(tableNameOrBean interface{}) *xorm.Session @@ -51,96 +60,35 @@ type Engine interface { Desc(colNames ...string) *xorm.Session Limit(limit int, start ...int) *xorm.Session SumInt(bean interface{}, columnName string) (res int64, err error) + Sync2(...interface{}) error + Select(string) *xorm.Session + NotIn(string, ...interface{}) *xorm.Session + OrderBy(string) *xorm.Session + Exist(...interface{}) (bool, error) + Distinct(...string) *xorm.Session + Query(...interface{}) ([]map[string][]byte, error) + Cols(...string) *xorm.Session } -const ( - // When queries are broken down in parts because of the number - // of parameters, attempt to break by this amount - maxQueryParameters = 300 -) +// TableInfo returns table's information via an object +func TableInfo(v interface{}) (*schemas.Table, error) { + return x.TableInfo(v) +} -var ( - x *xorm.Engine - tables []interface{} +// DumpTables dump tables information +func DumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error { + return x.DumpTables(tables, w, tp...) +} - // HasEngine specifies if we have a xorm.Engine - HasEngine bool -) +// RegisterModel registers model, if initfunc provided, it will be invoked after data model sync +func RegisterModel(bean interface{}, initFunc ...func() error) { + tables = append(tables, bean) + if len(initFuncs) > 0 && initFunc[0] != nil { + initFuncs = append(initFuncs, initFunc[0]) + } +} func init() { - tables = append(tables, - new(User), - new(PublicKey), - new(AccessToken), - new(Repository), - new(DeployKey), - new(Collaboration), - new(Access), - new(Upload), - new(Watch), - new(Star), - new(Follow), - new(Action), - new(Issue), - new(PullRequest), - new(Comment), - new(Attachment), - new(Label), - new(IssueLabel), - new(Milestone), - new(Mirror), - new(Release), - new(LoginSource), - new(Webhook), - new(HookTask), - new(Team), - new(OrgUser), - new(TeamUser), - new(TeamRepo), - new(Notice), - new(EmailAddress), - new(Notification), - new(IssueUser), - new(LFSMetaObject), - new(TwoFactor), - new(GPGKey), - new(GPGKeyImport), - new(RepoUnit), - new(RepoRedirect), - new(ExternalLoginUser), - new(ProtectedBranch), - new(UserOpenID), - new(IssueWatch), - new(CommitStatus), - new(Stopwatch), - new(TrackedTime), - new(DeletedBranch), - new(RepoIndexerStatus), - new(IssueDependency), - new(LFSLock), - new(Reaction), - new(IssueAssignees), - new(U2FRegistration), - new(TeamUnit), - new(Review), - new(OAuth2Application), - new(OAuth2AuthorizationCode), - new(OAuth2Grant), - new(Task), - new(LanguageStat), - new(EmailHash), - new(UserRedirect), - new(Project), - new(ProjectBoard), - new(ProjectIssue), - new(Session), - new(RepoTransfer), - new(IssueIndex), - new(PushMirror), - new(RepoArchiver), - new(ProtectedTag), - ) - gonicNames := []string{"SSL", "UID"} for _, name := range gonicNames { names.LintGonicMapper[name] = true @@ -221,6 +169,11 @@ func NewEngine(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err e return err } + DefaultContext = &Context{ + Context: ctx, + e: x, + } + x.SetDefaultContext(ctx) if err = x.Ping(); err != nil { @@ -235,13 +188,10 @@ func NewEngine(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err e return fmt.Errorf("sync database struct error: %v", err) } - if setting.SuccessfulTokensCacheSize > 0 { - successfulAccessTokenCache, err = lru.New(setting.SuccessfulTokensCacheSize) - if err != nil { - return fmt.Errorf("unable to allocate AccessToken cache: %v", err) + for _, initFunc := range initFuncs { + if err := initFunc(); err != nil { + return fmt.Errorf("initFunc failed: %v", err) } - } else { - successfulAccessTokenCache = nil } return nil @@ -277,62 +227,6 @@ func NamesToBean(names ...string) ([]interface{}, error) { return beans, nil } -// Statistic contains the database statistics -type Statistic struct { - Counter struct { - User, Org, PublicKey, - Repo, Watch, Star, Action, Access, - Issue, IssueClosed, IssueOpen, - Comment, Oauth, Follow, - Mirror, Release, LoginSource, Webhook, - Milestone, Label, HookTask, - Team, UpdateTask, Attachment int64 - } -} - -// GetStatistic returns the database statistics -func GetStatistic() (stats Statistic) { - stats.Counter.User = CountUsers() - stats.Counter.Org = CountOrganizations() - stats.Counter.PublicKey, _ = x.Count(new(PublicKey)) - stats.Counter.Repo = CountRepositories(true) - stats.Counter.Watch, _ = x.Count(new(Watch)) - stats.Counter.Star, _ = x.Count(new(Star)) - stats.Counter.Action, _ = x.Count(new(Action)) - stats.Counter.Access, _ = x.Count(new(Access)) - - type IssueCount struct { - Count int64 - IsClosed bool - } - issueCounts := []IssueCount{} - - _ = x.Select("COUNT(*) AS count, is_closed").Table("issue").GroupBy("is_closed").Find(&issueCounts) - for _, c := range issueCounts { - if c.IsClosed { - stats.Counter.IssueClosed = c.Count - } else { - stats.Counter.IssueOpen = c.Count - } - } - - stats.Counter.Issue = stats.Counter.IssueClosed + stats.Counter.IssueOpen - - stats.Counter.Comment, _ = x.Count(new(Comment)) - stats.Counter.Oauth = 0 - stats.Counter.Follow, _ = x.Count(new(Follow)) - stats.Counter.Mirror, _ = x.Count(new(Mirror)) - stats.Counter.Release, _ = x.Count(new(Release)) - stats.Counter.LoginSource = CountLoginSources() - stats.Counter.Webhook, _ = x.Count(new(Webhook)) - stats.Counter.Milestone, _ = x.Count(new(Milestone)) - stats.Counter.Label, _ = x.Count(new(Label)) - stats.Counter.HookTask, _ = x.Count(new(HookTask)) - stats.Counter.Team, _ = x.Count(new(Team)) - stats.Counter.Attachment, _ = x.Count(new(Attachment)) - return -} - // Ping tests if database is alive func Ping() error { if x != nil { diff --git a/models/index.go b/models/db/index.go similarity index 87% rename from models/index.go rename to models/db/index.go index 121a149ca0fbd..0086a8f548061 100644 --- a/models/index.go +++ b/models/db/index.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package db import ( "errors" @@ -18,11 +18,8 @@ type ResourceIndex struct { MaxIndex int64 `xorm:"index"` } -// IssueIndex represents the issue index table -type IssueIndex ResourceIndex - -// upsertResourceIndex the function will not return until it acquires the lock or receives an error. -func upsertResourceIndex(e Engine, tableName string, groupID int64) (err error) { +// UpsertResourceIndex the function will not return until it acquires the lock or receives an error. +func UpsertResourceIndex(e Engine, tableName string, groupID int64) (err error) { // An atomic UPSERT operation (INSERT/UPDATE) is the only operation // that ensures that the key is actually locked. switch { @@ -57,12 +54,13 @@ var ( ) const ( - maxDupIndexAttempts = 3 + // MaxDupIndexAttempts max retry times to create index + MaxDupIndexAttempts = 3 ) // GetNextResourceIndex retried 3 times to generate a resource index func GetNextResourceIndex(tableName string, groupID int64) (int64, error) { - for i := 0; i < maxDupIndexAttempts; i++ { + for i := 0; i < MaxDupIndexAttempts; i++ { idx, err := getNextResourceIndex(tableName, groupID) if err == ErrResouceOutdated { continue @@ -75,8 +73,8 @@ func GetNextResourceIndex(tableName string, groupID int64) (int64, error) { return 0, ErrGetResourceIndexFailed } -// deleteResouceIndex delete resource index -func deleteResouceIndex(e Engine, tableName string, groupID int64) error { +// DeleteResouceIndex delete resource index +func DeleteResouceIndex(e Engine, tableName string, groupID int64) error { _, err := e.Exec(fmt.Sprintf("DELETE FROM %s WHERE group_id=?", tableName), groupID) return err } @@ -94,7 +92,7 @@ func getNextResourceIndex(tableName string, groupID int64) (int64, error) { return 0, err } - if err := upsertResourceIndex(sess, tableName, groupID); err != nil { + if err := UpsertResourceIndex(sess, tableName, groupID); err != nil { return 0, err } diff --git a/models/db/list_options.go b/models/db/list_options.go new file mode 100644 index 0000000000000..f31febfe25a1d --- /dev/null +++ b/models/db/list_options.go @@ -0,0 +1,100 @@ +// Copyright 2020 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 db + +import ( + "code.gitea.io/gitea/modules/setting" + + "xorm.io/xorm" +) + +// Paginator is the base for different ListOptions types +type Paginator interface { + GetSkipTake() (skip, take int) + GetStartEnd() (start, end int) +} + +// GetPaginatedSession creates a paginated database session +func GetPaginatedSession(p Paginator) *xorm.Session { + skip, take := p.GetSkipTake() + + return x.Limit(take, skip) +} + +// SetSessionPagination sets pagination for a database session +func SetSessionPagination(sess *xorm.Session, p Paginator) *xorm.Session { + skip, take := p.GetSkipTake() + + return sess.Limit(take, skip) +} + +// SetEnginePagination sets pagination for a database engine +func SetEnginePagination(e Engine, p Paginator) Engine { + skip, take := p.GetSkipTake() + + return e.Limit(take, skip) +} + +// ListOptions options to paginate results +type ListOptions struct { + PageSize int + Page int // start from 1 +} + +// GetSkipTake returns the skip and take values +func (opts *ListOptions) GetSkipTake() (skip, take int) { + opts.SetDefaultValues() + return (opts.Page - 1) * opts.PageSize, opts.PageSize +} + +// GetStartEnd returns the start and end of the ListOptions +func (opts *ListOptions) GetStartEnd() (start, end int) { + start, take := opts.GetSkipTake() + end = start + take + return +} + +// SetDefaultValues sets default values +func (opts *ListOptions) SetDefaultValues() { + if opts.PageSize <= 0 { + opts.PageSize = setting.API.DefaultPagingNum + } + if opts.PageSize > setting.API.MaxResponseItems { + opts.PageSize = setting.API.MaxResponseItems + } + if opts.Page <= 0 { + opts.Page = 1 + } +} + +// AbsoluteListOptions absolute options to paginate results +type AbsoluteListOptions struct { + skip int + take int +} + +// NewAbsoluteListOptions creates a list option with applied limits +func NewAbsoluteListOptions(skip, take int) *AbsoluteListOptions { + if skip < 0 { + skip = 0 + } + if take <= 0 { + take = setting.API.DefaultPagingNum + } + if take > setting.API.MaxResponseItems { + take = setting.API.MaxResponseItems + } + return &AbsoluteListOptions{skip, take} +} + +// GetSkipTake returns the skip and take values +func (opts *AbsoluteListOptions) GetSkipTake() (skip, take int) { + return opts.skip, opts.take +} + +// GetStartEnd returns the start and end values +func (opts *AbsoluteListOptions) GetStartEnd() (start, end int) { + return opts.skip, opts.skip + opts.take +} diff --git a/models/db/list_options_test.go b/models/db/list_options_test.go new file mode 100644 index 0000000000000..2c860afdfbdd7 --- /dev/null +++ b/models/db/list_options_test.go @@ -0,0 +1,62 @@ +// Copyright 2021 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 db + +import ( + "testing" + + "code.gitea.io/gitea/modules/setting" + + "github.com/stretchr/testify/assert" +) + +func TestPaginator(t *testing.T) { + cases := []struct { + Paginator + Skip int + Take int + Start int + End int + }{ + { + Paginator: &ListOptions{Page: -1, PageSize: -1}, + Skip: 0, + Take: setting.API.DefaultPagingNum, + Start: 0, + End: setting.API.DefaultPagingNum, + }, + { + Paginator: &ListOptions{Page: 2, PageSize: 10}, + Skip: 10, + Take: 10, + Start: 10, + End: 20, + }, + { + Paginator: NewAbsoluteListOptions(-1, -1), + Skip: 0, + Take: setting.API.DefaultPagingNum, + Start: 0, + End: setting.API.DefaultPagingNum, + }, + { + Paginator: NewAbsoluteListOptions(2, 10), + Skip: 2, + Take: 10, + Start: 2, + End: 12, + }, + } + + for _, c := range cases { + skip, take := c.Paginator.GetSkipTake() + start, end := c.Paginator.GetStartEnd() + + assert.Equal(t, c.Skip, skip) + assert.Equal(t, c.Take, take) + assert.Equal(t, c.Start, start) + assert.Equal(t, c.End, end) + } +} diff --git a/models/log.go b/models/db/log.go similarity index 99% rename from models/log.go rename to models/db/log.go index caeab094bd132..f9febf440e2b2 100644 --- a/models/log.go +++ b/models/db/log.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package db import ( "fmt" diff --git a/models/db/main_test.go b/models/db/main_test.go new file mode 100644 index 0000000000000..f34ff65813c1f --- /dev/null +++ b/models/db/main_test.go @@ -0,0 +1,14 @@ +// Copyright 2021 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 db + +import ( + "path/filepath" + "testing" +) + +func TestMain(m *testing.M) { + MainTest(m, filepath.Join("..", "..")) +} diff --git a/models/db/sequence.go b/models/db/sequence.go new file mode 100644 index 0000000000000..48e4a8f1ac41b --- /dev/null +++ b/models/db/sequence.go @@ -0,0 +1,70 @@ +// Copyright 2018 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 db + +import ( + "fmt" + "regexp" + + "code.gitea.io/gitea/modules/setting" +) + +// CountBadSequences looks for broken sequences from recreate-table mistakes +func CountBadSequences() (int64, error) { + if !setting.Database.UsePostgreSQL { + return 0, nil + } + + sess := x.NewSession() + defer sess.Close() + + var sequences []string + schema := x.Dialect().URI().Schema + + sess.Engine().SetSchema("") + if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE 'tmp_recreate__%_id_seq%' AND sequence_catalog = ?", setting.Database.Name).Find(&sequences); err != nil { + return 0, err + } + sess.Engine().SetSchema(schema) + + return int64(len(sequences)), nil +} + +// FixBadSequences fixes for broken sequences from recreate-table mistakes +func FixBadSequences() error { + if !setting.Database.UsePostgreSQL { + return nil + } + + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + + var sequences []string + schema := sess.Engine().Dialect().URI().Schema + + sess.Engine().SetSchema("") + if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE 'tmp_recreate__%_id_seq%' AND sequence_catalog = ?", setting.Database.Name).Find(&sequences); err != nil { + return err + } + sess.Engine().SetSchema(schema) + + sequenceRegexp := regexp.MustCompile(`tmp_recreate__(\w+)_id_seq.*`) + + for _, sequence := range sequences { + tableName := sequenceRegexp.FindStringSubmatch(sequence)[1] + newSequenceName := tableName + "_id_seq" + if _, err := sess.Exec(fmt.Sprintf("ALTER SEQUENCE `%s` RENAME TO `%s`", sequence, newSequenceName)); err != nil { + return err + } + if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', COALESCE((SELECT MAX(id)+1 FROM `%s`), 1), false)", newSequenceName, tableName)); err != nil { + return err + } + } + + return sess.Commit() +} diff --git a/models/sql_postgres_with_schema.go b/models/db/sql_postgres_with_schema.go similarity index 99% rename from models/sql_postgres_with_schema.go rename to models/db/sql_postgres_with_schema.go index 0c8893ea275ca..d6b6262927c4a 100644 --- a/models/sql_postgres_with_schema.go +++ b/models/db/sql_postgres_with_schema.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package db import ( "database/sql" diff --git a/models/store.go b/models/db/store.go similarity index 88% rename from models/store.go rename to models/db/store.go index e8eba28fb6151..d5931e9521dd7 100644 --- a/models/store.go +++ b/models/db/store.go @@ -2,9 +2,11 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package db -import "github.com/lafriks/xormstore" +import ( + "github.com/lafriks/xormstore" +) // CreateStore creates a xormstore for the provided table and key func CreateStore(table, key string) (*xormstore.Store, error) { diff --git a/models/test_fixtures.go b/models/db/test_fixtures.go similarity index 91% rename from models/test_fixtures.go rename to models/db/test_fixtures.go index c1f84c66179d6..2715b688ea356 100644 --- a/models/test_fixtures.go +++ b/models/db/test_fixtures.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package db import ( "fmt" @@ -17,13 +17,18 @@ import ( var fixtures *testfixtures.Loader // InitFixtures initialize test fixtures for a test database -func InitFixtures(dir string, engine ...*xorm.Engine) (err error) { +func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) { e := x if len(engine) == 1 { e = engine[0] } - testfiles := testfixtures.Directory(dir) + var testfiles func(*testfixtures.Loader) error + if opts.Dir != "" { + testfiles = testfixtures.Directory(opts.Dir) + } else { + testfiles = testfixtures.Files(opts.Files...) + } dialect := "unknown" switch e.Dialect().URI().DBType { case schemas.POSTGRES: diff --git a/models/unit_tests.go b/models/db/unit_tests.go similarity index 85% rename from models/unit_tests.go rename to models/db/unit_tests.go index f8d6819333611..d81610df6b6ba 100644 --- a/models/unit_tests.go +++ b/models/db/unit_tests.go @@ -2,11 +2,11 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package db import ( + "context" "fmt" - "io/ioutil" "math" "net/url" "os" @@ -32,6 +32,11 @@ var ( fixturesDir string ) +// FixturesDir returns the fixture directory +func FixturesDir() string { + return fixturesDir +} + func fatalTestError(fmtStr string, args ...interface{}) { fmt.Fprintf(os.Stderr, fmtStr, args...) os.Exit(1) @@ -39,11 +44,21 @@ func fatalTestError(fmtStr string, args ...interface{}) { // MainTest a reusable TestMain(..) function for unit tests that need to use a // test database. Creates the test database, and sets necessary settings. -func MainTest(m *testing.M, pathToGiteaRoot string) { +func MainTest(m *testing.M, pathToGiteaRoot string, fixtureFiles ...string) { var err error giteaRoot = pathToGiteaRoot fixturesDir = filepath.Join(pathToGiteaRoot, "models", "fixtures") - if err = CreateTestEngine(fixturesDir); err != nil { + + var opts FixturesOptions + if len(fixtureFiles) == 0 { + opts.Dir = fixturesDir + } else { + for _, f := range fixtureFiles { + opts.Files = append(opts.Files, filepath.Join(fixturesDir, f)) + } + } + + if err = CreateTestEngine(opts); err != nil { fatalTestError("Error creating test engine: %v\n", err) } @@ -52,11 +67,11 @@ func MainTest(m *testing.M, pathToGiteaRoot string) { setting.SSH.Port = 3000 setting.SSH.Domain = "try.gitea.io" setting.Database.UseSQLite3 = true - setting.RepoRootPath, err = ioutil.TempDir(os.TempDir(), "repos") + setting.RepoRootPath, err = os.MkdirTemp(os.TempDir(), "repos") if err != nil { fatalTestError("TempDir: %v\n", err) } - setting.AppDataPath, err = ioutil.TempDir(os.TempDir(), "appdata") + setting.AppDataPath, err = os.MkdirTemp(os.TempDir(), "appdata") if err != nil { fatalTestError("TempDir: %v\n", err) } @@ -97,8 +112,14 @@ func MainTest(m *testing.M, pathToGiteaRoot string) { os.Exit(exitStatus) } +// FixturesOptions fixtures needs to be loaded options +type FixturesOptions struct { + Dir string + Files []string +} + // CreateTestEngine creates a memory database and loads the fixture data from fixturesDir -func CreateTestEngine(fixturesDir string) error { +func CreateTestEngine(opts FixturesOptions) error { var err error x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared&_txlock=immediate") if err != nil { @@ -113,7 +134,12 @@ func CreateTestEngine(fixturesDir string) error { x.ShowSQL(true) } - return InitFixtures(fixturesDir) + DefaultContext = &Context{ + Context: context.Background(), + e: x, + } + + return InitFixtures(opts) } // PrepareTestDatabase load test fixtures into test database @@ -152,6 +178,11 @@ func whereConditions(sess *xorm.Session, conditions []interface{}) { } } +// LoadBeanIfExists loads beans from fixture database if exist +func LoadBeanIfExists(bean interface{}, conditions ...interface{}) (bool, error) { + return loadBeanIfExists(bean, conditions...) +} + func loadBeanIfExists(bean interface{}, conditions ...interface{}) (bool, error) { sess := x.NewSession() defer sess.Close() diff --git a/models/engine_test.go b/models/engine_test.go new file mode 100644 index 0000000000000..d97fc3cc197a6 --- /dev/null +++ b/models/engine_test.go @@ -0,0 +1,34 @@ +// 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 models + +import ( + "os" + "path/filepath" + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/setting" + + "github.com/stretchr/testify/assert" +) + +func TestDumpDatabase(t *testing.T) { + assert.NoError(t, db.PrepareTestDatabase()) + + dir, err := os.MkdirTemp(os.TempDir(), "dump") + assert.NoError(t, err) + + type Version struct { + ID int64 `xorm:"pk autoincr"` + Version int64 + } + assert.NoError(t, db.GetEngine(db.DefaultContext).Sync2(new(Version))) + + for _, dbName := range setting.SupportedDatabases { + dbType := setting.GetDBTypeByName(dbName) + assert.NoError(t, db.DumpDatabase(filepath.Join(dir, dbType+".sql"), dbType)) + } +} diff --git a/models/error.go b/models/error.go index fd8f2771ae25d..1179fa6eb7515 100644 --- a/models/error.go +++ b/models/error.go @@ -1836,58 +1836,6 @@ func (err ErrAttachmentNotExist) Error() string { return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID) } -// .____ .__ _________ -// | | ____ ____ |__| ____ / _____/ ____ __ _________ ____ ____ -// | | / _ \ / ___\| |/ \ \_____ \ / _ \| | \_ __ \_/ ___\/ __ \ -// | |__( <_> ) /_/ > | | \ / ( <_> ) | /| | \/\ \__\ ___/ -// |_______ \____/\___ /|__|___| / /_______ /\____/|____/ |__| \___ >___ > -// \/ /_____/ \/ \/ \/ \/ - -// ErrLoginSourceNotExist represents a "LoginSourceNotExist" kind of error. -type ErrLoginSourceNotExist struct { - ID int64 -} - -// IsErrLoginSourceNotExist checks if an error is a ErrLoginSourceNotExist. -func IsErrLoginSourceNotExist(err error) bool { - _, ok := err.(ErrLoginSourceNotExist) - return ok -} - -func (err ErrLoginSourceNotExist) Error() string { - return fmt.Sprintf("login source does not exist [id: %d]", err.ID) -} - -// ErrLoginSourceAlreadyExist represents a "LoginSourceAlreadyExist" kind of error. -type ErrLoginSourceAlreadyExist struct { - Name string -} - -// IsErrLoginSourceAlreadyExist checks if an error is a ErrLoginSourceAlreadyExist. -func IsErrLoginSourceAlreadyExist(err error) bool { - _, ok := err.(ErrLoginSourceAlreadyExist) - return ok -} - -func (err ErrLoginSourceAlreadyExist) Error() string { - return fmt.Sprintf("login source already exists [name: %s]", err.Name) -} - -// ErrLoginSourceInUse represents a "LoginSourceInUse" kind of error. -type ErrLoginSourceInUse struct { - ID int64 -} - -// IsErrLoginSourceInUse checks if an error is a ErrLoginSourceInUse. -func IsErrLoginSourceInUse(err error) bool { - _, ok := err.(ErrLoginSourceInUse) - return ok -} - -func (err ErrLoginSourceInUse) Error() string { - return fmt.Sprintf("login source is still used by some users [id: %d]", err.ID) -} - // ___________ // \__ ___/___ _____ _____ // | |_/ __ \\__ \ / \ @@ -1928,25 +1876,6 @@ func (err ErrTeamNotExist) Error() string { return fmt.Sprintf("team does not exist [org_id %d, team_id %d, name: %s]", err.OrgID, err.TeamID, err.Name) } -// -// Two-factor authentication -// - -// ErrTwoFactorNotEnrolled indicates that a user is not enrolled in two-factor authentication. -type ErrTwoFactorNotEnrolled struct { - UID int64 -} - -// IsErrTwoFactorNotEnrolled checks if an error is a ErrTwoFactorNotEnrolled. -func IsErrTwoFactorNotEnrolled(err error) bool { - _, ok := err.(ErrTwoFactorNotEnrolled) - return ok -} - -func (err ErrTwoFactorNotEnrolled) Error() string { - return fmt.Sprintf("user not enrolled in 2FA [uid: %d]", err.UID) -} - // ____ ___ .__ .___ // | | \______ | | _________ __| _/ // | | /\____ \| | / _ \__ \ / __ | @@ -2011,28 +1940,6 @@ func (err ErrExternalLoginUserNotExist) Error() string { return fmt.Sprintf("external login user link does not exists [userID: %d, loginSourceID: %d]", err.UserID, err.LoginSourceID) } -// ____ ________________________________ .__ __ __ .__ -// | | \_____ \_ _____/\______ \ ____ ____ |__| _______/ |_____________ _/ |_|__| ____ ____ -// | | // ____/| __) | _// __ \ / ___\| |/ ___/\ __\_ __ \__ \\ __\ |/ _ \ / \ -// | | // \| \ | | \ ___// /_/ > |\___ \ | | | | \// __ \| | | ( <_> ) | \ -// |______/ \_______ \___ / |____|_ /\___ >___ /|__/____ > |__| |__| (____ /__| |__|\____/|___| / -// \/ \/ \/ \/_____/ \/ \/ \/ - -// ErrU2FRegistrationNotExist represents a "ErrU2FRegistrationNotExist" kind of error. -type ErrU2FRegistrationNotExist struct { - ID int64 -} - -func (err ErrU2FRegistrationNotExist) Error() string { - return fmt.Sprintf("U2F registration does not exist [id: %d]", err.ID) -} - -// IsErrU2FRegistrationNotExist checks if an error is a ErrU2FRegistrationNotExist. -func IsErrU2FRegistrationNotExist(err error) bool { - _, ok := err.(ErrU2FRegistrationNotExist) - return ok -} - // .___ ________ .___ .__ // | | ______ ________ __ ____ \______ \ ____ ______ ____ ____ __| _/____ ____ ____ |__| ____ ______ // | |/ ___// ___/ | \_/ __ \ | | \_/ __ \\____ \_/ __ \ / \ / __ |/ __ \ / \_/ ___\| |/ __ \ / ___/ @@ -2159,42 +2066,3 @@ func (err ErrNotValidReviewRequest) Error() string { err.UserID, err.RepoID) } - -// ________ _____ __ .__ -// \_____ \ / _ \ __ ___/ |_| |__ -// / | \ / /_\ \| | \ __\ | \ -// / | \/ | \ | /| | | Y \ -// \_______ /\____|__ /____/ |__| |___| / -// \/ \/ \/ - -// ErrOAuthClientIDInvalid will be thrown if client id cannot be found -type ErrOAuthClientIDInvalid struct { - ClientID string -} - -// IsErrOauthClientIDInvalid checks if an error is a ErrReviewNotExist. -func IsErrOauthClientIDInvalid(err error) bool { - _, ok := err.(ErrOAuthClientIDInvalid) - return ok -} - -// Error returns the error message -func (err ErrOAuthClientIDInvalid) Error() string { - return fmt.Sprintf("Client ID invalid [Client ID: %s]", err.ClientID) -} - -// ErrOAuthApplicationNotFound will be thrown if id cannot be found -type ErrOAuthApplicationNotFound struct { - ID int64 -} - -// IsErrOAuthApplicationNotFound checks if an error is a ErrReviewNotExist. -func IsErrOAuthApplicationNotFound(err error) bool { - _, ok := err.(ErrOAuthApplicationNotFound) - return ok -} - -// Error returns the error message -func (err ErrOAuthApplicationNotFound) Error() string { - return fmt.Sprintf("OAuth application not found [ID: %d]", err.ID) -} diff --git a/models/external_login_user.go b/models/external_login_user.go index aa5da8134a3e3..6b023a4cb2a98 100644 --- a/models/external_login_user.go +++ b/models/external_login_user.go @@ -7,6 +7,8 @@ package models import ( "time" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/structs" "github.com/markbates/goth" @@ -34,15 +36,19 @@ type ExternalLoginUser struct { ExpiresAt time.Time } +func init() { + db.RegisterModel(new(ExternalLoginUser)) +} + // GetExternalLogin checks if a externalID in loginSourceID scope already exists func GetExternalLogin(externalLoginUser *ExternalLoginUser) (bool, error) { - return x.Get(externalLoginUser) + return db.GetEngine(db.DefaultContext).Get(externalLoginUser) } // ListAccountLinks returns a map with the ExternalLoginUser and its LoginSource func ListAccountLinks(user *User) ([]*ExternalLoginUser, error) { externalAccounts := make([]*ExternalLoginUser, 0, 5) - err := x.Where("user_id=?", user.ID). + err := db.GetEngine(db.DefaultContext).Where("user_id=?", user.ID). Desc("login_source_id"). Find(&externalAccounts) if err != nil { @@ -54,7 +60,7 @@ func ListAccountLinks(user *User) ([]*ExternalLoginUser, error) { // LinkExternalToUser link the external user to the user func LinkExternalToUser(user *User, externalLoginUser *ExternalLoginUser) error { - has, err := x.Where("external_id=? AND login_source_id=?", externalLoginUser.ExternalID, externalLoginUser.LoginSourceID). + has, err := db.GetEngine(db.DefaultContext).Where("external_id=? AND login_source_id=?", externalLoginUser.ExternalID, externalLoginUser.LoginSourceID). NoAutoCondition(). Exist(externalLoginUser) if err != nil { @@ -63,13 +69,13 @@ func LinkExternalToUser(user *User, externalLoginUser *ExternalLoginUser) error return ErrExternalLoginUserAlreadyExist{externalLoginUser.ExternalID, user.ID, externalLoginUser.LoginSourceID} } - _, err = x.Insert(externalLoginUser) + _, err = db.GetEngine(db.DefaultContext).Insert(externalLoginUser) return err } // RemoveAccountLink will remove all external login sources for the given user func RemoveAccountLink(user *User, loginSourceID int64) (int64, error) { - deleted, err := x.Delete(&ExternalLoginUser{UserID: user.ID, LoginSourceID: loginSourceID}) + deleted, err := db.GetEngine(db.DefaultContext).Delete(&ExternalLoginUser{UserID: user.ID, LoginSourceID: loginSourceID}) if err != nil { return deleted, err } @@ -80,7 +86,7 @@ func RemoveAccountLink(user *User, loginSourceID int64) (int64, error) { } // removeAllAccountLinks will remove all external login sources for the given user -func removeAllAccountLinks(e Engine, user *User) error { +func removeAllAccountLinks(e db.Engine, user *User) error { _, err := e.Delete(&ExternalLoginUser{UserID: user.ID}) return err } @@ -88,7 +94,7 @@ func removeAllAccountLinks(e Engine, user *User) error { // GetUserIDByExternalUserID get user id according to provider and userID func GetUserIDByExternalUserID(provider, userID string) (int64, error) { var id int64 - _, err := x.Table("external_login_user"). + _, err := db.GetEngine(db.DefaultContext).Table("external_login_user"). Select("user_id"). Where("provider=?", provider). And("external_id=?", userID). @@ -101,7 +107,7 @@ func GetUserIDByExternalUserID(provider, userID string) (int64, error) { // UpdateExternalUser updates external user's information func UpdateExternalUser(user *User, gothUser goth.User) error { - loginSource, err := GetActiveOAuth2LoginSourceByName(gothUser.Provider) + loginSource, err := login.GetActiveOAuth2LoginSourceByName(gothUser.Provider) if err != nil { return err } @@ -125,7 +131,7 @@ func UpdateExternalUser(user *User, gothUser goth.User) error { ExpiresAt: gothUser.ExpiresAt, } - has, err := x.Where("external_id=? AND login_source_id=?", gothUser.UserID, loginSource.ID). + has, err := db.GetEngine(db.DefaultContext).Where("external_id=? AND login_source_id=?", gothUser.UserID, loginSource.ID). NoAutoCondition(). Exist(externalLoginUser) if err != nil { @@ -134,7 +140,7 @@ func UpdateExternalUser(user *User, gothUser goth.User) error { return ErrExternalLoginUserNotExist{user.ID, loginSource.ID} } - _, err = x.Where("external_id=? AND login_source_id=?", gothUser.UserID, loginSource.ID).AllCols().Update(externalLoginUser) + _, err = db.GetEngine(db.DefaultContext).Where("external_id=? AND login_source_id=?", gothUser.UserID, loginSource.ID).AllCols().Update(externalLoginUser) return err } @@ -156,7 +162,7 @@ func (opts FindExternalUserOptions) toConds() builder.Cond { // FindExternalUsersByProvider represents external users via provider func FindExternalUsersByProvider(opts FindExternalUserOptions) ([]ExternalLoginUser, error) { var users []ExternalLoginUser - err := x.Where(opts.toConds()). + err := db.GetEngine(db.DefaultContext).Where(opts.toConds()). Limit(opts.Limit, opts.Start). OrderBy("login_source_id ASC, external_id ASC"). Find(&users) diff --git a/models/fixture_generation.go b/models/fixture_generation.go index 64cf16036515f..c87909d01ba52 100644 --- a/models/fixture_generation.go +++ b/models/fixture_generation.go @@ -7,13 +7,15 @@ package models import ( "fmt" "strings" + + "code.gitea.io/gitea/models/db" ) // GetYamlFixturesAccess returns a string containing the contents // for the access table, as recalculated using repo.RecalculateAccesses() func GetYamlFixturesAccess() (string, error) { repos := make([]*Repository, 0, 50) - if err := x.Find(&repos); err != nil { + if err := db.GetEngine(db.DefaultContext).Find(&repos); err != nil { return "", err } @@ -27,7 +29,7 @@ func GetYamlFixturesAccess() (string, error) { var b strings.Builder accesses := make([]*Access, 0, 200) - if err := x.OrderBy("user_id, repo_id").Find(&accesses); err != nil { + if err := db.GetEngine(db.DefaultContext).OrderBy("user_id, repo_id").Find(&accesses); err != nil { return "", err } diff --git a/models/fixture_test.go b/models/fixture_test.go index 450e374f86db4..3c6ebc06850d1 100644 --- a/models/fixture_test.go +++ b/models/fixture_test.go @@ -5,24 +5,25 @@ package models import ( - "io/ioutil" + "os" "path/filepath" "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) func TestFixtureGeneration(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(gen func() (string, error), name string) { expected, err := gen() if !assert.NoError(t, err) { return } - bytes, err := ioutil.ReadFile(filepath.Join(fixturesDir, name+".yml")) + bytes, err := os.ReadFile(filepath.Join(db.FixturesDir(), name+".yml")) if !assert.NoError(t, err) { return } diff --git a/models/fixtures/commit_status_index.yml b/models/fixtures/commit_status_index.yml new file mode 100644 index 0000000000000..3f252e87ef092 --- /dev/null +++ b/models/fixtures/commit_status_index.yml @@ -0,0 +1,5 @@ +- + id: 1 + repo_id: 1 + sha: "1234123412341234123412341234123412341234" + max_index: 5 \ No newline at end of file diff --git a/models/gpg_key.go b/models/gpg_key.go index 1072813b1b71c..a62ed61966eda 100644 --- a/models/gpg_key.go +++ b/models/gpg_key.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" @@ -43,6 +44,10 @@ type GPGKey struct { CanCertify bool } +func init() { + db.RegisterModel(new(GPGKey)) +} + // BeforeInsert will be invoked by XORM before inserting a record func (key *GPGKey) BeforeInsert() { key.AddedUnix = timeutil.TimeStampNow() @@ -57,14 +62,14 @@ func (key *GPGKey) AfterLoad(session *xorm.Session) { } // ListGPGKeys returns a list of public keys belongs to given user. -func ListGPGKeys(uid int64, listOptions ListOptions) ([]*GPGKey, error) { - return listGPGKeys(x, uid, listOptions) +func ListGPGKeys(uid int64, listOptions db.ListOptions) ([]*GPGKey, error) { + return listGPGKeys(db.GetEngine(db.DefaultContext), uid, listOptions) } -func listGPGKeys(e Engine, uid int64, listOptions ListOptions) ([]*GPGKey, error) { +func listGPGKeys(e db.Engine, uid int64, listOptions db.ListOptions) ([]*GPGKey, error) { sess := e.Table(&GPGKey{}).Where("owner_id=? AND primary_key_id=''", uid) if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, &listOptions) } keys := make([]*GPGKey, 0, 2) @@ -73,13 +78,13 @@ func listGPGKeys(e Engine, uid int64, listOptions ListOptions) ([]*GPGKey, error // CountUserGPGKeys return number of gpg keys a user own func CountUserGPGKeys(userID int64) (int64, error) { - return x.Where("owner_id=? AND primary_key_id=''", userID).Count(&GPGKey{}) + return db.GetEngine(db.DefaultContext).Where("owner_id=? AND primary_key_id=''", userID).Count(&GPGKey{}) } // GetGPGKeyByID returns public key by given ID. func GetGPGKeyByID(keyID int64) (*GPGKey, error) { key := new(GPGKey) - has, err := x.ID(keyID).Get(key) + has, err := db.GetEngine(db.DefaultContext).ID(keyID).Get(key) if err != nil { return nil, err } else if !has { @@ -91,7 +96,7 @@ func GetGPGKeyByID(keyID int64) (*GPGKey, error) { // GetGPGKeysByKeyID returns public key by given ID. func GetGPGKeysByKeyID(keyID string) ([]*GPGKey, error) { keys := make([]*GPGKey, 0, 1) - return keys, x.Where("key_id=?", keyID).Find(&keys) + return keys, db.GetEngine(db.DefaultContext).Where("key_id=?", keyID).Find(&keys) } // GPGKeyToEntity retrieve the imported key and the traducted entity @@ -195,7 +200,7 @@ func parseGPGKey(ownerID int64, e *openpgp.Entity, verified bool) (*GPGKey, erro } // deleteGPGKey does the actual key deletion -func deleteGPGKey(e *xorm.Session, keyID string) (int64, error) { +func deleteGPGKey(e db.Engine, keyID string) (int64, error) { if keyID == "" { return 0, fmt.Errorf("empty KeyId forbidden") // Should never happen but just to be sure } @@ -222,17 +227,17 @@ func DeleteGPGKey(doer *User, id int64) (err error) { return ErrGPGKeyAccessDenied{doer.ID, key.ID} } - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { + ctx, committer, err := db.TxContext() + if err != nil { return err } + defer committer.Close() - if _, err = deleteGPGKey(sess, key.KeyID); err != nil { + if _, err = deleteGPGKey(db.GetEngine(ctx), key.KeyID); err != nil { return err } - return sess.Commit() + return committer.Commit() } func checkKeyEmails(email string, keys ...*GPGKey) (bool, string) { diff --git a/models/gpg_key_add.go b/models/gpg_key_add.go index 1e589e7fee527..711fc86dee5f2 100644 --- a/models/gpg_key_add.go +++ b/models/gpg_key_add.go @@ -7,6 +7,7 @@ package models import ( "strings" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "github.com/keybase/go-crypto/openpgp" @@ -28,7 +29,7 @@ import ( // This file contains functions relating to adding GPG Keys // addGPGKey add key, import and subkeys to database -func addGPGKey(e Engine, key *GPGKey, content string) (err error) { +func addGPGKey(e db.Engine, key *GPGKey, content string) (err error) { // Add GPGKeyImport if _, err = e.Insert(GPGKeyImport{ KeyID: key.KeyID, @@ -50,7 +51,7 @@ func addGPGKey(e Engine, key *GPGKey, content string) (err error) { } // addGPGSubKey add subkeys to database -func addGPGSubKey(e Engine, key *GPGKey) (err error) { +func addGPGSubKey(e db.Engine, key *GPGKey) (err error) { // Save GPG primary key. if _, err = e.Insert(key); err != nil { return err @@ -71,11 +72,12 @@ func AddGPGKey(ownerID int64, content, token, signature string) ([]*GPGKey, erro return nil, err } - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { + ctx, committer, err := db.TxContext() + if err != nil { return nil, err } + defer committer.Close() + keys := make([]*GPGKey, 0, len(ekeys)) verified := false @@ -99,9 +101,49 @@ func AddGPGKey(ownerID int64, content, token, signature string) ([]*GPGKey, erro verified = true } + if len(ekeys) > 1 { + id2key := map[string]*openpgp.Entity{} + newEKeys := make([]*openpgp.Entity, 0, len(ekeys)) + for _, ekey := range ekeys { + id := ekey.PrimaryKey.KeyIdString() + if original, has := id2key[id]; has { + // Coalesce this with the other one + for _, subkey := range ekey.Subkeys { + if subkey.PublicKey == nil { + continue + } + found := false + + for _, originalSubkey := range original.Subkeys { + if originalSubkey.PublicKey == nil { + continue + } + if originalSubkey.PublicKey.KeyId == subkey.PublicKey.KeyId { + found = true + break + } + } + if !found { + original.Subkeys = append(original.Subkeys, subkey) + } + } + for name, identity := range ekey.Identities { + if _, has := original.Identities[name]; has { + continue + } + original.Identities[name] = identity + } + continue + } + id2key[id] = ekey + newEKeys = append(newEKeys, ekey) + } + ekeys = newEKeys + } + for _, ekey := range ekeys { // Key ID cannot be duplicated. - has, err := sess.Where("key_id=?", ekey.PrimaryKey.KeyIdString()). + has, err := db.GetEngine(ctx).Where("key_id=?", ekey.PrimaryKey.KeyIdString()). Get(new(GPGKey)) if err != nil { return nil, err @@ -116,10 +158,10 @@ func AddGPGKey(ownerID int64, content, token, signature string) ([]*GPGKey, erro return nil, err } - if err = addGPGKey(sess, key, content); err != nil { + if err = addGPGKey(db.GetEngine(ctx), key, content); err != nil { return nil, err } keys = append(keys, key) } - return keys, sess.Commit() + return keys, committer.Commit() } diff --git a/models/gpg_key_commit_verification.go b/models/gpg_key_commit_verification.go index a4c7d702850fa..f508303a0965c 100644 --- a/models/gpg_key_commit_verification.go +++ b/models/gpg_key_commit_verification.go @@ -9,6 +9,7 @@ import ( "hash" "strings" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -156,7 +157,7 @@ func ParseCommitWithSignature(c *git.Commit) *CommitVerification { // Now try to associate the signature with the committer, if present if committer.ID != 0 { - keys, err := ListGPGKeys(committer.ID, ListOptions{}) + keys, err := ListGPGKeys(committer.ID, db.ListOptions{}) if err != nil { // Skipping failed to get gpg keys of user log.Error("ListGPGKeys: %v", err) return &CommitVerification{ diff --git a/models/gpg_key_import.go b/models/gpg_key_import.go index bd1d530eca26b..1eed9296272b3 100644 --- a/models/gpg_key_import.go +++ b/models/gpg_key_import.go @@ -4,6 +4,8 @@ package models +import "code.gitea.io/gitea/models/db" + // __________________ ________ ____ __. // / _____/\______ \/ _____/ | |/ _|____ ___.__. // / \ ___ | ___/ \ ___ | <_/ __ < | | @@ -25,10 +27,14 @@ type GPGKeyImport struct { Content string `xorm:"TEXT NOT NULL"` } +func init() { + db.RegisterModel(new(GPGKeyImport)) +} + // GetGPGImportByKeyID returns the import public armored key by given KeyID. func GetGPGImportByKeyID(keyID string) (*GPGKeyImport, error) { key := new(GPGKeyImport) - has, err := x.ID(keyID).Get(key) + has, err := db.GetEngine(db.DefaultContext).ID(keyID).Get(key) if err != nil { return nil, err } else if !has { diff --git a/models/gpg_key_test.go b/models/gpg_key_test.go index be2d8a223bc66..7a3cbfd67fea4 100644 --- a/models/gpg_key_test.go +++ b/models/gpg_key_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" "github.com/stretchr/testify/assert" @@ -192,9 +193,9 @@ Unknown GPG key with good email } func TestCheckGPGUserEmail(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - _ = AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + _ = db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) testEmailWithUpperCaseLetters := `-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1 diff --git a/models/gpg_key_verify.go b/models/gpg_key_verify.go index 15774dc058e86..1c6b79ec5f6af 100644 --- a/models/gpg_key_verify.go +++ b/models/gpg_key_verify.go @@ -8,6 +8,7 @@ import ( "strconv" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" ) @@ -29,15 +30,15 @@ import ( // VerifyGPGKey marks a GPG key as verified func VerifyGPGKey(ownerID int64, keyID, token, signature string) (string, error) { - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { + ctx, committer, err := db.TxContext() + if err != nil { return "", err } + defer committer.Close() key := new(GPGKey) - has, err := sess.Where("owner_id = ? AND key_id = ?", ownerID, keyID).Get(key) + has, err := db.GetEngine(ctx).Where("owner_id = ? AND key_id = ?", ownerID, keyID).Get(key) if err != nil { return "", err } else if !has { @@ -91,11 +92,11 @@ func VerifyGPGKey(ownerID int64, keyID, token, signature string) (string, error) } key.Verified = true - if _, err := sess.ID(key.ID).SetExpr("verified", true).Update(new(GPGKey)); err != nil { + if _, err := db.GetEngine(ctx).ID(key.ID).SetExpr("verified", true).Update(new(GPGKey)); err != nil { return "", err } - if err := sess.Commit(); err != nil { + if err := committer.Commit(); err != nil { return "", err } diff --git a/models/helper.go b/models/helper.go index c499b5512ddc7..710c15a978371 100644 --- a/models/helper.go +++ b/models/helper.go @@ -51,7 +51,7 @@ func JSONUnmarshalHandleDoubleEncode(bs []byte, v interface{}) error { rs = append(rs, temp...) } if ok { - if rs[0] == 0xff && rs[1] == 0xfe { + if len(rs) > 1 && rs[0] == 0xff && rs[1] == 0xfe { rs = rs[2:] } err = json.Unmarshal(rs, v) diff --git a/models/helper_directory.go b/models/helper_directory.go index aed2dbcf9b3a5..10114959ef381 100644 --- a/models/helper_directory.go +++ b/models/helper_directory.go @@ -6,7 +6,6 @@ package models import ( "fmt" - "io/ioutil" "os" "path" "path/filepath" @@ -30,7 +29,7 @@ func CreateTemporaryPath(prefix string) (string, error) { log.Error("Unable to create localcopypath directory: %s (%v)", LocalCopyPath(), err) return "", fmt.Errorf("Failed to create localcopypath directory %s: %v", LocalCopyPath(), err) } - basePath, err := ioutil.TempDir(LocalCopyPath(), prefix+".git") + basePath, err := os.MkdirTemp(LocalCopyPath(), prefix+".git") if err != nil { log.Error("Unable to create temporary directory: %s-*.git (%v)", prefix, err) return "", fmt.Errorf("Failed to create dir %s-*.git: %v", prefix, err) diff --git a/models/index_test.go b/models/index_test.go deleted file mode 100644 index 40e570ad9fa77..0000000000000 --- a/models/index_test.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2021 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 models - -import ( - "fmt" - "sync" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestResourceIndex(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - - var wg sync.WaitGroup - for i := 0; i < 100; i++ { - wg.Add(1) - go func(i int) { - testInsertIssue(t, fmt.Sprintf("issue %d", i+1), "my issue", 0) - wg.Done() - }(i) - } - wg.Wait() -} diff --git a/models/issue.go b/models/issue.go index 9e63ac4e58665..b62394919ce9a 100644 --- a/models/issue.go +++ b/models/issue.go @@ -6,16 +6,17 @@ package models import ( + "context" "fmt" "regexp" "sort" "strconv" "strings" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/references" - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" @@ -82,12 +83,18 @@ const ( issueTasksDoneRegexpStr = `(^\s*[-*]\s\[[xX]\]\s.)|(\n\s*[-*]\s\[[xX]\]\s.)` ) +// IssueIndex represents the issue index table +type IssueIndex db.ResourceIndex + func init() { issueTasksPat = regexp.MustCompile(issueTasksRegexpStr) issueTasksDonePat = regexp.MustCompile(issueTasksDoneRegexpStr) + + db.RegisterModel(new(Issue)) + db.RegisterModel(new(IssueIndex)) } -func (issue *Issue) loadTotalTimes(e Engine) (err error) { +func (issue *Issue) loadTotalTimes(e db.Engine) (err error) { opts := FindTrackedTimesOptions{IssueID: issue.ID} issue.TotalTrackedTime, err = opts.toSession(e).SumInt(&TrackedTime{}, "time") if err != nil { @@ -106,10 +113,10 @@ func (issue *Issue) IsOverdue() bool { // LoadRepo loads issue's repository func (issue *Issue) LoadRepo() error { - return issue.loadRepo(x) + return issue.loadRepo(db.GetEngine(db.DefaultContext)) } -func (issue *Issue) loadRepo(e Engine) (err error) { +func (issue *Issue) loadRepo(e db.Engine) (err error) { if issue.Repo == nil { issue.Repo, err = getRepositoryByID(e, issue.RepoID) if err != nil { @@ -121,10 +128,10 @@ func (issue *Issue) loadRepo(e Engine) (err error) { // IsTimetrackerEnabled returns true if the repo enables timetracking func (issue *Issue) IsTimetrackerEnabled() bool { - return issue.isTimetrackerEnabled(x) + return issue.isTimetrackerEnabled(db.GetEngine(db.DefaultContext)) } -func (issue *Issue) isTimetrackerEnabled(e Engine) bool { +func (issue *Issue) isTimetrackerEnabled(e db.Engine) bool { if err := issue.loadRepo(e); err != nil { log.Error(fmt.Sprintf("loadRepo: %v", err)) return false @@ -138,7 +145,7 @@ func (issue *Issue) GetPullRequest() (pr *PullRequest, err error) { return nil, fmt.Errorf("Issue is not a pull request") } - pr, err = getPullRequestByIssueID(x, issue.ID) + pr, err = getPullRequestByIssueID(db.GetEngine(db.DefaultContext), issue.ID) if err != nil { return nil, err } @@ -148,10 +155,10 @@ func (issue *Issue) GetPullRequest() (pr *PullRequest, err error) { // LoadLabels loads labels func (issue *Issue) LoadLabels() error { - return issue.loadLabels(x) + return issue.loadLabels(db.GetEngine(db.DefaultContext)) } -func (issue *Issue) loadLabels(e Engine) (err error) { +func (issue *Issue) loadLabels(e db.Engine) (err error) { if issue.Labels == nil { issue.Labels, err = getLabelsByIssueID(e, issue.ID) if err != nil { @@ -163,10 +170,10 @@ func (issue *Issue) loadLabels(e Engine) (err error) { // LoadPoster loads poster func (issue *Issue) LoadPoster() error { - return issue.loadPoster(x) + return issue.loadPoster(db.GetEngine(db.DefaultContext)) } -func (issue *Issue) loadPoster(e Engine) (err error) { +func (issue *Issue) loadPoster(e db.Engine) (err error) { if issue.Poster == nil { issue.Poster, err = getUserByID(e, issue.PosterID) if err != nil { @@ -182,7 +189,7 @@ func (issue *Issue) loadPoster(e Engine) (err error) { return } -func (issue *Issue) loadPullRequest(e Engine) (err error) { +func (issue *Issue) loadPullRequest(e db.Engine) (err error) { if issue.IsPull && issue.PullRequest == nil { issue.PullRequest, err = getPullRequestByIssueID(e, issue.ID) if err != nil { @@ -198,19 +205,19 @@ func (issue *Issue) loadPullRequest(e Engine) (err error) { // LoadPullRequest loads pull request info func (issue *Issue) LoadPullRequest() error { - return issue.loadPullRequest(x) + return issue.loadPullRequest(db.GetEngine(db.DefaultContext)) } -func (issue *Issue) loadComments(e Engine) (err error) { +func (issue *Issue) loadComments(e db.Engine) (err error) { return issue.loadCommentsByType(e, CommentTypeUnknown) } // LoadDiscussComments loads discuss comments func (issue *Issue) LoadDiscussComments() error { - return issue.loadCommentsByType(x, CommentTypeComment) + return issue.loadCommentsByType(db.GetEngine(db.DefaultContext), CommentTypeComment) } -func (issue *Issue) loadCommentsByType(e Engine, tp CommentType) (err error) { +func (issue *Issue) loadCommentsByType(e db.Engine, tp CommentType) (err error) { if issue.Comments != nil { return nil } @@ -221,7 +228,7 @@ func (issue *Issue) loadCommentsByType(e Engine, tp CommentType) (err error) { return err } -func (issue *Issue) loadReactions(e Engine) (err error) { +func (issue *Issue) loadReactions(e db.Engine) (err error) { if issue.Reactions != nil { return nil } @@ -255,7 +262,7 @@ func (issue *Issue) loadReactions(e Engine) (err error) { return nil } -func (issue *Issue) loadMilestone(e Engine) (err error) { +func (issue *Issue) loadMilestone(e db.Engine) (err error) { if (issue.Milestone == nil || issue.Milestone.ID != issue.MilestoneID) && issue.MilestoneID > 0 { issue.Milestone, err = getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID) if err != nil && !IsErrMilestoneNotExist(err) { @@ -265,7 +272,7 @@ func (issue *Issue) loadMilestone(e Engine) (err error) { return nil } -func (issue *Issue) loadAttributes(e Engine) (err error) { +func (issue *Issue) loadAttributes(e db.Engine) (err error) { if err = issue.loadRepo(e); err != nil { return } @@ -320,18 +327,18 @@ func (issue *Issue) loadAttributes(e Engine) (err error) { // LoadAttributes loads the attribute of this issue. func (issue *Issue) LoadAttributes() error { - return issue.loadAttributes(x) + return issue.loadAttributes(db.GetEngine(db.DefaultContext)) } // LoadMilestone load milestone of this issue. func (issue *Issue) LoadMilestone() error { - return issue.loadMilestone(x) + return issue.loadMilestone(db.GetEngine(db.DefaultContext)) } // GetIsRead load the `IsRead` field of the issue func (issue *Issue) GetIsRead(userID int64) error { issueUser := &IssueUser{IssueID: issue.ID, UID: userID} - if has, err := x.Get(issueUser); err != nil { + if has, err := db.GetEngine(db.DefaultContext).Get(issueUser); err != nil { return err } else if !has { issue.IsRead = false @@ -398,36 +405,24 @@ func (issue *Issue) IsPoster(uid int64) bool { return issue.OriginalAuthorID == 0 && issue.PosterID == uid } -func (issue *Issue) hasLabel(e Engine, labelID int64) bool { +func (issue *Issue) hasLabel(e db.Engine, labelID int64) bool { return hasIssueLabel(e, issue.ID, labelID) } // HasLabel returns true if issue has been labeled by given ID. func (issue *Issue) HasLabel(labelID int64) bool { - return issue.hasLabel(x, labelID) -} - -// ReplyReference returns tokenized address to use for email reply headers -func (issue *Issue) ReplyReference() string { - var path string - if issue.IsPull { - path = "pulls" - } else { - path = "issues" - } - - return fmt.Sprintf("%s/%s/%d@%s", issue.Repo.FullName(), path, issue.Index, setting.Domain) + return issue.hasLabel(db.GetEngine(db.DefaultContext), labelID) } -func (issue *Issue) addLabel(e *xorm.Session, label *Label, doer *User) error { +func (issue *Issue) addLabel(e db.Engine, label *Label, doer *User) error { return newIssueLabel(e, issue, label, doer) } -func (issue *Issue) addLabels(e *xorm.Session, labels []*Label, doer *User) error { +func (issue *Issue) addLabels(e db.Engine, labels []*Label, doer *User) error { return newIssueLabels(e, issue, labels, doer) } -func (issue *Issue) getLabels(e Engine) (err error) { +func (issue *Issue) getLabels(e db.Engine) (err error) { if len(issue.Labels) > 0 { return nil } @@ -439,11 +434,11 @@ func (issue *Issue) getLabels(e Engine) (err error) { return nil } -func (issue *Issue) removeLabel(e *xorm.Session, doer *User, label *Label) error { +func (issue *Issue) removeLabel(e db.Engine, doer *User, label *Label) error { return deleteIssueLabel(e, issue, label, doer) } -func (issue *Issue) clearLabels(e *xorm.Session, doer *User) (err error) { +func (issue *Issue) clearLabels(e db.Engine, doer *User) (err error) { if err = issue.getLabels(e); err != nil { return fmt.Errorf("getLabels: %v", err) } @@ -460,19 +455,19 @@ func (issue *Issue) clearLabels(e *xorm.Session, doer *User) (err error) { // ClearLabels removes all issue labels as the given user. // Triggers appropriate WebHooks, if any. func (issue *Issue) ClearLabels(doer *User) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { + ctx, committer, err := db.TxContext() + if err != nil { return err } + defer committer.Close() - if err := issue.loadRepo(sess); err != nil { + if err := issue.loadRepo(db.GetEngine(ctx)); err != nil { return err - } else if err = issue.loadPullRequest(sess); err != nil { + } else if err = issue.loadPullRequest(db.GetEngine(ctx)); err != nil { return err } - perm, err := getUserRepoPermission(sess, issue.Repo, doer) + perm, err := getUserRepoPermission(db.GetEngine(ctx), issue.Repo, doer) if err != nil { return err } @@ -480,11 +475,11 @@ func (issue *Issue) ClearLabels(doer *User) (err error) { return ErrRepoLabelNotExist{} } - if err = issue.clearLabels(sess, doer); err != nil { + if err = issue.clearLabels(db.GetEngine(ctx), doer); err != nil { return err } - if err = sess.Commit(); err != nil { + if err = committer.Commit(); err != nil { return fmt.Errorf("Commit: %v", err) } @@ -508,17 +503,17 @@ func (ts labelSorter) Swap(i, j int) { // ReplaceLabels removes all current labels and add new labels to the issue. // Triggers appropriate WebHooks, if any. func (issue *Issue) ReplaceLabels(labels []*Label, doer *User) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { + ctx, committer, err := db.TxContext() + if err != nil { return err } + defer committer.Close() - if err = issue.loadRepo(sess); err != nil { + if err = issue.loadRepo(db.GetEngine(ctx)); err != nil { return err } - if err = issue.loadLabels(sess); err != nil { + if err = issue.loadLabels(db.GetEngine(ctx)); err != nil { return err } @@ -554,23 +549,23 @@ func (issue *Issue) ReplaceLabels(labels []*Label, doer *User) (err error) { toRemove = append(toRemove, issue.Labels[removeIndex:]...) if len(toAdd) > 0 { - if err = issue.addLabels(sess, toAdd, doer); err != nil { + if err = issue.addLabels(db.GetEngine(ctx), toAdd, doer); err != nil { return fmt.Errorf("addLabels: %v", err) } } for _, l := range toRemove { - if err = issue.removeLabel(sess, doer, l); err != nil { + if err = issue.removeLabel(db.GetEngine(ctx), doer, l); err != nil { return fmt.Errorf("removeLabel: %v", err) } } issue.Labels = nil - if err = issue.loadLabels(sess); err != nil { + if err = issue.loadLabels(db.GetEngine(ctx)); err != nil { return err } - return sess.Commit() + return committer.Commit() } // ReadBy sets issue to be read by given user. @@ -579,17 +574,17 @@ func (issue *Issue) ReadBy(userID int64) error { return err } - return setIssueNotificationStatusReadIfUnread(x, userID, issue.ID) + return setIssueNotificationStatusReadIfUnread(db.GetEngine(db.DefaultContext), userID, issue.ID) } -func updateIssueCols(e Engine, issue *Issue, cols ...string) error { +func updateIssueCols(e db.Engine, issue *Issue, cols ...string) error { if _, err := e.ID(issue.ID).Cols(cols...).Update(issue); err != nil { return err } return nil } -func (issue *Issue) changeStatus(e *xorm.Session, doer *User, isClosed, isMergePull bool) (*Comment, error) { +func (issue *Issue) changeStatus(e db.Engine, doer *User, isClosed, isMergePull bool) (*Comment, error) { // Reload the issue currentIssue, err := getIssueByID(e, issue.ID) if err != nil { @@ -612,7 +607,7 @@ func (issue *Issue) changeStatus(e *xorm.Session, doer *User, isClosed, isMergeP return issue.doChangeStatus(e, doer, isMergePull) } -func (issue *Issue) doChangeStatus(e *xorm.Session, doer *User, isMergePull bool) (*Comment, error) { +func (issue *Issue) doChangeStatus(e db.Engine, doer *User, isMergePull bool) (*Comment, error) { // Check for open dependencies if issue.IsClosed && issue.Repo.isDependenciesEnabled(e) { // 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 @@ -675,25 +670,25 @@ func (issue *Issue) doChangeStatus(e *xorm.Session, doer *User, isMergePull bool // ChangeStatus changes issue status to open or closed. func (issue *Issue) ChangeStatus(doer *User, isClosed bool) (*Comment, error) { - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { + ctx, committer, err := db.TxContext() + if err != nil { return nil, err } + defer committer.Close() - if err := issue.loadRepo(sess); err != nil { + if err := issue.loadRepo(db.GetEngine(ctx)); err != nil { return nil, err } - if err := issue.loadPoster(sess); err != nil { + if err := issue.loadPoster(db.GetEngine(ctx)); err != nil { return nil, err } - comment, err := issue.changeStatus(sess, doer, isClosed, false) + comment, err := issue.changeStatus(db.GetEngine(ctx), doer, isClosed, false) if err != nil { return nil, err } - if err = sess.Commit(); err != nil { + if err = committer.Commit(); err != nil { return nil, fmt.Errorf("Commit: %v", err) } @@ -702,18 +697,17 @@ func (issue *Issue) ChangeStatus(doer *User, isClosed bool) (*Comment, error) { // ChangeTitle changes the title of this issue, as the given user. func (issue *Issue) ChangeTitle(doer *User, oldTitle string) (err error) { - sess := x.NewSession() - defer sess.Close() - - if err = sess.Begin(); err != nil { + ctx, committer, err := db.TxContext() + if err != nil { return err } + defer committer.Close() - if err = updateIssueCols(sess, issue, "name"); err != nil { + if err = updateIssueCols(db.GetEngine(ctx), issue, "name"); err != nil { return fmt.Errorf("updateIssueCols: %v", err) } - if err = issue.loadRepo(sess); err != nil { + if err = issue.loadRepo(db.GetEngine(ctx)); err != nil { return fmt.Errorf("loadRepo: %v", err) } @@ -725,43 +719,42 @@ func (issue *Issue) ChangeTitle(doer *User, oldTitle string) (err error) { OldTitle: oldTitle, NewTitle: issue.Title, } - if _, err = createComment(sess, opts); err != nil { + if _, err = createComment(db.GetEngine(ctx), opts); err != nil { return fmt.Errorf("createComment: %v", err) } - if err = issue.addCrossReferences(sess, doer, true); err != nil { + if err = issue.addCrossReferences(db.GetEngine(ctx), doer, true); err != nil { return err } - return sess.Commit() + return committer.Commit() } // ChangeRef changes the branch of this issue, as the given user. func (issue *Issue) ChangeRef(doer *User, oldRef string) (err error) { - sess := x.NewSession() - defer sess.Close() - - if err = sess.Begin(); err != nil { + ctx, committer, err := db.TxContext() + if err != nil { return err } + defer committer.Close() - if err = updateIssueCols(sess, issue, "ref"); err != nil { + if err = updateIssueCols(db.GetEngine(ctx), issue, "ref"); err != nil { return fmt.Errorf("updateIssueCols: %v", err) } - return sess.Commit() + return committer.Commit() } // AddDeletePRBranchComment adds delete branch comment for pull request issue func AddDeletePRBranchComment(doer *User, repo *Repository, issueID int64, branchName string) error { - issue, err := getIssueByID(x, issueID) + issue, err := getIssueByID(db.GetEngine(db.DefaultContext), issueID) if err != nil { return err } - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { + ctx, committer, err := db.TxContext() + if err != nil { return err } + defer committer.Close() opts := &CreateCommentOptions{ Type: CommentTypeDeleteBranch, Doer: doer, @@ -769,52 +762,52 @@ func AddDeletePRBranchComment(doer *User, repo *Repository, issueID int64, branc Issue: issue, OldRef: branchName, } - if _, err = createComment(sess, opts); err != nil { + if _, err = createComment(db.GetEngine(ctx), opts); err != nil { return err } - return sess.Commit() + return committer.Commit() } // UpdateAttachments update attachments by UUIDs for the issue func (issue *Issue) UpdateAttachments(uuids []string) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { + ctx, committer, err := db.TxContext() + if err != nil { return err } - attachments, err := getAttachmentsByUUIDs(sess, uuids) + defer committer.Close() + attachments, err := getAttachmentsByUUIDs(db.GetEngine(ctx), uuids) if err != nil { return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %v", uuids, err) } for i := 0; i < len(attachments); i++ { attachments[i].IssueID = issue.ID - if err := updateAttachment(sess, attachments[i]); err != nil { + if err := updateAttachment(db.GetEngine(ctx), attachments[i]); err != nil { return fmt.Errorf("update attachment [id: %d]: %v", attachments[i].ID, err) } } - return sess.Commit() + return committer.Commit() } // ChangeContent changes issue content, as the given user. func (issue *Issue) ChangeContent(doer *User, content string) (err error) { issue.Content = content - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { + ctx, committer, err := db.TxContext() + if err != nil { return err } + defer committer.Close() - if err = updateIssueCols(sess, issue, "content"); err != nil { + if err = updateIssueCols(db.GetEngine(ctx), issue, "content"); err != nil { return fmt.Errorf("UpdateIssueCols: %v", err) } - if err = issue.addCrossReferences(sess, doer, true); err != nil { + if err = issue.addCrossReferences(db.GetEngine(ctx), doer, true); err != nil { return err } - return sess.Commit() + return committer.Commit() } // GetTasks returns the amount of tasks in the issues content @@ -849,8 +842,8 @@ func (issue *Issue) GetLastEventLabel() string { // GetLastComment return last comment for the current issue. func (issue *Issue) GetLastComment() (*Comment, error) { var c Comment - exist, err := x.Where("type = ?", CommentTypeComment). - And("issue_id = ?", issue.ID).Desc("id").Get(&c) + exist, err := db.GetEngine(db.DefaultContext).Where("type = ?", CommentTypeComment). + And("issue_id = ?", issue.ID).Desc("created_unix").Get(&c) if err != nil { return nil, err } @@ -880,7 +873,7 @@ type NewIssueOptions struct { IsPull bool } -func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) { +func newIssue(e db.Engine, doer *User, opts NewIssueOptions) (err error) { opts.Issue.Title = strings.TrimSpace(opts.Issue.Title) if opts.Issue.MilestoneID > 0 { @@ -985,44 +978,44 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) { // RecalculateIssueIndexForRepo create issue_index for repo if not exist and // update it based on highest index of existing issues assigned to a repo func RecalculateIssueIndexForRepo(repoID int64) error { - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { + ctx, committer, err := db.TxContext() + if err != nil { return err } + defer committer.Close() - if err := upsertResourceIndex(sess, "issue_index", repoID); err != nil { + if err := db.UpsertResourceIndex(db.GetEngine(ctx), "issue_index", repoID); err != nil { return err } var max int64 - if _, err := sess.Select(" MAX(`index`)").Table("issue").Where("repo_id=?", repoID).Get(&max); err != nil { + if _, err := db.GetEngine(ctx).Select(" MAX(`index`)").Table("issue").Where("repo_id=?", repoID).Get(&max); err != nil { return err } - if _, err := sess.Exec("UPDATE `issue_index` SET max_index=? WHERE group_id=?", max, repoID); err != nil { + if _, err := db.GetEngine(ctx).Exec("UPDATE `issue_index` SET max_index=? WHERE group_id=?", max, repoID); err != nil { return err } - return sess.Commit() + return committer.Commit() } // NewIssue creates new issue with labels for repository. func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) { - idx, err := GetNextResourceIndex("issue_index", repo.ID) + idx, err := db.GetNextResourceIndex("issue_index", repo.ID) if err != nil { return fmt.Errorf("generate issue index failed: %v", err) } issue.Index = idx - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { + ctx, committer, err := db.TxContext() + if err != nil { return err } + defer committer.Close() - if err = newIssue(sess, issue.Poster, NewIssueOptions{ + if err = newIssue(db.GetEngine(ctx), issue.Poster, NewIssueOptions{ Repo: repo, Issue: issue, LabelIDs: labelIDs, @@ -1034,7 +1027,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) return fmt.Errorf("newIssue: %v", err) } - if err = sess.Commit(); err != nil { + if err = committer.Commit(); err != nil { return fmt.Errorf("Commit: %v", err) } @@ -1050,7 +1043,7 @@ func GetIssueByIndex(repoID, index int64) (*Issue, error) { RepoID: repoID, Index: index, } - has, err := x.Get(issue) + has, err := db.GetEngine(db.DefaultContext).Get(issue) if err != nil { return nil, err } else if !has { @@ -1068,7 +1061,7 @@ func GetIssueWithAttrsByIndex(repoID, index int64) (*Issue, error) { return issue, issue.LoadAttributes() } -func getIssueByID(e Engine, id int64) (*Issue, error) { +func getIssueByID(e db.Engine, id int64) (*Issue, error) { issue := new(Issue) has, err := e.ID(id).Get(issue) if err != nil { @@ -1081,24 +1074,24 @@ func getIssueByID(e Engine, id int64) (*Issue, error) { // GetIssueWithAttrsByID returns an issue with attributes by given ID. func GetIssueWithAttrsByID(id int64) (*Issue, error) { - issue, err := getIssueByID(x, id) + issue, err := getIssueByID(db.GetEngine(db.DefaultContext), id) if err != nil { return nil, err } - return issue, issue.loadAttributes(x) + return issue, issue.loadAttributes(db.GetEngine(db.DefaultContext)) } // GetIssueByID returns an issue by given ID. func GetIssueByID(id int64) (*Issue, error) { - return getIssueByID(x, id) + return getIssueByID(db.GetEngine(db.DefaultContext), id) } -func getIssuesByIDs(e Engine, issueIDs []int64) ([]*Issue, error) { +func getIssuesByIDs(e db.Engine, issueIDs []int64) ([]*Issue, error) { issues := make([]*Issue, 0, 10) return issues, e.In("id", issueIDs).Find(&issues) } -func getIssueIDsByRepoID(e Engine, repoID int64) ([]int64, error) { +func getIssueIDsByRepoID(e db.Engine, repoID int64) ([]int64, error) { ids := make([]int64, 0, 10) err := e.Table("issue").Cols("id").Where("repo_id = ?", repoID).Find(&ids) return ids, err @@ -1106,17 +1099,17 @@ func getIssueIDsByRepoID(e Engine, repoID int64) ([]int64, error) { // GetIssueIDsByRepoID returns all issue ids by repo id func GetIssueIDsByRepoID(repoID int64) ([]int64, error) { - return getIssueIDsByRepoID(x, repoID) + return getIssueIDsByRepoID(db.GetEngine(db.DefaultContext), repoID) } // GetIssuesByIDs return issues with the given IDs. func GetIssuesByIDs(issueIDs []int64) ([]*Issue, error) { - return getIssuesByIDs(x, issueIDs) + return getIssuesByIDs(db.GetEngine(db.DefaultContext), issueIDs) } // IssuesOptions represents options of an issue. type IssuesOptions struct { - ListOptions + db.ListOptions RepoIDs []int64 // include all repos if empty AssigneeID int64 PosterID int64 @@ -1311,7 +1304,7 @@ func applyReviewRequestedCondition(sess *xorm.Session, reviewRequestedID int64) // CountIssuesByRepo map from repoID to number of issues matching the options func CountIssuesByRepo(opts *IssuesOptions) (map[int64]int64, error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() sess.Join("INNER", "repository", "`issue`.repo_id = `repository`.id") @@ -1339,7 +1332,7 @@ func CountIssuesByRepo(opts *IssuesOptions) (map[int64]int64, error) { // GetRepoIDsForIssuesOptions find all repo ids for the given options func GetRepoIDsForIssuesOptions(opts *IssuesOptions, user *User) ([]int64, error) { repoIDs := make([]int64, 0, 5) - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() sess.Join("INNER", "repository", "`issue`.repo_id = `repository`.id") @@ -1359,7 +1352,7 @@ func GetRepoIDsForIssuesOptions(opts *IssuesOptions, user *User) ([]int64, error // Issues returns a list of issues by given conditions. func Issues(opts *IssuesOptions) ([]*Issue, error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() sess.Join("INNER", "repository", "`issue`.repo_id = `repository`.id") @@ -1381,7 +1374,7 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) { // CountIssues number return of issues by given conditions. func CountIssues(opts *IssuesOptions) (int64, error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() countsSlice := make([]*struct { @@ -1406,7 +1399,7 @@ func CountIssues(opts *IssuesOptions) (int64, error) { // User permissions must be verified elsewhere if required. func GetParticipantsIDsByIssueID(issueID int64) ([]int64, error) { userIDs := make([]int64, 0, 5) - return userIDs, x.Table("comment"). + return userIDs, db.GetEngine(db.DefaultContext).Table("comment"). Cols("poster_id"). Where("issue_id = ?", issueID). And("type in (?,?,?)", CommentTypeComment, CommentTypeCode, CommentTypeReview). @@ -1416,7 +1409,7 @@ func GetParticipantsIDsByIssueID(issueID int64) ([]int64, error) { // IsUserParticipantsOfIssue return true if user is participants of an issue func IsUserParticipantsOfIssue(user *User, issue *Issue) bool { - userIDs, err := issue.getParticipantIDsByIssue(x) + userIDs, err := issue.getParticipantIDsByIssue(db.GetEngine(db.DefaultContext)) if err != nil { log.Error(err.Error()) return false @@ -1425,7 +1418,7 @@ func IsUserParticipantsOfIssue(user *User, issue *Issue) bool { } // UpdateIssueMentions updates issue-user relations for mentioned users. -func UpdateIssueMentions(ctx DBContext, issueID int64, mentions []*User) error { +func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*User) error { if len(mentions) == 0 { return nil } @@ -1482,6 +1475,12 @@ type IssueStatsOptions struct { IssueIDs []int64 } +const ( + // When queries are broken down in parts because of the number + // of parameters, attempt to break by this amount + maxQueryParameters = 300 +) + // GetIssueStats returns issue statistic information by given conditions. func GetIssueStats(opts *IssueStatsOptions) (*IssueStats, error) { if len(opts.IssueIDs) <= maxQueryParameters { @@ -1518,7 +1517,7 @@ func getIssueStatsChunk(opts *IssueStatsOptions, issueIDs []int64) (*IssueStats, stats := &IssueStats{} countSession := func(opts *IssueStatsOptions) *xorm.Session { - sess := x. + sess := db.GetEngine(db.DefaultContext). Where("issue.repo_id = ?", opts.RepoID) if len(opts.IssueIDs) > 0 { @@ -1612,7 +1611,7 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) { } sess := func(cond builder.Cond) *xorm.Session { - s := x.Where(cond) + s := db.GetEngine(db.DefaultContext).Where(cond) if len(opts.LabelIDs) > 0 { s.Join("INNER", "issue_label", "issue_label.issue_id = issue.id"). In("issue_label.label_id", opts.LabelIDs) @@ -1724,7 +1723,7 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) { // GetRepoIssueStats returns number of open and closed repository issues by given filter mode. func GetRepoIssueStats(repoID, uid int64, filterMode int, isPull bool) (numOpen, numClosed int64) { countSession := func(isClosed, isPull bool, repoID int64) *xorm.Session { - sess := x. + sess := db.GetEngine(db.DefaultContext). Where("is_closed = ?", isClosed). And("is_pull = ?", isPull). And("repo_id = ?", repoID) @@ -1776,7 +1775,7 @@ func SearchIssueIDsByKeyword(kw string, repoIDs []int64, limit, start int) (int6 ID int64 UpdatedUnix int64 }, 0, limit) - err := x.Distinct("id", "updated_unix").Table("issue").Where(cond). + err := db.GetEngine(db.DefaultContext).Distinct("id", "updated_unix").Table("issue").Where(cond). OrderBy("`updated_unix` DESC").Limit(limit, start). Find(&res) if err != nil { @@ -1786,7 +1785,7 @@ func SearchIssueIDsByKeyword(kw string, repoIDs []int64, limit, start int) (int6 ids = append(ids, r.ID) } - total, err := x.Distinct("id").Table("issue").Where(cond).Count() + total, err := db.GetEngine(db.DefaultContext).Distinct("id").Table("issue").Where(cond).Count() if err != nil { return 0, nil, err } @@ -1798,7 +1797,7 @@ func SearchIssueIDsByKeyword(kw string, repoIDs []int64, limit, start int) (int6 // If the issue status is changed a statusChangeComment is returned // similarly if the title is changed the titleChanged bool is set to true func UpdateIssueByAPI(issue *Issue, doer *User) (statusChangeComment *Comment, titleChanged bool, err error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return nil, false, err @@ -1857,7 +1856,7 @@ func UpdateIssueDeadline(issue *Issue, deadlineUnix timeutil.TimeStamp, doer *Us return nil } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -1883,7 +1882,7 @@ type DependencyInfo struct { } // getParticipantIDsByIssue returns all userIDs who are participated in comments of an issue and issue author -func (issue *Issue) getParticipantIDsByIssue(e Engine) ([]int64, error) { +func (issue *Issue) getParticipantIDsByIssue(e db.Engine) ([]int64, error) { if issue == nil { return nil, nil } @@ -1905,7 +1904,7 @@ func (issue *Issue) getParticipantIDsByIssue(e Engine) ([]int64, error) { } // Get Blocked By Dependencies, aka all issues this issue is blocked by. -func (issue *Issue) getBlockedByDependencies(e Engine) (issueDeps []*DependencyInfo, err error) { +func (issue *Issue) getBlockedByDependencies(e db.Engine) (issueDeps []*DependencyInfo, err error) { return issueDeps, e. Table("issue"). Join("INNER", "repository", "repository.id = issue.repo_id"). @@ -1917,7 +1916,7 @@ func (issue *Issue) getBlockedByDependencies(e Engine) (issueDeps []*DependencyI } // Get Blocking Dependencies, aka all issues this issue blocks. -func (issue *Issue) getBlockingDependencies(e Engine) (issueDeps []*DependencyInfo, err error) { +func (issue *Issue) getBlockingDependencies(e db.Engine) (issueDeps []*DependencyInfo, err error) { return issueDeps, e. Table("issue"). Join("INNER", "repository", "repository.id = issue.repo_id"). @@ -1930,15 +1929,15 @@ func (issue *Issue) getBlockingDependencies(e Engine) (issueDeps []*DependencyIn // BlockedByDependencies finds all Dependencies an issue is blocked by func (issue *Issue) BlockedByDependencies() ([]*DependencyInfo, error) { - return issue.getBlockedByDependencies(x) + return issue.getBlockedByDependencies(db.GetEngine(db.DefaultContext)) } // BlockingDependencies returns all blocking dependencies, aka all other issues a given issue blocks func (issue *Issue) BlockingDependencies() ([]*DependencyInfo, error) { - return issue.getBlockingDependencies(x) + return issue.getBlockingDependencies(db.GetEngine(db.DefaultContext)) } -func (issue *Issue) updateClosedNum(e Engine) (err error) { +func (issue *Issue) updateClosedNum(e db.Engine) (err error) { if issue.IsPull { _, err = e.Exec("UPDATE `repository` SET num_closed_pulls=(SELECT count(*) FROM issue WHERE repo_id=? AND is_pull=? AND is_closed=?) WHERE id=?", issue.RepoID, @@ -1958,7 +1957,7 @@ func (issue *Issue) updateClosedNum(e Engine) (err error) { } // FindAndUpdateIssueMentions finds users mentioned in the given content string, and saves them in the database. -func (issue *Issue) FindAndUpdateIssueMentions(ctx DBContext, doer *User, content string) (mentions []*User, err error) { +func (issue *Issue) FindAndUpdateIssueMentions(ctx context.Context, doer *User, content string) (mentions []*User, err error) { rawMentions := references.FindAllMentionsMarkdown(content) mentions, err = issue.ResolveMentionsByVisibility(ctx, doer, rawMentions) if err != nil { @@ -1972,18 +1971,18 @@ func (issue *Issue) FindAndUpdateIssueMentions(ctx DBContext, doer *User, conten // ResolveMentionsByVisibility returns the users mentioned in an issue, removing those that // don't have access to reading it. Teams are expanded into their users, but organizations are ignored. -func (issue *Issue) ResolveMentionsByVisibility(ctx DBContext, doer *User, mentions []string) (users []*User, err error) { +func (issue *Issue) ResolveMentionsByVisibility(ctx context.Context, doer *User, mentions []string) (users []*User, err error) { if len(mentions) == 0 { return } - if err = issue.loadRepo(ctx.e); err != nil { + if err = issue.loadRepo(db.GetEngine(ctx)); err != nil { return } resolved := make(map[string]bool, 10) var mentionTeams []string - if err := issue.Repo.getOwner(ctx.e); err != nil { + if err := issue.Repo.getOwner(db.GetEngine(ctx)); err != nil { return nil, err } @@ -2012,7 +2011,7 @@ func (issue *Issue) ResolveMentionsByVisibility(ctx DBContext, doer *User, menti if issue.Repo.Owner.IsOrganization() && len(mentionTeams) > 0 { teams := make([]*Team, 0, len(mentionTeams)) - if err := ctx.e. + if err := db.GetEngine(ctx). Join("INNER", "team_repo", "team_repo.team_id = team.id"). Where("team_repo.repo_id=?", issue.Repo.ID). In("team.lower_name", mentionTeams). @@ -2031,7 +2030,7 @@ func (issue *Issue) ResolveMentionsByVisibility(ctx DBContext, doer *User, menti resolved[issue.Repo.Owner.LowerName+"/"+team.LowerName] = true continue } - has, err := ctx.e.Get(&TeamUnit{OrgID: issue.Repo.Owner.ID, TeamID: team.ID, Type: unittype}) + has, err := db.GetEngine(ctx).Get(&TeamUnit{OrgID: issue.Repo.Owner.ID, TeamID: team.ID, Type: unittype}) if err != nil { return nil, fmt.Errorf("get team units (%d): %v", team.ID, err) } @@ -2042,7 +2041,7 @@ func (issue *Issue) ResolveMentionsByVisibility(ctx DBContext, doer *User, menti } if len(checked) != 0 { teamusers := make([]*User, 0, 20) - if err := ctx.e. + if err := db.GetEngine(ctx). Join("INNER", "team_user", "team_user.uid = `user`.id"). In("`team_user`.team_id", checked). And("`user`.is_active = ?", true). @@ -2079,7 +2078,7 @@ func (issue *Issue) ResolveMentionsByVisibility(ctx DBContext, doer *User, menti } unchecked := make([]*User, 0, len(mentionUsers)) - if err := ctx.e. + if err := db.GetEngine(ctx). Where("`user`.is_active = ?", true). And("`user`.prohibit_login = ?", false). In("`user`.lower_name", mentionUsers). @@ -2091,7 +2090,7 @@ func (issue *Issue) ResolveMentionsByVisibility(ctx DBContext, doer *User, menti continue } // Normal users must have read access to the referencing issue - perm, err := getUserRepoPermission(ctx.e, issue.Repo, user) + perm, err := getUserRepoPermission(db.GetEngine(ctx), issue.Repo, user) if err != nil { return nil, fmt.Errorf("getUserRepoPermission [%d]: %v", user.ID, err) } @@ -2106,7 +2105,7 @@ func (issue *Issue) ResolveMentionsByVisibility(ctx DBContext, doer *User, menti // UpdateIssuesMigrationsByType updates all migrated repositories' issues from gitServiceType to replace originalAuthorID to posterID func UpdateIssuesMigrationsByType(gitServiceType structs.GitServiceType, originalAuthorID string, posterID int64) error { - _, err := x.Table("issue"). + _, err := db.GetEngine(db.DefaultContext).Table("issue"). Where("repo_id IN (SELECT id FROM repository WHERE original_service_type = ?)", gitServiceType). And("original_author_id = ?", originalAuthorID). Update(map[string]interface{}{ @@ -2119,7 +2118,7 @@ func UpdateIssuesMigrationsByType(gitServiceType structs.GitServiceType, origina // UpdateReactionsMigrationsByType updates all migrated repositories' reactions from gitServiceType to replace originalAuthorID to posterID func UpdateReactionsMigrationsByType(gitServiceType structs.GitServiceType, originalAuthorID string, userID int64) error { - _, err := x.Table("reaction"). + _, err := db.GetEngine(db.DefaultContext).Table("reaction"). Where("original_author_id = ?", originalAuthorID). And(migratedIssueCond(gitServiceType)). Update(map[string]interface{}{ @@ -2130,7 +2129,7 @@ func UpdateReactionsMigrationsByType(gitServiceType structs.GitServiceType, orig return err } -func deleteIssuesByRepoID(sess Engine, repoID int64) (attachmentPaths []string, err error) { +func deleteIssuesByRepoID(sess db.Engine, repoID int64) (attachmentPaths []string, err error) { deleteCond := builder.Select("id").From("issue").Where(builder.Eq{"issue.repo_id": repoID}) // Delete comments and attachments diff --git a/models/issue_assignees.go b/models/issue_assignees.go index e05c0f0fd236e..0f7ba2d7022cf 100644 --- a/models/issue_assignees.go +++ b/models/issue_assignees.go @@ -7,6 +7,7 @@ package models import ( "fmt" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/util" "xorm.io/xorm" @@ -19,13 +20,17 @@ type IssueAssignees struct { IssueID int64 `xorm:"INDEX"` } +func init() { + db.RegisterModel(new(IssueAssignees)) +} + // LoadAssignees load assignees of this issue. func (issue *Issue) LoadAssignees() error { - return issue.loadAssignees(x) + return issue.loadAssignees(db.GetEngine(db.DefaultContext)) } // This loads all assignees of an issue -func (issue *Issue) loadAssignees(e Engine) (err error) { +func (issue *Issue) loadAssignees(e db.Engine) (err error) { // Reset maybe preexisting assignees issue.Assignees = []*User{} @@ -51,7 +56,7 @@ func (issue *Issue) loadAssignees(e Engine) (err error) { // User permissions must be verified elsewhere if required. func GetAssigneeIDsByIssue(issueID int64) ([]int64, error) { userIDs := make([]int64, 0, 5) - return userIDs, x.Table("issue_assignees"). + return userIDs, db.GetEngine(db.DefaultContext).Table("issue_assignees"). Cols("assignee_id"). Where("issue_id = ?", issueID). Distinct("assignee_id"). @@ -60,10 +65,10 @@ func GetAssigneeIDsByIssue(issueID int64) ([]int64, error) { // GetAssigneesByIssue returns everyone assigned to that issue func GetAssigneesByIssue(issue *Issue) (assignees []*User, err error) { - return getAssigneesByIssue(x, issue) + return getAssigneesByIssue(db.GetEngine(db.DefaultContext), issue) } -func getAssigneesByIssue(e Engine, issue *Issue) (assignees []*User, err error) { +func getAssigneesByIssue(e db.Engine, issue *Issue) (assignees []*User, err error) { err = issue.loadAssignees(e) if err != nil { return assignees, err @@ -74,22 +79,22 @@ func getAssigneesByIssue(e Engine, issue *Issue) (assignees []*User, err error) // IsUserAssignedToIssue returns true when the user is assigned to the issue func IsUserAssignedToIssue(issue *Issue, user *User) (isAssigned bool, err error) { - return isUserAssignedToIssue(x, issue, user) + return isUserAssignedToIssue(db.GetEngine(db.DefaultContext), issue, user) } -func isUserAssignedToIssue(e Engine, issue *Issue, user *User) (isAssigned bool, err error) { +func isUserAssignedToIssue(e db.Engine, issue *Issue, user *User) (isAssigned bool, err error) { return e.Get(&IssueAssignees{IssueID: issue.ID, AssigneeID: user.ID}) } // ClearAssigneeByUserID deletes all assignments of an user -func clearAssigneeByUserID(sess Engine, userID int64) (err error) { +func clearAssigneeByUserID(sess db.Engine, userID int64) (err error) { _, err = sess.Delete(&IssueAssignees{AssigneeID: userID}) return } // ToggleAssignee changes a user between assigned and not assigned for this issue, and make issue comment for it. func (issue *Issue) ToggleAssignee(doer *User, assigneeID int64) (removed bool, comment *Comment, err error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { diff --git a/models/issue_assignees_test.go b/models/issue_assignees_test.go index e0359b0b9faa5..5052df3dfb107 100644 --- a/models/issue_assignees_test.go +++ b/models/issue_assignees_test.go @@ -7,11 +7,12 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestUpdateAssignee(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // Fake issue with assignees issue, err := GetIssueWithAttrsByID(1) @@ -61,10 +62,10 @@ func TestUpdateAssignee(t *testing.T) { } func TestMakeIDsFromAPIAssigneesToAdd(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - _ = AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) - _ = AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + _ = db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + _ = db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) IDs, err := MakeIDsFromAPIAssigneesToAdd("", []string{""}) assert.NoError(t, err) diff --git a/models/issue_comment.go b/models/issue_comment.go index 10a7d0b114c3d..01e41814a4744 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -13,6 +13,7 @@ import ( "strings" "unicode/utf8" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" @@ -197,6 +198,10 @@ type Comment struct { IsForcePush bool `xorm:"-"` } +func init() { + db.RegisterModel(new(Comment)) +} + // PushActionContent is content of push pull comment type PushActionContent struct { IsForcePush bool `json:"is_force_push"` @@ -205,10 +210,10 @@ type PushActionContent struct { // LoadIssue loads issue from database func (c *Comment) LoadIssue() (err error) { - return c.loadIssue(x) + return c.loadIssue(db.GetEngine(db.DefaultContext)) } -func (c *Comment) loadIssue(e Engine) (err error) { +func (c *Comment) loadIssue(e db.Engine) (err error) { if c.Issue != nil { return nil } @@ -243,7 +248,7 @@ func (c *Comment) AfterLoad(session *xorm.Session) { } } -func (c *Comment) loadPoster(e Engine) (err error) { +func (c *Comment) loadPoster(e db.Engine) (err error) { if c.PosterID <= 0 || c.Poster != nil { return nil } @@ -279,7 +284,7 @@ func (c *Comment) HTMLURL() string { log.Error("LoadIssue(%d): %v", c.IssueID, err) return "" } - err = c.Issue.loadRepo(x) + err = c.Issue.loadRepo(db.GetEngine(db.DefaultContext)) if err != nil { // Silently dropping errors :unamused: log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) return "" @@ -308,7 +313,7 @@ func (c *Comment) APIURL() string { log.Error("LoadIssue(%d): %v", c.IssueID, err) return "" } - err = c.Issue.loadRepo(x) + err = c.Issue.loadRepo(db.GetEngine(db.DefaultContext)) if err != nil { // Silently dropping errors :unamused: log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) return "" @@ -329,7 +334,7 @@ func (c *Comment) IssueURL() string { return "" } - err = c.Issue.loadRepo(x) + err = c.Issue.loadRepo(db.GetEngine(db.DefaultContext)) if err != nil { // Silently dropping errors :unamused: log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) return "" @@ -345,7 +350,7 @@ func (c *Comment) PRURL() string { return "" } - err = c.Issue.loadRepo(x) + err = c.Issue.loadRepo(db.GetEngine(db.DefaultContext)) if err != nil { // Silently dropping errors :unamused: log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) return "" @@ -375,7 +380,7 @@ func (c *Comment) EventTag() string { // LoadLabel if comment.Type is CommentTypeLabel, then load Label func (c *Comment) LoadLabel() error { var label Label - has, err := x.ID(c.LabelID).Get(&label) + has, err := db.GetEngine(db.DefaultContext).ID(c.LabelID).Get(&label) if err != nil { return err } else if has { @@ -392,7 +397,7 @@ func (c *Comment) LoadLabel() error { func (c *Comment) LoadProject() error { if c.OldProjectID > 0 { var oldProject Project - has, err := x.ID(c.OldProjectID).Get(&oldProject) + has, err := db.GetEngine(db.DefaultContext).ID(c.OldProjectID).Get(&oldProject) if err != nil { return err } else if has { @@ -402,7 +407,7 @@ func (c *Comment) LoadProject() error { if c.ProjectID > 0 { var project Project - has, err := x.ID(c.ProjectID).Get(&project) + has, err := db.GetEngine(db.DefaultContext).ID(c.ProjectID).Get(&project) if err != nil { return err } else if has { @@ -417,7 +422,7 @@ func (c *Comment) LoadProject() error { func (c *Comment) LoadMilestone() error { if c.OldMilestoneID > 0 { var oldMilestone Milestone - has, err := x.ID(c.OldMilestoneID).Get(&oldMilestone) + has, err := db.GetEngine(db.DefaultContext).ID(c.OldMilestoneID).Get(&oldMilestone) if err != nil { return err } else if has { @@ -427,7 +432,7 @@ func (c *Comment) LoadMilestone() error { if c.MilestoneID > 0 { var milestone Milestone - has, err := x.ID(c.MilestoneID).Get(&milestone) + has, err := db.GetEngine(db.DefaultContext).ID(c.MilestoneID).Get(&milestone) if err != nil { return err } else if has { @@ -439,7 +444,7 @@ func (c *Comment) LoadMilestone() error { // LoadPoster loads comment poster func (c *Comment) LoadPoster() error { - return c.loadPoster(x) + return c.loadPoster(db.GetEngine(db.DefaultContext)) } // LoadAttachments loads attachments @@ -449,7 +454,7 @@ func (c *Comment) LoadAttachments() error { } var err error - c.Attachments, err = getAttachmentsByCommentID(x, c.ID) + c.Attachments, err = getAttachmentsByCommentID(db.GetEngine(db.DefaultContext), c.ID) if err != nil { log.Error("getAttachmentsByCommentID[%d]: %v", c.ID, err) } @@ -458,7 +463,7 @@ func (c *Comment) LoadAttachments() error { // UpdateAttachments update attachments by UUIDs for the comment func (c *Comment) UpdateAttachments(uuids []string) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -482,7 +487,7 @@ func (c *Comment) LoadAssigneeUserAndTeam() error { var err error if c.AssigneeID > 0 && c.Assignee == nil { - c.Assignee, err = getUserByID(x, c.AssigneeID) + c.Assignee, err = getUserByID(db.GetEngine(db.DefaultContext), c.AssigneeID) if err != nil { if !IsErrUserNotExist(err) { return err @@ -517,7 +522,7 @@ func (c *Comment) LoadResolveDoer() (err error) { if c.ResolveDoerID == 0 || c.Type != CommentTypeCode { return nil } - c.ResolveDoer, err = getUserByID(x, c.ResolveDoerID) + c.ResolveDoer, err = getUserByID(db.GetEngine(db.DefaultContext), c.ResolveDoerID) if err != nil { if IsErrUserNotExist(err) { c.ResolveDoer = NewGhostUser() @@ -537,7 +542,7 @@ func (c *Comment) LoadDepIssueDetails() (err error) { if c.DependentIssueID <= 0 || c.DependentIssue != nil { return nil } - c.DependentIssue, err = getIssueByID(x, c.DependentIssueID) + c.DependentIssue, err = getIssueByID(db.GetEngine(db.DefaultContext), c.DependentIssueID) return err } @@ -551,7 +556,7 @@ func (c *Comment) LoadTime() error { return err } -func (c *Comment) loadReactions(e Engine, repo *Repository) (err error) { +func (c *Comment) loadReactions(e db.Engine, repo *Repository) (err error) { if c.Reactions != nil { return nil } @@ -571,10 +576,10 @@ func (c *Comment) loadReactions(e Engine, repo *Repository) (err error) { // LoadReactions loads comment reactions func (c *Comment) LoadReactions(repo *Repository) error { - return c.loadReactions(x, repo) + return c.loadReactions(db.GetEngine(db.DefaultContext), repo) } -func (c *Comment) loadReview(e Engine) (err error) { +func (c *Comment) loadReview(e db.Engine) (err error) { if c.Review == nil { if c.Review, err = getReviewByID(e, c.ReviewID); err != nil { return err @@ -586,7 +591,7 @@ func (c *Comment) loadReview(e Engine) (err error) { // LoadReview loads the associated review func (c *Comment) LoadReview() error { - return c.loadReview(x) + return c.loadReview(db.GetEngine(db.DefaultContext)) } var notEnoughLines = regexp.MustCompile(`fatal: file .* has only \d+ lines?`) @@ -637,7 +642,7 @@ func (c *Comment) CodeCommentURL() string { log.Error("LoadIssue(%d): %v", c.IssueID, err) return "" } - err = c.Issue.loadRepo(x) + err = c.Issue.loadRepo(db.GetEngine(db.DefaultContext)) if err != nil { // Silently dropping errors :unamused: log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) return "" @@ -681,7 +686,7 @@ func (c *Comment) LoadPushCommits() (err error) { return err } -func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) { +func createComment(e db.Engine, opts *CreateCommentOptions) (_ *Comment, err error) { var LabelID int64 if opts.Label != nil { LabelID = opts.Label.ID @@ -740,7 +745,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err return comment, nil } -func updateCommentInfos(e *xorm.Session, opts *CreateCommentOptions, comment *Comment) (err error) { +func updateCommentInfos(e db.Engine, opts *CreateCommentOptions, comment *Comment) (err error) { // Check comment type. switch opts.Type { case CommentTypeCode: @@ -894,7 +899,7 @@ type CreateCommentOptions struct { // CreateComment creates comment of issue or commit. func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return nil, err @@ -919,7 +924,7 @@ func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commi } // Check if same reference from same commit has already existed. - has, err := x.Get(&Comment{ + has, err := db.GetEngine(db.DefaultContext).Get(&Comment{ Type: CommentTypeCommitRef, IssueID: issue.ID, CommitSHA: commitSHA, @@ -943,10 +948,10 @@ func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commi // GetCommentByID returns the comment by given ID. func GetCommentByID(id int64) (*Comment, error) { - return getCommentByID(x, id) + return getCommentByID(db.GetEngine(db.DefaultContext), id) } -func getCommentByID(e Engine, id int64) (*Comment, error) { +func getCommentByID(e db.Engine, id int64) (*Comment, error) { c := new(Comment) has, err := e.ID(id).Get(c) if err != nil { @@ -959,7 +964,7 @@ func getCommentByID(e Engine, id int64) (*Comment, error) { // FindCommentsOptions describes the conditions to Find comments type FindCommentsOptions struct { - ListOptions + db.ListOptions RepoID int64 IssueID int64 ReviewID int64 @@ -999,7 +1004,7 @@ func (opts *FindCommentsOptions) toConds() builder.Cond { return cond } -func findComments(e Engine, opts *FindCommentsOptions) ([]*Comment, error) { +func findComments(e db.Engine, opts *FindCommentsOptions) ([]*Comment, error) { comments := make([]*Comment, 0, 10) sess := e.Where(opts.toConds()) if opts.RepoID > 0 { @@ -1007,7 +1012,7 @@ func findComments(e Engine, opts *FindCommentsOptions) ([]*Comment, error) { } if opts.Page != 0 { - sess = opts.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, opts) } // WARNING: If you change this order you will need to fix createCodeComment @@ -1020,12 +1025,12 @@ func findComments(e Engine, opts *FindCommentsOptions) ([]*Comment, error) { // FindComments returns all comments according options func FindComments(opts *FindCommentsOptions) ([]*Comment, error) { - return findComments(x, opts) + return findComments(db.GetEngine(db.DefaultContext), opts) } // CountComments count all comments according options by ignoring pagination func CountComments(opts *FindCommentsOptions) (int64, error) { - sess := x.Where(opts.toConds()) + sess := db.GetEngine(db.DefaultContext).Where(opts.toConds()) if opts.RepoID > 0 { sess.Join("INNER", "issue", "issue.id = comment.issue_id") } @@ -1034,7 +1039,7 @@ func CountComments(opts *FindCommentsOptions) (int64, error) { // UpdateComment updates information of comment. func UpdateComment(c *Comment, doer *User) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -1058,7 +1063,7 @@ func UpdateComment(c *Comment, doer *User) error { // DeleteComment deletes the comment func DeleteComment(comment *Comment) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -1071,7 +1076,7 @@ func DeleteComment(comment *Comment) error { return sess.Commit() } -func deleteComment(e Engine, comment *Comment) error { +func deleteComment(e db.Engine, comment *Comment) error { if _, err := e.Delete(&Comment{ ID: comment.ID, }); err != nil { @@ -1097,11 +1102,11 @@ func deleteComment(e Engine, comment *Comment) error { // CodeComments represents comments on code by using this structure: FILENAME -> LINE (+ == proposed; - == previous) -> COMMENTS type CodeComments map[string]map[int64][]*Comment -func fetchCodeComments(e Engine, issue *Issue, currentUser *User) (CodeComments, error) { +func fetchCodeComments(e db.Engine, issue *Issue, currentUser *User) (CodeComments, error) { return fetchCodeCommentsByReview(e, issue, currentUser, nil) } -func fetchCodeCommentsByReview(e Engine, issue *Issue, currentUser *User, review *Review) (CodeComments, error) { +func fetchCodeCommentsByReview(e db.Engine, issue *Issue, currentUser *User, review *Review) (CodeComments, error) { pathToLineToComment := make(CodeComments) if review == nil { review = &Review{ID: 0} @@ -1126,7 +1131,7 @@ func fetchCodeCommentsByReview(e Engine, issue *Issue, currentUser *User, review return pathToLineToComment, nil } -func findCodeComments(e Engine, opts FindCommentsOptions, issue *Issue, currentUser *User, review *Review) ([]*Comment, error) { +func findCodeComments(e db.Engine, opts FindCommentsOptions, issue *Issue, currentUser *User, review *Review) ([]*Comment, error) { var comments []*Comment if review == nil { review = &Review{ID: 0} @@ -1202,17 +1207,17 @@ func FetchCodeCommentsByLine(issue *Issue, currentUser *User, treePath string, l TreePath: treePath, Line: line, } - return findCodeComments(x, opts, issue, currentUser, nil) + return findCodeComments(db.GetEngine(db.DefaultContext), opts, issue, currentUser, nil) } // FetchCodeComments will return a 2d-map: ["Path"]["Line"] = Comments at line func FetchCodeComments(issue *Issue, currentUser *User) (CodeComments, error) { - return fetchCodeComments(x, issue, currentUser) + return fetchCodeComments(db.GetEngine(db.DefaultContext), issue, currentUser) } // UpdateCommentsMigrationsByType updates comments' migrations information via given git service type and original id and poster id func UpdateCommentsMigrationsByType(tp structs.GitServiceType, originalAuthorID string, posterID int64) error { - _, err := x.Table("comment"). + _, err := db.GetEngine(db.DefaultContext).Table("comment"). Where(builder.In("issue_id", builder.Select("issue.id"). From("issue"). diff --git a/models/issue_comment_list.go b/models/issue_comment_list.go index df1b1ac55f2d8..bd1b48f8e5bf0 100644 --- a/models/issue_comment_list.go +++ b/models/issue_comment_list.go @@ -4,6 +4,8 @@ package models +import "code.gitea.io/gitea/models/db" + // CommentList defines a list of comments type CommentList []*Comment @@ -17,7 +19,7 @@ func (comments CommentList) getPosterIDs() []int64 { return keysInt64(posterIDs) } -func (comments CommentList) loadPosters(e Engine) error { +func (comments CommentList) loadPosters(e db.Engine) error { if len(comments) == 0 { return nil } @@ -70,7 +72,7 @@ func (comments CommentList) getLabelIDs() []int64 { return keysInt64(ids) } -func (comments CommentList) loadLabels(e Engine) error { +func (comments CommentList) loadLabels(e db.Engine) error { if len(comments) == 0 { return nil } @@ -120,7 +122,7 @@ func (comments CommentList) getMilestoneIDs() []int64 { return keysInt64(ids) } -func (comments CommentList) loadMilestones(e Engine) error { +func (comments CommentList) loadMilestones(e db.Engine) error { if len(comments) == 0 { return nil } @@ -163,7 +165,7 @@ func (comments CommentList) getOldMilestoneIDs() []int64 { return keysInt64(ids) } -func (comments CommentList) loadOldMilestones(e Engine) error { +func (comments CommentList) loadOldMilestones(e db.Engine) error { if len(comments) == 0 { return nil } @@ -206,7 +208,7 @@ func (comments CommentList) getAssigneeIDs() []int64 { return keysInt64(ids) } -func (comments CommentList) loadAssignees(e Engine) error { +func (comments CommentList) loadAssignees(e db.Engine) error { if len(comments) == 0 { return nil } @@ -280,7 +282,7 @@ func (comments CommentList) Issues() IssueList { return issueList } -func (comments CommentList) loadIssues(e Engine) error { +func (comments CommentList) loadIssues(e db.Engine) error { if len(comments) == 0 { return nil } @@ -337,7 +339,7 @@ func (comments CommentList) getDependentIssueIDs() []int64 { return keysInt64(ids) } -func (comments CommentList) loadDependentIssues(e Engine) error { +func (comments CommentList) loadDependentIssues(e db.Engine) error { if len(comments) == 0 { return nil } @@ -386,7 +388,7 @@ func (comments CommentList) loadDependentIssues(e Engine) error { return nil } -func (comments CommentList) loadAttachments(e Engine) (err error) { +func (comments CommentList) loadAttachments(e db.Engine) (err error) { if len(comments) == 0 { return nil } @@ -438,7 +440,7 @@ func (comments CommentList) getReviewIDs() []int64 { return keysInt64(ids) } -func (comments CommentList) loadReviews(e Engine) error { +func (comments CommentList) loadReviews(e db.Engine) error { if len(comments) == 0 { return nil } @@ -481,7 +483,7 @@ func (comments CommentList) loadReviews(e Engine) error { } // loadAttributes loads all attributes -func (comments CommentList) loadAttributes(e Engine) (err error) { +func (comments CommentList) loadAttributes(e db.Engine) (err error) { if err = comments.loadPosters(e); err != nil { return } @@ -524,20 +526,20 @@ func (comments CommentList) loadAttributes(e Engine) (err error) { // LoadAttributes loads attributes of the comments, except for attachments and // comments func (comments CommentList) LoadAttributes() error { - return comments.loadAttributes(x) + return comments.loadAttributes(db.GetEngine(db.DefaultContext)) } // LoadAttachments loads attachments func (comments CommentList) LoadAttachments() error { - return comments.loadAttachments(x) + return comments.loadAttachments(db.GetEngine(db.DefaultContext)) } // LoadPosters loads posters func (comments CommentList) LoadPosters() error { - return comments.loadPosters(x) + return comments.loadPosters(db.GetEngine(db.DefaultContext)) } // LoadIssues loads issues of comments func (comments CommentList) LoadIssues() error { - return comments.loadIssues(x) + return comments.loadIssues(db.GetEngine(db.DefaultContext)) } diff --git a/models/issue_comment_test.go b/models/issue_comment_test.go index 91dd5c17322dd..78199881c6027 100644 --- a/models/issue_comment_test.go +++ b/models/issue_comment_test.go @@ -8,15 +8,16 @@ import ( "testing" "time" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestCreateComment(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - issue := AssertExistsAndLoadBean(t, &Issue{}).(*Issue) - repo := AssertExistsAndLoadBean(t, &Repository{ID: issue.RepoID}).(*Repository) - doer := AssertExistsAndLoadBean(t, &User{ID: repo.OwnerID}).(*User) + issue := db.AssertExistsAndLoadBean(t, &Issue{}).(*Issue) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: issue.RepoID}).(*Repository) + doer := db.AssertExistsAndLoadBean(t, &User{ID: repo.OwnerID}).(*User) now := time.Now().Unix() comment, err := CreateComment(&CreateCommentOptions{ @@ -33,18 +34,18 @@ func TestCreateComment(t *testing.T) { assert.EqualValues(t, "Hello", comment.Content) assert.EqualValues(t, issue.ID, comment.IssueID) assert.EqualValues(t, doer.ID, comment.PosterID) - AssertInt64InRange(t, now, then, int64(comment.CreatedUnix)) - AssertExistsAndLoadBean(t, comment) // assert actually added to DB + db.AssertInt64InRange(t, now, then, int64(comment.CreatedUnix)) + db.AssertExistsAndLoadBean(t, comment) // assert actually added to DB - updatedIssue := AssertExistsAndLoadBean(t, &Issue{ID: issue.ID}).(*Issue) - AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix)) + updatedIssue := db.AssertExistsAndLoadBean(t, &Issue{ID: issue.ID}).(*Issue) + db.AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix)) } func TestFetchCodeComments(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - issue := AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue) - user := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + issue := db.AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue) + user := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) res, err := FetchCodeComments(issue, user) assert.NoError(t, err) assert.Contains(t, res, "README.md") @@ -52,7 +53,7 @@ func TestFetchCodeComments(t *testing.T) { assert.Len(t, res["README.md"][4], 1) assert.Equal(t, int64(4), res["README.md"][4][0].ID) - user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + user2 := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) res, err = FetchCodeComments(issue, user2) assert.NoError(t, err) assert.Len(t, res, 1) diff --git a/models/issue_dependency.go b/models/issue_dependency.go index 4008008f34741..0dfb99ca014de 100644 --- a/models/issue_dependency.go +++ b/models/issue_dependency.go @@ -5,6 +5,7 @@ package models import ( + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" @@ -20,6 +21,10 @@ type IssueDependency struct { UpdatedUnix timeutil.TimeStamp `xorm:"updated"` } +func init() { + db.RegisterModel(new(IssueDependency)) +} + // DependencyType Defines Dependency Type Constants type DependencyType int @@ -31,7 +36,7 @@ const ( // CreateIssueDependency creates a new dependency for an issue func CreateIssueDependency(user *User, issue, dep *Issue) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -72,7 +77,7 @@ func CreateIssueDependency(user *User, issue, dep *Issue) error { // RemoveIssueDependency removes a dependency from an issue func RemoveIssueDependency(user *User, issue, dep *Issue, depType DependencyType) (err error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -107,16 +112,16 @@ func RemoveIssueDependency(user *User, issue, dep *Issue, depType DependencyType } // Check if the dependency already exists -func issueDepExists(e Engine, issueID, depID int64) (bool, error) { +func issueDepExists(e db.Engine, issueID, depID int64) (bool, error) { return e.Where("(issue_id = ? AND dependency_id = ?)", issueID, depID).Exist(&IssueDependency{}) } // IssueNoDependenciesLeft checks if issue can be closed func IssueNoDependenciesLeft(issue *Issue) (bool, error) { - return issueNoDependenciesLeft(x, issue) + return issueNoDependenciesLeft(db.GetEngine(db.DefaultContext), issue) } -func issueNoDependenciesLeft(e Engine, issue *Issue) (bool, error) { +func issueNoDependenciesLeft(e db.Engine, issue *Issue) (bool, error) { exists, err := e. Table("issue_dependency"). Select("issue.*"). @@ -130,10 +135,10 @@ func issueNoDependenciesLeft(e Engine, issue *Issue) (bool, error) { // IsDependenciesEnabled returns if dependencies are enabled and returns the default setting if not set. func (repo *Repository) IsDependenciesEnabled() bool { - return repo.isDependenciesEnabled(x) + return repo.isDependenciesEnabled(db.GetEngine(db.DefaultContext)) } -func (repo *Repository) isDependenciesEnabled(e Engine) bool { +func (repo *Repository) isDependenciesEnabled(e db.Engine) bool { var u *RepoUnit var err error if u, err = repo.getUnit(e, UnitTypeIssues); err != nil { diff --git a/models/issue_dependency_test.go b/models/issue_dependency_test.go index bf323abb98bf8..10872645b0813 100644 --- a/models/issue_dependency_test.go +++ b/models/issue_dependency_test.go @@ -7,12 +7,13 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestCreateIssueDependency(t *testing.T) { // Prepare - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) user1, err := GetUserByID(1) assert.NoError(t, err) @@ -37,7 +38,7 @@ func TestCreateIssueDependency(t *testing.T) { assert.Error(t, err) assert.True(t, IsErrCircularDependency(err)) - _ = AssertExistsAndLoadBean(t, &Comment{Type: CommentTypeAddDependency, PosterID: user1.ID, IssueID: issue1.ID}) + _ = db.AssertExistsAndLoadBean(t, &Comment{Type: CommentTypeAddDependency, PosterID: user1.ID, IssueID: issue1.ID}) // Check if dependencies left is correct left, err := IssueNoDependenciesLeft(issue1) diff --git a/models/issue_label.go b/models/issue_label.go index 936dd71e381de..293b7140f7773 100644 --- a/models/issue_label.go +++ b/models/issue_label.go @@ -6,6 +6,7 @@ package models import ( + "context" "fmt" "html/template" "math" @@ -13,10 +14,10 @@ import ( "strconv" "strings" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" "xorm.io/builder" - "xorm.io/xorm" ) // LabelColorPattern is a regexp witch can validate LabelColor @@ -43,6 +44,11 @@ type Label struct { IsExcluded bool `xorm:"-"` } +func init() { + db.RegisterModel(new(Label)) + db.RegisterModel(new(IssueLabel)) +} + // GetLabelTemplateFile loads the label template file by given name, // then parses and returns a list of name-color pairs and optionally description. func GetLabelTemplateFile(name string) ([][3]string, error) { @@ -209,7 +215,7 @@ func LoadLabelsFormatted(labelTemplate string) (string, error) { return strings.Join(labels, ", "), err } -func initializeLabels(e Engine, id int64, labelTemplate string, isOrg bool) error { +func initializeLabels(e db.Engine, id int64, labelTemplate string, isOrg bool) error { list, err := GetLabelTemplateFile(labelTemplate) if err != nil { return err @@ -237,11 +243,11 @@ func initializeLabels(e Engine, id int64, labelTemplate string, isOrg bool) erro } // InitializeLabels adds a label set to a repository using a template -func InitializeLabels(ctx DBContext, repoID int64, labelTemplate string, isOrg bool) error { - return initializeLabels(ctx.e, repoID, labelTemplate, isOrg) +func InitializeLabels(ctx context.Context, repoID int64, labelTemplate string, isOrg bool) error { + return initializeLabels(db.GetEngine(ctx), repoID, labelTemplate, isOrg) } -func newLabel(e Engine, label *Label) error { +func newLabel(e db.Engine, label *Label) error { _, err := e.Insert(label) return err } @@ -251,25 +257,26 @@ func NewLabel(label *Label) error { if !LabelColorPattern.MatchString(label.Color) { return fmt.Errorf("bad color code: %s", label.Color) } - return newLabel(x, label) + return newLabel(db.GetEngine(db.DefaultContext), label) } // NewLabels creates new labels func NewLabels(labels ...*Label) error { - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { + ctx, committer, err := db.TxContext() + if err != nil { return err } + defer committer.Close() + for _, label := range labels { if !LabelColorPattern.MatchString(label.Color) { return fmt.Errorf("bad color code: %s", label.Color) } - if err := newLabel(sess, label); err != nil { + if err := newLabel(db.GetEngine(ctx), label); err != nil { return err } } - return sess.Commit() + return committer.Commit() } // UpdateLabel updates label information. @@ -277,7 +284,7 @@ func UpdateLabel(l *Label) error { if !LabelColorPattern.MatchString(l.Color) { return fmt.Errorf("bad color code: %s", l.Color) } - return updateLabelCols(x, l, "name", "description", "color") + return updateLabelCols(db.GetEngine(db.DefaultContext), l, "name", "description", "color") } // DeleteLabel delete a label @@ -290,7 +297,7 @@ func DeleteLabel(id, labelID int64) error { return err } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -320,7 +327,7 @@ func DeleteLabel(id, labelID int64) error { } // getLabelByID returns a label by label id -func getLabelByID(e Engine, labelID int64) (*Label, error) { +func getLabelByID(e db.Engine, labelID int64) (*Label, error) { if labelID <= 0 { return nil, ErrLabelNotExist{labelID} } @@ -337,13 +344,13 @@ func getLabelByID(e Engine, labelID int64) (*Label, error) { // GetLabelByID returns a label by given ID. func GetLabelByID(id int64) (*Label, error) { - return getLabelByID(x, id) + return getLabelByID(db.GetEngine(db.DefaultContext), id) } // GetLabelsByIDs returns a list of labels by IDs func GetLabelsByIDs(labelIDs []int64) ([]*Label, error) { labels := make([]*Label, 0, len(labelIDs)) - return labels, x.Table("label"). + return labels, db.GetEngine(db.DefaultContext).Table("label"). In("id", labelIDs). Asc("name"). Cols("id", "repo_id", "org_id"). @@ -358,7 +365,7 @@ func GetLabelsByIDs(labelIDs []int64) ([]*Label, error) { // \/ \/|__| \/ \/ // getLabelInRepoByName returns a label by Name in given repository. -func getLabelInRepoByName(e Engine, repoID int64, labelName string) (*Label, error) { +func getLabelInRepoByName(e db.Engine, repoID int64, labelName string) (*Label, error) { if len(labelName) == 0 || repoID <= 0 { return nil, ErrRepoLabelNotExist{0, repoID} } @@ -377,7 +384,7 @@ func getLabelInRepoByName(e Engine, repoID int64, labelName string) (*Label, err } // getLabelInRepoByID returns a label by ID in given repository. -func getLabelInRepoByID(e Engine, repoID, labelID int64) (*Label, error) { +func getLabelInRepoByID(e db.Engine, repoID, labelID int64) (*Label, error) { if labelID <= 0 || repoID <= 0 { return nil, ErrRepoLabelNotExist{labelID, repoID} } @@ -397,7 +404,7 @@ func getLabelInRepoByID(e Engine, repoID, labelID int64) (*Label, error) { // GetLabelInRepoByName returns a label by name in given repository. func GetLabelInRepoByName(repoID int64, labelName string) (*Label, error) { - return getLabelInRepoByName(x, repoID, labelName) + return getLabelInRepoByName(db.GetEngine(db.DefaultContext), repoID, labelName) } // GetLabelIDsInRepoByNames returns a list of labelIDs by names in a given @@ -405,7 +412,7 @@ func GetLabelInRepoByName(repoID int64, labelName string) (*Label, error) { // it silently ignores label names that do not belong to the repository. func GetLabelIDsInRepoByNames(repoID int64, labelNames []string) ([]int64, error) { labelIDs := make([]int64, 0, len(labelNames)) - return labelIDs, x.Table("label"). + return labelIDs, db.GetEngine(db.DefaultContext).Table("label"). Where("repo_id = ?", repoID). In("name", labelNames). Asc("name"). @@ -426,21 +433,21 @@ func BuildLabelNamesIssueIDsCondition(labelNames []string) *builder.Builder { // GetLabelInRepoByID returns a label by ID in given repository. func GetLabelInRepoByID(repoID, labelID int64) (*Label, error) { - return getLabelInRepoByID(x, repoID, labelID) + return getLabelInRepoByID(db.GetEngine(db.DefaultContext), repoID, labelID) } // GetLabelsInRepoByIDs returns a list of labels by IDs in given repository, // it silently ignores label IDs that do not belong to the repository. func GetLabelsInRepoByIDs(repoID int64, labelIDs []int64) ([]*Label, error) { labels := make([]*Label, 0, len(labelIDs)) - return labels, x. + return labels, db.GetEngine(db.DefaultContext). Where("repo_id = ?", repoID). In("id", labelIDs). Asc("name"). Find(&labels) } -func getLabelsByRepoID(e Engine, repoID int64, sortType string, listOptions ListOptions) ([]*Label, error) { +func getLabelsByRepoID(e db.Engine, repoID int64, sortType string, listOptions db.ListOptions) ([]*Label, error) { if repoID <= 0 { return nil, ErrRepoLabelNotExist{0, repoID} } @@ -459,20 +466,20 @@ func getLabelsByRepoID(e Engine, repoID int64, sortType string, listOptions List } if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, &listOptions) } return labels, sess.Find(&labels) } // GetLabelsByRepoID returns all labels that belong to given repository by ID. -func GetLabelsByRepoID(repoID int64, sortType string, listOptions ListOptions) ([]*Label, error) { - return getLabelsByRepoID(x, repoID, sortType, listOptions) +func GetLabelsByRepoID(repoID int64, sortType string, listOptions db.ListOptions) ([]*Label, error) { + return getLabelsByRepoID(db.GetEngine(db.DefaultContext), repoID, sortType, listOptions) } // CountLabelsByRepoID count number of all labels that belong to given repository by ID. func CountLabelsByRepoID(repoID int64) (int64, error) { - return x.Where("repo_id = ?", repoID).Count(&Label{}) + return db.GetEngine(db.DefaultContext).Where("repo_id = ?", repoID).Count(&Label{}) } // ________ @@ -483,7 +490,7 @@ func CountLabelsByRepoID(repoID int64) (int64, error) { // \/ /_____/ // getLabelInOrgByName returns a label by Name in given organization -func getLabelInOrgByName(e Engine, orgID int64, labelName string) (*Label, error) { +func getLabelInOrgByName(e db.Engine, orgID int64, labelName string) (*Label, error) { if len(labelName) == 0 || orgID <= 0 { return nil, ErrOrgLabelNotExist{0, orgID} } @@ -502,7 +509,7 @@ func getLabelInOrgByName(e Engine, orgID int64, labelName string) (*Label, error } // getLabelInOrgByID returns a label by ID in given organization. -func getLabelInOrgByID(e Engine, orgID, labelID int64) (*Label, error) { +func getLabelInOrgByID(e db.Engine, orgID, labelID int64) (*Label, error) { if labelID <= 0 || orgID <= 0 { return nil, ErrOrgLabelNotExist{labelID, orgID} } @@ -522,7 +529,7 @@ func getLabelInOrgByID(e Engine, orgID, labelID int64) (*Label, error) { // GetLabelInOrgByName returns a label by name in given organization. func GetLabelInOrgByName(orgID int64, labelName string) (*Label, error) { - return getLabelInOrgByName(x, orgID, labelName) + return getLabelInOrgByName(db.GetEngine(db.DefaultContext), orgID, labelName) } // GetLabelIDsInOrgByNames returns a list of labelIDs by names in a given @@ -533,7 +540,7 @@ func GetLabelIDsInOrgByNames(orgID int64, labelNames []string) ([]int64, error) } labelIDs := make([]int64, 0, len(labelNames)) - return labelIDs, x.Table("label"). + return labelIDs, db.GetEngine(db.DefaultContext).Table("label"). Where("org_id = ?", orgID). In("name", labelNames). Asc("name"). @@ -543,21 +550,21 @@ func GetLabelIDsInOrgByNames(orgID int64, labelNames []string) ([]int64, error) // GetLabelInOrgByID returns a label by ID in given organization. func GetLabelInOrgByID(orgID, labelID int64) (*Label, error) { - return getLabelInOrgByID(x, orgID, labelID) + return getLabelInOrgByID(db.GetEngine(db.DefaultContext), orgID, labelID) } // GetLabelsInOrgByIDs returns a list of labels by IDs in given organization, // it silently ignores label IDs that do not belong to the organization. func GetLabelsInOrgByIDs(orgID int64, labelIDs []int64) ([]*Label, error) { labels := make([]*Label, 0, len(labelIDs)) - return labels, x. + return labels, db.GetEngine(db.DefaultContext). Where("org_id = ?", orgID). In("id", labelIDs). Asc("name"). Find(&labels) } -func getLabelsByOrgID(e Engine, orgID int64, sortType string, listOptions ListOptions) ([]*Label, error) { +func getLabelsByOrgID(e db.Engine, orgID int64, sortType string, listOptions db.ListOptions) ([]*Label, error) { if orgID <= 0 { return nil, ErrOrgLabelNotExist{0, orgID} } @@ -576,20 +583,20 @@ func getLabelsByOrgID(e Engine, orgID int64, sortType string, listOptions ListOp } if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, &listOptions) } return labels, sess.Find(&labels) } // GetLabelsByOrgID returns all labels that belong to given organization by ID. -func GetLabelsByOrgID(orgID int64, sortType string, listOptions ListOptions) ([]*Label, error) { - return getLabelsByOrgID(x, orgID, sortType, listOptions) +func GetLabelsByOrgID(orgID int64, sortType string, listOptions db.ListOptions) ([]*Label, error) { + return getLabelsByOrgID(db.GetEngine(db.DefaultContext), orgID, sortType, listOptions) } // CountLabelsByOrgID count all labels that belong to given organization by ID. func CountLabelsByOrgID(orgID int64) (int64, error) { - return x.Where("org_id = ?", orgID).Count(&Label{}) + return db.GetEngine(db.DefaultContext).Where("org_id = ?", orgID).Count(&Label{}) } // .___ @@ -599,7 +606,7 @@ func CountLabelsByOrgID(orgID int64) (int64, error) { // |___/____ >____ >____/ \___ | // \/ \/ \/ -func getLabelsByIssueID(e Engine, issueID int64) ([]*Label, error) { +func getLabelsByIssueID(e db.Engine, issueID int64) ([]*Label, error) { var labels []*Label return labels, e.Where("issue_label.issue_id = ?", issueID). Join("LEFT", "issue_label", "issue_label.label_id = label.id"). @@ -609,10 +616,10 @@ func getLabelsByIssueID(e Engine, issueID int64) ([]*Label, error) { // GetLabelsByIssueID returns all labels that belong to given issue by ID. func GetLabelsByIssueID(issueID int64) ([]*Label, error) { - return getLabelsByIssueID(x, issueID) + return getLabelsByIssueID(db.GetEngine(db.DefaultContext), issueID) } -func updateLabelCols(e Engine, l *Label, cols ...string) error { +func updateLabelCols(e db.Engine, l *Label, cols ...string) error { _, err := e.ID(l.ID). SetExpr("num_issues", builder.Select("count(*)").From("issue_label"). @@ -644,19 +651,19 @@ type IssueLabel struct { LabelID int64 `xorm:"UNIQUE(s)"` } -func hasIssueLabel(e Engine, issueID, labelID int64) bool { +func hasIssueLabel(e db.Engine, issueID, labelID int64) bool { has, _ := e.Where("issue_id = ? AND label_id = ?", issueID, labelID).Get(new(IssueLabel)) return has } // HasIssueLabel returns true if issue has been labeled. func HasIssueLabel(issueID, labelID int64) bool { - return hasIssueLabel(x, issueID, labelID) + return hasIssueLabel(db.GetEngine(db.DefaultContext), issueID, labelID) } // newIssueLabel this function creates a new label it does not check if the label is valid for the issue // YOU MUST CHECK THIS BEFORE THIS FUNCTION -func newIssueLabel(e *xorm.Session, issue *Issue, label *Label, doer *User) (err error) { +func newIssueLabel(e db.Engine, issue *Issue, label *Label, doer *User) (err error) { if _, err = e.Insert(&IssueLabel{ IssueID: issue.ID, LabelID: label.ID, @@ -689,7 +696,7 @@ func NewIssueLabel(issue *Issue, label *Label, doer *User) (err error) { return nil } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -717,7 +724,7 @@ func NewIssueLabel(issue *Issue, label *Label, doer *User) (err error) { } // newIssueLabels add labels to an issue. It will check if the labels are valid for the issue -func newIssueLabels(e *xorm.Session, issue *Issue, labels []*Label, doer *User) (err error) { +func newIssueLabels(e db.Engine, issue *Issue, labels []*Label, doer *User) (err error) { if err = issue.loadRepo(e); err != nil { return err } @@ -738,25 +745,25 @@ func newIssueLabels(e *xorm.Session, issue *Issue, labels []*Label, doer *User) // NewIssueLabels creates a list of issue-label relations. func NewIssueLabels(issue *Issue, labels []*Label, doer *User) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { + ctx, committer, err := db.TxContext() + if err != nil { return err } + defer committer.Close() - if err = newIssueLabels(sess, issue, labels, doer); err != nil { + if err = newIssueLabels(db.GetEngine(ctx), issue, labels, doer); err != nil { return err } issue.Labels = nil - if err = issue.loadLabels(sess); err != nil { + if err = issue.loadLabels(db.GetEngine(ctx)); err != nil { return err } - return sess.Commit() + return committer.Commit() } -func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label, doer *User) (err error) { +func deleteIssueLabel(e db.Engine, issue *Issue, label *Label, doer *User) (err error) { if count, err := e.Delete(&IssueLabel{ IssueID: issue.ID, LabelID: label.ID, @@ -786,7 +793,7 @@ func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label, doer *User) ( // DeleteIssueLabel deletes issue-label relation. func DeleteIssueLabel(issue *Issue, label *Label, doer *User) (err error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -804,7 +811,7 @@ func DeleteIssueLabel(issue *Issue, label *Label, doer *User) (err error) { return sess.Commit() } -func deleteLabelsByRepoID(sess Engine, repoID int64) error { +func deleteLabelsByRepoID(sess db.Engine, repoID int64) error { deleteCond := builder.Select("id").From("label").Where(builder.Eq{"label.repo_id": repoID}) if _, err := sess.In("label_id", deleteCond). diff --git a/models/issue_label_test.go b/models/issue_label_test.go index 3dde1a40696a9..93807a326f808 100644 --- a/models/issue_label_test.go +++ b/models/issue_label_test.go @@ -8,29 +8,30 @@ import ( "html/template" "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) // TODO TestGetLabelTemplateFile func TestLabel_CalOpenIssues(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - label := AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label) + assert.NoError(t, db.PrepareTestDatabase()) + label := db.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label) label.CalOpenIssues() assert.EqualValues(t, 2, label.NumOpenIssues) } func TestLabel_ForegroundColor(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - label := AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label) + assert.NoError(t, db.PrepareTestDatabase()) + label := db.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label) assert.Equal(t, template.CSS("#000"), label.ForegroundColor()) - label = AssertExistsAndLoadBean(t, &Label{ID: 2}).(*Label) + label = db.AssertExistsAndLoadBean(t, &Label{ID: 2}).(*Label) assert.Equal(t, template.CSS("#fff"), label.ForegroundColor()) } func TestNewLabels(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) labels := []*Label{ {RepoID: 2, Name: "labelName2", Color: "#123456"}, {RepoID: 3, Name: "labelName3", Color: "#23456F"}, @@ -39,27 +40,27 @@ func TestNewLabels(t *testing.T) { assert.Error(t, NewLabel(&Label{RepoID: 3, Name: "invalid Color", Color: "123456"})) assert.Error(t, NewLabel(&Label{RepoID: 3, Name: "invalid Color", Color: "#12345G"})) for _, label := range labels { - AssertNotExistsBean(t, label) + db.AssertNotExistsBean(t, label) } assert.NoError(t, NewLabels(labels...)) for _, label := range labels { - AssertExistsAndLoadBean(t, label, Cond("id = ?", label.ID)) + db.AssertExistsAndLoadBean(t, label, db.Cond("id = ?", label.ID)) } CheckConsistencyFor(t, &Label{}, &Repository{}) } func TestGetLabelByID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) label, err := GetLabelByID(1) assert.NoError(t, err) assert.EqualValues(t, 1, label.ID) - _, err = GetLabelByID(NonexistentID) + _, err = GetLabelByID(db.NonexistentID) assert.True(t, IsErrLabelNotExist(err)) } func TestGetLabelInRepoByName(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) label, err := GetLabelInRepoByName(1, "label1") assert.NoError(t, err) assert.EqualValues(t, 1, label.ID) @@ -68,12 +69,12 @@ func TestGetLabelInRepoByName(t *testing.T) { _, err = GetLabelInRepoByName(1, "") assert.True(t, IsErrRepoLabelNotExist(err)) - _, err = GetLabelInRepoByName(NonexistentID, "nonexistent") + _, err = GetLabelInRepoByName(db.NonexistentID, "nonexistent") assert.True(t, IsErrRepoLabelNotExist(err)) } func TestGetLabelInRepoByNames(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) labelIDs, err := GetLabelIDsInRepoByNames(1, []string{"label1", "label2"}) assert.NoError(t, err) @@ -84,7 +85,7 @@ func TestGetLabelInRepoByNames(t *testing.T) { } func TestGetLabelInRepoByNamesDiscardsNonExistentLabels(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // label3 doesn't exists.. See labels.yml labelIDs, err := GetLabelIDsInRepoByNames(1, []string{"label1", "label2", "label3"}) assert.NoError(t, err) @@ -97,7 +98,7 @@ func TestGetLabelInRepoByNamesDiscardsNonExistentLabels(t *testing.T) { } func TestGetLabelInRepoByID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) label, err := GetLabelInRepoByID(1, 1) assert.NoError(t, err) assert.EqualValues(t, 1, label.ID) @@ -105,13 +106,13 @@ func TestGetLabelInRepoByID(t *testing.T) { _, err = GetLabelInRepoByID(1, -1) assert.True(t, IsErrRepoLabelNotExist(err)) - _, err = GetLabelInRepoByID(NonexistentID, NonexistentID) + _, err = GetLabelInRepoByID(db.NonexistentID, db.NonexistentID) assert.True(t, IsErrRepoLabelNotExist(err)) } func TestGetLabelsInRepoByIDs(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - labels, err := GetLabelsInRepoByIDs(1, []int64{1, 2, NonexistentID}) + assert.NoError(t, db.PrepareTestDatabase()) + labels, err := GetLabelsInRepoByIDs(1, []int64{1, 2, db.NonexistentID}) assert.NoError(t, err) if assert.Len(t, labels, 2) { assert.EqualValues(t, 1, labels[0].ID) @@ -120,9 +121,9 @@ func TestGetLabelsInRepoByIDs(t *testing.T) { } func TestGetLabelsByRepoID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testSuccess := func(repoID int64, sortType string, expectedIssueIDs []int64) { - labels, err := GetLabelsByRepoID(repoID, sortType, ListOptions{}) + labels, err := GetLabelsByRepoID(repoID, sortType, db.ListOptions{}) assert.NoError(t, err) assert.Len(t, labels, len(expectedIssueIDs)) for i, label := range labels { @@ -138,7 +139,7 @@ func TestGetLabelsByRepoID(t *testing.T) { // Org versions func TestGetLabelInOrgByName(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) label, err := GetLabelInOrgByName(3, "orglabel3") assert.NoError(t, err) assert.EqualValues(t, 3, label.ID) @@ -153,12 +154,12 @@ func TestGetLabelInOrgByName(t *testing.T) { _, err = GetLabelInOrgByName(-1, "orglabel3") assert.True(t, IsErrOrgLabelNotExist(err)) - _, err = GetLabelInOrgByName(NonexistentID, "nonexistent") + _, err = GetLabelInOrgByName(db.NonexistentID, "nonexistent") assert.True(t, IsErrOrgLabelNotExist(err)) } func TestGetLabelInOrgByNames(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) labelIDs, err := GetLabelIDsInOrgByNames(3, []string{"orglabel3", "orglabel4"}) assert.NoError(t, err) @@ -169,7 +170,7 @@ func TestGetLabelInOrgByNames(t *testing.T) { } func TestGetLabelInOrgByNamesDiscardsNonExistentLabels(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // orglabel99 doesn't exists.. See labels.yml labelIDs, err := GetLabelIDsInOrgByNames(3, []string{"orglabel3", "orglabel4", "orglabel99"}) assert.NoError(t, err) @@ -182,7 +183,7 @@ func TestGetLabelInOrgByNamesDiscardsNonExistentLabels(t *testing.T) { } func TestGetLabelInOrgByID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) label, err := GetLabelInOrgByID(3, 3) assert.NoError(t, err) assert.EqualValues(t, 3, label.ID) @@ -196,13 +197,13 @@ func TestGetLabelInOrgByID(t *testing.T) { _, err = GetLabelInOrgByID(-1, 3) assert.True(t, IsErrOrgLabelNotExist(err)) - _, err = GetLabelInOrgByID(NonexistentID, NonexistentID) + _, err = GetLabelInOrgByID(db.NonexistentID, db.NonexistentID) assert.True(t, IsErrOrgLabelNotExist(err)) } func TestGetLabelsInOrgByIDs(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - labels, err := GetLabelsInOrgByIDs(3, []int64{3, 4, NonexistentID}) + assert.NoError(t, db.PrepareTestDatabase()) + labels, err := GetLabelsInOrgByIDs(3, []int64{3, 4, db.NonexistentID}) assert.NoError(t, err) if assert.Len(t, labels, 2) { assert.EqualValues(t, 3, labels[0].ID) @@ -211,9 +212,9 @@ func TestGetLabelsInOrgByIDs(t *testing.T) { } func TestGetLabelsByOrgID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testSuccess := func(orgID int64, sortType string, expectedIssueIDs []int64) { - labels, err := GetLabelsByOrgID(orgID, sortType, ListOptions{}) + labels, err := GetLabelsByOrgID(orgID, sortType, db.ListOptions{}) assert.NoError(t, err) assert.Len(t, labels, len(expectedIssueIDs)) for i, label := range labels { @@ -226,31 +227,31 @@ func TestGetLabelsByOrgID(t *testing.T) { testSuccess(3, "default", []int64{3, 4}) var err error - _, err = GetLabelsByOrgID(0, "leastissues", ListOptions{}) + _, err = GetLabelsByOrgID(0, "leastissues", db.ListOptions{}) assert.True(t, IsErrOrgLabelNotExist(err)) - _, err = GetLabelsByOrgID(-1, "leastissues", ListOptions{}) + _, err = GetLabelsByOrgID(-1, "leastissues", db.ListOptions{}) assert.True(t, IsErrOrgLabelNotExist(err)) } // func TestGetLabelsByIssueID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) labels, err := GetLabelsByIssueID(1) assert.NoError(t, err) if assert.Len(t, labels, 1) { assert.EqualValues(t, 1, labels[0].ID) } - labels, err = GetLabelsByIssueID(NonexistentID) + labels, err = GetLabelsByIssueID(db.NonexistentID) assert.NoError(t, err) assert.Len(t, labels, 0) } func TestUpdateLabel(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - label := AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label) + assert.NoError(t, db.PrepareTestDatabase()) + label := db.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label) // make sure update wont overwrite it update := &Label{ ID: label.ID, @@ -261,7 +262,7 @@ func TestUpdateLabel(t *testing.T) { label.Color = update.Color label.Name = update.Name assert.NoError(t, UpdateLabel(update)) - newLabel := AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label) + newLabel := db.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label) assert.EqualValues(t, label.ID, newLabel.ID) assert.EqualValues(t, label.Color, newLabel.Color) assert.EqualValues(t, label.Name, newLabel.Name) @@ -270,43 +271,43 @@ func TestUpdateLabel(t *testing.T) { } func TestDeleteLabel(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - label := AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label) + assert.NoError(t, db.PrepareTestDatabase()) + label := db.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label) assert.NoError(t, DeleteLabel(label.RepoID, label.ID)) - AssertNotExistsBean(t, &Label{ID: label.ID, RepoID: label.RepoID}) + db.AssertNotExistsBean(t, &Label{ID: label.ID, RepoID: label.RepoID}) assert.NoError(t, DeleteLabel(label.RepoID, label.ID)) - AssertNotExistsBean(t, &Label{ID: label.ID}) + db.AssertNotExistsBean(t, &Label{ID: label.ID}) - assert.NoError(t, DeleteLabel(NonexistentID, NonexistentID)) + assert.NoError(t, DeleteLabel(db.NonexistentID, db.NonexistentID)) CheckConsistencyFor(t, &Label{}, &Repository{}) } func TestHasIssueLabel(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) assert.True(t, HasIssueLabel(1, 1)) assert.False(t, HasIssueLabel(1, 2)) - assert.False(t, HasIssueLabel(NonexistentID, NonexistentID)) + assert.False(t, HasIssueLabel(db.NonexistentID, db.NonexistentID)) } func TestNewIssueLabel(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - label := AssertExistsAndLoadBean(t, &Label{ID: 2}).(*Label) - issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) - doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + label := db.AssertExistsAndLoadBean(t, &Label{ID: 2}).(*Label) + issue := db.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) + doer := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) // add new IssueLabel prevNumIssues := label.NumIssues assert.NoError(t, NewIssueLabel(issue, label, doer)) - AssertExistsAndLoadBean(t, &IssueLabel{IssueID: issue.ID, LabelID: label.ID}) - AssertExistsAndLoadBean(t, &Comment{ + db.AssertExistsAndLoadBean(t, &IssueLabel{IssueID: issue.ID, LabelID: label.ID}) + db.AssertExistsAndLoadBean(t, &Comment{ Type: CommentTypeLabel, PosterID: doer.ID, IssueID: issue.ID, LabelID: label.ID, Content: "1", }) - label = AssertExistsAndLoadBean(t, &Label{ID: 2}).(*Label) + label = db.AssertExistsAndLoadBean(t, &Label{ID: 2}).(*Label) assert.EqualValues(t, prevNumIssues+1, label.NumIssues) // re-add existing IssueLabel @@ -315,26 +316,26 @@ func TestNewIssueLabel(t *testing.T) { } func TestNewIssueLabels(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - label1 := AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label) - label2 := AssertExistsAndLoadBean(t, &Label{ID: 2}).(*Label) - issue := AssertExistsAndLoadBean(t, &Issue{ID: 5}).(*Issue) - doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + label1 := db.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label) + label2 := db.AssertExistsAndLoadBean(t, &Label{ID: 2}).(*Label) + issue := db.AssertExistsAndLoadBean(t, &Issue{ID: 5}).(*Issue) + doer := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) assert.NoError(t, NewIssueLabels(issue, []*Label{label1, label2}, doer)) - AssertExistsAndLoadBean(t, &IssueLabel{IssueID: issue.ID, LabelID: label1.ID}) - AssertExistsAndLoadBean(t, &Comment{ + db.AssertExistsAndLoadBean(t, &IssueLabel{IssueID: issue.ID, LabelID: label1.ID}) + db.AssertExistsAndLoadBean(t, &Comment{ Type: CommentTypeLabel, PosterID: doer.ID, IssueID: issue.ID, LabelID: label1.ID, Content: "1", }) - AssertExistsAndLoadBean(t, &IssueLabel{IssueID: issue.ID, LabelID: label1.ID}) - label1 = AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label) + db.AssertExistsAndLoadBean(t, &IssueLabel{IssueID: issue.ID, LabelID: label1.ID}) + label1 = db.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label) assert.EqualValues(t, 3, label1.NumIssues) assert.EqualValues(t, 1, label1.NumClosedIssues) - label2 = AssertExistsAndLoadBean(t, &Label{ID: 2}).(*Label) + label2 = db.AssertExistsAndLoadBean(t, &Label{ID: 2}).(*Label) assert.EqualValues(t, 1, label2.NumIssues) assert.EqualValues(t, 1, label2.NumClosedIssues) @@ -345,15 +346,15 @@ func TestNewIssueLabels(t *testing.T) { } func TestDeleteIssueLabel(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testSuccess := func(labelID, issueID, doerID int64) { - label := AssertExistsAndLoadBean(t, &Label{ID: labelID}).(*Label) - issue := AssertExistsAndLoadBean(t, &Issue{ID: issueID}).(*Issue) - doer := AssertExistsAndLoadBean(t, &User{ID: doerID}).(*User) + label := db.AssertExistsAndLoadBean(t, &Label{ID: labelID}).(*Label) + issue := db.AssertExistsAndLoadBean(t, &Issue{ID: issueID}).(*Issue) + doer := db.AssertExistsAndLoadBean(t, &User{ID: doerID}).(*User) expectedNumIssues := label.NumIssues expectedNumClosedIssues := label.NumClosedIssues - if BeanExists(t, &IssueLabel{IssueID: issueID, LabelID: labelID}) { + if db.BeanExists(t, &IssueLabel{IssueID: issueID, LabelID: labelID}) { expectedNumIssues-- if issue.IsClosed { expectedNumClosedIssues-- @@ -361,14 +362,14 @@ func TestDeleteIssueLabel(t *testing.T) { } assert.NoError(t, DeleteIssueLabel(issue, label, doer)) - AssertNotExistsBean(t, &IssueLabel{IssueID: issueID, LabelID: labelID}) - AssertExistsAndLoadBean(t, &Comment{ + db.AssertNotExistsBean(t, &IssueLabel{IssueID: issueID, LabelID: labelID}) + db.AssertExistsAndLoadBean(t, &Comment{ Type: CommentTypeLabel, PosterID: doerID, IssueID: issueID, LabelID: labelID, }, `content=""`) - label = AssertExistsAndLoadBean(t, &Label{ID: labelID}).(*Label) + label = db.AssertExistsAndLoadBean(t, &Label{ID: labelID}).(*Label) assert.EqualValues(t, expectedNumIssues, label.NumIssues) assert.EqualValues(t, expectedNumClosedIssues, label.NumClosedIssues) } diff --git a/models/issue_list.go b/models/issue_list.go index 87ad318dcf2d6..ac7ec7ccbf240 100644 --- a/models/issue_list.go +++ b/models/issue_list.go @@ -7,6 +7,7 @@ package models import ( "fmt" + "code.gitea.io/gitea/models/db" "xorm.io/builder" ) @@ -28,7 +29,7 @@ func (issues IssueList) getRepoIDs() []int64 { return keysInt64(repoIDs) } -func (issues IssueList) loadRepositories(e Engine) ([]*Repository, error) { +func (issues IssueList) loadRepositories(e db.Engine) ([]*Repository, error) { if len(issues) == 0 { return nil, nil } @@ -62,7 +63,7 @@ func (issues IssueList) loadRepositories(e Engine) ([]*Repository, error) { // LoadRepositories loads issues' all repositories func (issues IssueList) LoadRepositories() ([]*Repository, error) { - return issues.loadRepositories(x) + return issues.loadRepositories(db.GetEngine(db.DefaultContext)) } func (issues IssueList) getPosterIDs() []int64 { @@ -75,7 +76,7 @@ func (issues IssueList) getPosterIDs() []int64 { return keysInt64(posterIDs) } -func (issues IssueList) loadPosters(e Engine) error { +func (issues IssueList) loadPosters(e db.Engine) error { if len(issues) == 0 { return nil } @@ -118,7 +119,7 @@ func (issues IssueList) getIssueIDs() []int64 { return ids } -func (issues IssueList) loadLabels(e Engine) error { +func (issues IssueList) loadLabels(e db.Engine) error { if len(issues) == 0 { return nil } @@ -181,7 +182,7 @@ func (issues IssueList) getMilestoneIDs() []int64 { return keysInt64(ids) } -func (issues IssueList) loadMilestones(e Engine) error { +func (issues IssueList) loadMilestones(e db.Engine) error { milestoneIDs := issues.getMilestoneIDs() if len(milestoneIDs) == 0 { return nil @@ -210,7 +211,7 @@ func (issues IssueList) loadMilestones(e Engine) error { return nil } -func (issues IssueList) loadAssignees(e Engine) error { +func (issues IssueList) loadAssignees(e db.Engine) error { if len(issues) == 0 { return nil } @@ -271,7 +272,7 @@ func (issues IssueList) getPullIssueIDs() []int64 { return ids } -func (issues IssueList) loadPullRequests(e Engine) error { +func (issues IssueList) loadPullRequests(e db.Engine) error { issuesIDs := issues.getPullIssueIDs() if len(issuesIDs) == 0 { return nil @@ -315,7 +316,7 @@ func (issues IssueList) loadPullRequests(e Engine) error { return nil } -func (issues IssueList) loadAttachments(e Engine) (err error) { +func (issues IssueList) loadAttachments(e db.Engine) (err error) { if len(issues) == 0 { return nil } @@ -360,7 +361,7 @@ func (issues IssueList) loadAttachments(e Engine) (err error) { return nil } -func (issues IssueList) loadComments(e Engine, cond builder.Cond) (err error) { +func (issues IssueList) loadComments(e db.Engine, cond builder.Cond) (err error) { if len(issues) == 0 { return nil } @@ -406,7 +407,7 @@ func (issues IssueList) loadComments(e Engine, cond builder.Cond) (err error) { return nil } -func (issues IssueList) loadTotalTrackedTimes(e Engine) (err error) { +func (issues IssueList) loadTotalTrackedTimes(e db.Engine) (err error) { type totalTimesByIssue struct { IssueID int64 Time int64 @@ -466,7 +467,7 @@ func (issues IssueList) loadTotalTrackedTimes(e Engine) (err error) { } // loadAttributes loads all attributes, expect for attachments and comments -func (issues IssueList) loadAttributes(e Engine) error { +func (issues IssueList) loadAttributes(e db.Engine) error { if _, err := issues.loadRepositories(e); err != nil { return fmt.Errorf("issue.loadAttributes: loadRepositories: %v", err) } @@ -501,36 +502,36 @@ func (issues IssueList) loadAttributes(e Engine) error { // LoadAttributes loads attributes of the issues, except for attachments and // comments func (issues IssueList) LoadAttributes() error { - return issues.loadAttributes(x) + return issues.loadAttributes(db.GetEngine(db.DefaultContext)) } // LoadAttachments loads attachments func (issues IssueList) LoadAttachments() error { - return issues.loadAttachments(x) + return issues.loadAttachments(db.GetEngine(db.DefaultContext)) } // LoadComments loads comments func (issues IssueList) LoadComments() error { - return issues.loadComments(x, builder.NewCond()) + return issues.loadComments(db.GetEngine(db.DefaultContext), builder.NewCond()) } // LoadDiscussComments loads discuss comments func (issues IssueList) LoadDiscussComments() error { - return issues.loadComments(x, builder.Eq{"comment.type": CommentTypeComment}) + return issues.loadComments(db.GetEngine(db.DefaultContext), builder.Eq{"comment.type": CommentTypeComment}) } // LoadPullRequests loads pull requests func (issues IssueList) LoadPullRequests() error { - return issues.loadPullRequests(x) + return issues.loadPullRequests(db.GetEngine(db.DefaultContext)) } // GetApprovalCounts returns a map of issue ID to slice of approval counts // FIXME: only returns official counts due to double counting of non-official approvals func (issues IssueList) GetApprovalCounts() (map[int64][]*ReviewCount, error) { - return issues.getApprovalCounts(x) + return issues.getApprovalCounts(db.GetEngine(db.DefaultContext)) } -func (issues IssueList) getApprovalCounts(e Engine) (map[int64][]*ReviewCount, error) { +func (issues IssueList) getApprovalCounts(e db.Engine) (map[int64][]*ReviewCount, error) { rCounts := make([]*ReviewCount, 0, 2*len(issues)) ids := make([]int64, len(issues)) for i, issue := range issues { diff --git a/models/issue_list_test.go b/models/issue_list_test.go index c9c39332c7a8e..dadca3844c887 100644 --- a/models/issue_list_test.go +++ b/models/issue_list_test.go @@ -7,18 +7,19 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" ) func TestIssueList_LoadRepositories(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) issueList := IssueList{ - AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue), - AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue), - AssertExistsAndLoadBean(t, &Issue{ID: 4}).(*Issue), + db.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue), + db.AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue), + db.AssertExistsAndLoadBean(t, &Issue{ID: 4}).(*Issue), } repos, err := issueList.LoadRepositories() @@ -30,11 +31,11 @@ func TestIssueList_LoadRepositories(t *testing.T) { } func TestIssueList_LoadAttributes(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) setting.Service.EnableTimetracking = true issueList := IssueList{ - AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue), - AssertExistsAndLoadBean(t, &Issue{ID: 4}).(*Issue), + db.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue), + db.AssertExistsAndLoadBean(t, &Issue{ID: 4}).(*Issue), } assert.NoError(t, issueList.LoadAttributes()) @@ -42,7 +43,7 @@ func TestIssueList_LoadAttributes(t *testing.T) { assert.EqualValues(t, issue.RepoID, issue.Repo.ID) for _, label := range issue.Labels { assert.EqualValues(t, issue.RepoID, label.RepoID) - AssertExistsAndLoadBean(t, &IssueLabel{IssueID: issue.ID, LabelID: label.ID}) + db.AssertExistsAndLoadBean(t, &IssueLabel{IssueID: issue.ID, LabelID: label.ID}) } if issue.PosterID > 0 { assert.EqualValues(t, issue.PosterID, issue.Poster.ID) diff --git a/models/issue_lock.go b/models/issue_lock.go index 78b2c5cdb795a..d8e3b4c0aba82 100644 --- a/models/issue_lock.go +++ b/models/issue_lock.go @@ -4,6 +4,8 @@ package models +import "code.gitea.io/gitea/models/db" + // IssueLockOptions defines options for locking and/or unlocking an issue/PR type IssueLockOptions struct { Doer *User @@ -35,7 +37,7 @@ func updateIssueLock(opts *IssueLockOptions, lock bool) error { commentType = CommentTypeUnlock } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err diff --git a/models/issue_milestone.go b/models/issue_milestone.go index e6976a46c71e2..3898e5b397856 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" @@ -42,6 +43,10 @@ type Milestone struct { TimeSinceUpdate int64 `xorm:"-"` } +func init() { + db.RegisterModel(new(Milestone)) +} + // BeforeUpdate is invoked from XORM before updating this object. func (m *Milestone) BeforeUpdate() { if m.NumIssues > 0 { @@ -80,7 +85,7 @@ func (m *Milestone) State() api.StateType { // NewMilestone creates new milestone of repository. func NewMilestone(m *Milestone) (err error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -98,7 +103,7 @@ func NewMilestone(m *Milestone) (err error) { return sess.Commit() } -func getMilestoneByRepoID(e Engine, repoID, id int64) (*Milestone, error) { +func getMilestoneByRepoID(e db.Engine, repoID, id int64) (*Milestone, error) { m := new(Milestone) has, err := e.ID(id).Where("repo_id=?", repoID).Get(m) if err != nil { @@ -111,13 +116,13 @@ func getMilestoneByRepoID(e Engine, repoID, id int64) (*Milestone, error) { // GetMilestoneByRepoID returns the milestone in a repository. func GetMilestoneByRepoID(repoID, id int64) (*Milestone, error) { - return getMilestoneByRepoID(x, repoID, id) + return getMilestoneByRepoID(db.GetEngine(db.DefaultContext), repoID, id) } // GetMilestoneByRepoIDANDName return a milestone if one exist by name and repo func GetMilestoneByRepoIDANDName(repoID int64, name string) (*Milestone, error) { var mile Milestone - has, err := x.Where("repo_id=? AND name=?", repoID, name).Get(&mile) + has, err := db.GetEngine(db.DefaultContext).Where("repo_id=? AND name=?", repoID, name).Get(&mile) if err != nil { return nil, err } @@ -129,10 +134,10 @@ func GetMilestoneByRepoIDANDName(repoID int64, name string) (*Milestone, error) // GetMilestoneByID returns the milestone via id . func GetMilestoneByID(id int64) (*Milestone, error) { - return getMilestoneByID(x, id) + return getMilestoneByID(db.GetEngine(db.DefaultContext), id) } -func getMilestoneByID(e Engine, id int64) (*Milestone, error) { +func getMilestoneByID(e db.Engine, id int64) (*Milestone, error) { var m Milestone has, err := e.ID(id).Get(&m) if err != nil { @@ -145,7 +150,7 @@ func getMilestoneByID(e Engine, id int64) (*Milestone, error) { // UpdateMilestone updates information of given milestone. func UpdateMilestone(m *Milestone, oldIsClosed bool) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -169,7 +174,7 @@ func UpdateMilestone(m *Milestone, oldIsClosed bool) error { return sess.Commit() } -func updateMilestone(e Engine, m *Milestone) error { +func updateMilestone(e db.Engine, m *Milestone) error { m.Name = strings.TrimSpace(m.Name) _, err := e.ID(m.ID).AllCols().Update(m) if err != nil { @@ -179,7 +184,7 @@ func updateMilestone(e Engine, m *Milestone) error { } // updateMilestoneCounters calculates NumIssues, NumClosesIssues and Completeness -func updateMilestoneCounters(e Engine, id int64) error { +func updateMilestoneCounters(e db.Engine, id int64) error { _, err := e.ID(id). SetExpr("num_issues", builder.Select("count(*)").From("issue").Where( builder.Eq{"milestone_id": id}, @@ -202,7 +207,7 @@ func updateMilestoneCounters(e Engine, id int64) error { // ChangeMilestoneStatusByRepoIDAndID changes a milestone open/closed status if the milestone ID is in the repo. func ChangeMilestoneStatusByRepoIDAndID(repoID, milestoneID int64, isClosed bool) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -229,7 +234,7 @@ func ChangeMilestoneStatusByRepoIDAndID(repoID, milestoneID int64, isClosed bool // ChangeMilestoneStatus changes the milestone open/closed status. func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -242,7 +247,7 @@ func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) { return sess.Commit() } -func changeMilestoneStatus(e Engine, m *Milestone, isClosed bool) error { +func changeMilestoneStatus(e db.Engine, m *Milestone, isClosed bool) error { m.IsClosed = isClosed if isClosed { m.ClosedDateUnix = timeutil.TimeStampNow() @@ -298,7 +303,7 @@ func changeMilestoneAssign(e *xorm.Session, doer *User, issue *Issue, oldMilesto // ChangeMilestoneAssign changes assignment of milestone for issue. func ChangeMilestoneAssign(issue *Issue, doer *User, oldMilestoneID int64) (err error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -329,7 +334,7 @@ func DeleteMilestoneByRepoID(repoID, id int64) error { return err } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -373,7 +378,7 @@ func (milestones MilestoneList) getMilestoneIDs() []int64 { // GetMilestonesOption contain options to get milestones type GetMilestonesOption struct { - ListOptions + db.ListOptions RepoID int64 State api.StateType Name string @@ -405,10 +410,10 @@ func (opts GetMilestonesOption) toCond() builder.Cond { // GetMilestones returns milestones filtered by GetMilestonesOption's func GetMilestones(opts GetMilestonesOption) (MilestoneList, int64, error) { - sess := x.Where(opts.toCond()) + sess := db.GetEngine(db.DefaultContext).Where(opts.toCond()) if opts.Page != 0 { - sess = opts.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, &opts) } switch opts.SortType { @@ -436,7 +441,7 @@ func GetMilestones(opts GetMilestonesOption) (MilestoneList, int64, error) { // SearchMilestones search milestones func SearchMilestones(repoCond builder.Cond, page int, isClosed bool, sortType string, keyword string) (MilestoneList, error) { miles := make([]*Milestone, 0, setting.UI.IssuePagingNum) - sess := x.Where("is_closed = ?", isClosed) + sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", isClosed) if len(keyword) > 0 { sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) } @@ -497,7 +502,7 @@ func GetMilestonesStatsByRepoCond(repoCond builder.Cond) (*MilestonesStats, erro var err error stats := &MilestonesStats{} - sess := x.Where("is_closed = ?", false) + sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", false) if repoCond.IsValid() { sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond))) } @@ -506,7 +511,7 @@ func GetMilestonesStatsByRepoCond(repoCond builder.Cond) (*MilestonesStats, erro return nil, err } - sess = x.Where("is_closed = ?", true) + sess = db.GetEngine(db.DefaultContext).Where("is_closed = ?", true) if repoCond.IsValid() { sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond))) } @@ -523,7 +528,7 @@ func GetMilestonesStatsByRepoCondAndKw(repoCond builder.Cond, keyword string) (* var err error stats := &MilestonesStats{} - sess := x.Where("is_closed = ?", false) + sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", false) if len(keyword) > 0 { sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) } @@ -535,7 +540,7 @@ func GetMilestonesStatsByRepoCondAndKw(repoCond builder.Cond, keyword string) (* return nil, err } - sess = x.Where("is_closed = ?", true) + sess = db.GetEngine(db.DefaultContext).Where("is_closed = ?", true) if len(keyword) > 0 { sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) } @@ -550,13 +555,13 @@ func GetMilestonesStatsByRepoCondAndKw(repoCond builder.Cond, keyword string) (* return stats, nil } -func countRepoMilestones(e Engine, repoID int64) (int64, error) { +func countRepoMilestones(e db.Engine, repoID int64) (int64, error) { return e. Where("repo_id=?", repoID). Count(new(Milestone)) } -func countRepoClosedMilestones(e Engine, repoID int64) (int64, error) { +func countRepoClosedMilestones(e db.Engine, repoID int64) (int64, error) { return e. Where("repo_id=? AND is_closed=?", repoID, true). Count(new(Milestone)) @@ -564,12 +569,12 @@ func countRepoClosedMilestones(e Engine, repoID int64) (int64, error) { // CountRepoClosedMilestones returns number of closed milestones in given repository. func CountRepoClosedMilestones(repoID int64) (int64, error) { - return countRepoClosedMilestones(x, repoID) + return countRepoClosedMilestones(db.GetEngine(db.DefaultContext), repoID) } // CountMilestonesByRepoCond map from repo conditions to number of milestones matching the options` func CountMilestonesByRepoCond(repoCond builder.Cond, isClosed bool) (map[int64]int64, error) { - sess := x.Where("is_closed = ?", isClosed) + sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", isClosed) if repoCond.IsValid() { sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond)) } @@ -594,7 +599,7 @@ func CountMilestonesByRepoCond(repoCond builder.Cond, isClosed bool) (map[int64] // CountMilestonesByRepoCondAndKw map from repo conditions and the keyword of milestones' name to number of milestones matching the options` func CountMilestonesByRepoCondAndKw(repoCond builder.Cond, keyword string, isClosed bool) (map[int64]int64, error) { - sess := x.Where("is_closed = ?", isClosed) + sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", isClosed) if len(keyword) > 0 { sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) } @@ -620,7 +625,7 @@ func CountMilestonesByRepoCondAndKw(repoCond builder.Cond, keyword string, isClo return countMap, nil } -func updateRepoMilestoneNum(e Engine, repoID int64) error { +func updateRepoMilestoneNum(e db.Engine, repoID int64) error { _, err := e.Exec("UPDATE `repository` SET num_milestones=(SELECT count(*) FROM milestone WHERE repo_id=?),num_closed_milestones=(SELECT count(*) FROM milestone WHERE repo_id=? AND is_closed=?) WHERE id=?", repoID, repoID, @@ -637,7 +642,7 @@ func updateRepoMilestoneNum(e Engine, repoID int64) error { // |_||_| \__,_|\___|_|\_\___|\__,_| |_| |_|_| |_| |_|\___||___/ // -func (milestones MilestoneList) loadTotalTrackedTimes(e Engine) error { +func (milestones MilestoneList) loadTotalTrackedTimes(e db.Engine) error { type totalTimesByMilestone struct { MilestoneID int64 Time int64 @@ -677,7 +682,7 @@ func (milestones MilestoneList) loadTotalTrackedTimes(e Engine) error { return nil } -func (m *Milestone) loadTotalTrackedTime(e Engine) error { +func (m *Milestone) loadTotalTrackedTime(e db.Engine) error { type totalTimesByMilestone struct { MilestoneID int64 Time int64 @@ -702,10 +707,10 @@ func (m *Milestone) loadTotalTrackedTime(e Engine) error { // LoadTotalTrackedTimes loads for every milestone in the list the TotalTrackedTime by a batch request func (milestones MilestoneList) LoadTotalTrackedTimes() error { - return milestones.loadTotalTrackedTimes(x) + return milestones.loadTotalTrackedTimes(db.GetEngine(db.DefaultContext)) } // LoadTotalTrackedTime loads the tracked time for the milestone func (m *Milestone) LoadTotalTrackedTime() error { - return m.loadTotalTrackedTime(x) + return m.loadTotalTrackedTime(db.GetEngine(db.DefaultContext)) } diff --git a/models/issue_milestone_test.go b/models/issue_milestone_test.go index 1472c951eb405..099fe47c7c1ca 100644 --- a/models/issue_milestone_test.go +++ b/models/issue_milestone_test.go @@ -8,6 +8,7 @@ import ( "sort" "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" @@ -22,7 +23,7 @@ func TestMilestone_State(t *testing.T) { } func TestNewMilestone(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) milestone := &Milestone{ RepoID: 1, Name: "milestoneName", @@ -30,26 +31,26 @@ func TestNewMilestone(t *testing.T) { } assert.NoError(t, NewMilestone(milestone)) - AssertExistsAndLoadBean(t, milestone) + db.AssertExistsAndLoadBean(t, milestone) CheckConsistencyFor(t, &Repository{ID: milestone.RepoID}, &Milestone{}) } func TestGetMilestoneByRepoID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) milestone, err := GetMilestoneByRepoID(1, 1) assert.NoError(t, err) assert.EqualValues(t, 1, milestone.ID) assert.EqualValues(t, 1, milestone.RepoID) - _, err = GetMilestoneByRepoID(NonexistentID, NonexistentID) + _, err = GetMilestoneByRepoID(db.NonexistentID, db.NonexistentID) assert.True(t, IsErrMilestoneNotExist(err)) } func TestGetMilestonesByRepoID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(repoID int64, state api.StateType) { - repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) milestones, _, err := GetMilestones(GetMilestonesOption{ RepoID: repo.ID, State: state, @@ -88,7 +89,7 @@ func TestGetMilestonesByRepoID(t *testing.T) { test(3, api.StateAll) milestones, _, err := GetMilestones(GetMilestonesOption{ - RepoID: NonexistentID, + RepoID: db.NonexistentID, State: api.StateOpen, }) assert.NoError(t, err) @@ -96,12 +97,12 @@ func TestGetMilestonesByRepoID(t *testing.T) { } func TestGetMilestones(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + assert.NoError(t, db.PrepareTestDatabase()) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) test := func(sortType string, sortCond func(*Milestone) int) { for _, page := range []int{0, 1} { milestones, _, err := GetMilestones(GetMilestonesOption{ - ListOptions: ListOptions{ + ListOptions: db.ListOptions{ Page: page, PageSize: setting.UI.IssuePagingNum, }, @@ -118,7 +119,7 @@ func TestGetMilestones(t *testing.T) { assert.True(t, sort.IntsAreSorted(values)) milestones, _, err = GetMilestones(GetMilestonesOption{ - ListOptions: ListOptions{ + ListOptions: db.ListOptions{ Page: page, PageSize: setting.UI.IssuePagingNum, }, @@ -157,22 +158,22 @@ func TestGetMilestones(t *testing.T) { } func TestUpdateMilestone(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - milestone := AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone) + milestone := db.AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone) milestone.Name = " newMilestoneName " milestone.Content = "newMilestoneContent" assert.NoError(t, UpdateMilestone(milestone, milestone.IsClosed)) - milestone = AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone) + milestone = db.AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone) assert.EqualValues(t, "newMilestoneName", milestone.Name) CheckConsistencyFor(t, &Milestone{}) } func TestCountRepoMilestones(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(repoID int64) { - repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) - count, err := countRepoMilestones(x, repoID) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) + count, err := countRepoMilestones(db.GetEngine(db.DefaultContext), repoID) assert.NoError(t, err) assert.EqualValues(t, repo.NumMilestones, count) } @@ -180,15 +181,15 @@ func TestCountRepoMilestones(t *testing.T) { test(2) test(3) - count, err := countRepoMilestones(x, NonexistentID) + count, err := countRepoMilestones(db.GetEngine(db.DefaultContext), db.NonexistentID) assert.NoError(t, err) assert.EqualValues(t, 0, count) } func TestCountRepoClosedMilestones(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(repoID int64) { - repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) count, err := CountRepoClosedMilestones(repoID) assert.NoError(t, err) assert.EqualValues(t, repo.NumClosedMilestones, count) @@ -197,55 +198,55 @@ func TestCountRepoClosedMilestones(t *testing.T) { test(2) test(3) - count, err := CountRepoClosedMilestones(NonexistentID) + count, err := CountRepoClosedMilestones(db.NonexistentID) assert.NoError(t, err) assert.EqualValues(t, 0, count) } func TestChangeMilestoneStatus(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - milestone := AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone) + assert.NoError(t, db.PrepareTestDatabase()) + milestone := db.AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone) assert.NoError(t, ChangeMilestoneStatus(milestone, true)) - AssertExistsAndLoadBean(t, &Milestone{ID: 1}, "is_closed=1") + db.AssertExistsAndLoadBean(t, &Milestone{ID: 1}, "is_closed=1") CheckConsistencyFor(t, &Repository{ID: milestone.RepoID}, &Milestone{}) assert.NoError(t, ChangeMilestoneStatus(milestone, false)) - AssertExistsAndLoadBean(t, &Milestone{ID: 1}, "is_closed=0") + db.AssertExistsAndLoadBean(t, &Milestone{ID: 1}, "is_closed=0") CheckConsistencyFor(t, &Repository{ID: milestone.RepoID}, &Milestone{}) } func TestUpdateMilestoneCounters(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - issue := AssertExistsAndLoadBean(t, &Issue{MilestoneID: 1}, + assert.NoError(t, db.PrepareTestDatabase()) + issue := db.AssertExistsAndLoadBean(t, &Issue{MilestoneID: 1}, "is_closed=0").(*Issue) issue.IsClosed = true issue.ClosedUnix = timeutil.TimeStampNow() - _, err := x.ID(issue.ID).Cols("is_closed", "closed_unix").Update(issue) + _, err := db.GetEngine(db.DefaultContext).ID(issue.ID).Cols("is_closed", "closed_unix").Update(issue) assert.NoError(t, err) - assert.NoError(t, updateMilestoneCounters(x, issue.MilestoneID)) + assert.NoError(t, updateMilestoneCounters(db.GetEngine(db.DefaultContext), issue.MilestoneID)) CheckConsistencyFor(t, &Milestone{}) issue.IsClosed = false issue.ClosedUnix = 0 - _, err = x.ID(issue.ID).Cols("is_closed", "closed_unix").Update(issue) + _, err = db.GetEngine(db.DefaultContext).ID(issue.ID).Cols("is_closed", "closed_unix").Update(issue) assert.NoError(t, err) - assert.NoError(t, updateMilestoneCounters(x, issue.MilestoneID)) + assert.NoError(t, updateMilestoneCounters(db.GetEngine(db.DefaultContext), issue.MilestoneID)) CheckConsistencyFor(t, &Milestone{}) } func TestChangeMilestoneAssign(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - issue := AssertExistsAndLoadBean(t, &Issue{RepoID: 1}).(*Issue) - doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + issue := db.AssertExistsAndLoadBean(t, &Issue{RepoID: 1}).(*Issue) + doer := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) assert.NotNil(t, issue) assert.NotNil(t, doer) oldMilestoneID := issue.MilestoneID issue.MilestoneID = 2 assert.NoError(t, ChangeMilestoneAssign(issue, doer, oldMilestoneID)) - AssertExistsAndLoadBean(t, &Comment{ + db.AssertExistsAndLoadBean(t, &Comment{ IssueID: issue.ID, Type: CommentTypeMilestone, MilestoneID: issue.MilestoneID, @@ -255,18 +256,18 @@ func TestChangeMilestoneAssign(t *testing.T) { } func TestDeleteMilestoneByRepoID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) assert.NoError(t, DeleteMilestoneByRepoID(1, 1)) - AssertNotExistsBean(t, &Milestone{ID: 1}) + db.AssertNotExistsBean(t, &Milestone{ID: 1}) CheckConsistencyFor(t, &Repository{ID: 1}) - assert.NoError(t, DeleteMilestoneByRepoID(NonexistentID, NonexistentID)) + assert.NoError(t, DeleteMilestoneByRepoID(db.NonexistentID, db.NonexistentID)) } func TestMilestoneList_LoadTotalTrackedTimes(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) miles := MilestoneList{ - AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone), + db.AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone), } assert.NoError(t, miles.LoadTotalTrackedTimes()) @@ -275,9 +276,9 @@ func TestMilestoneList_LoadTotalTrackedTimes(t *testing.T) { } func TestCountMilestonesByRepoIDs(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) milestonesCount := func(repoID int64) (int, int) { - repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) return repo.NumOpenMilestones, repo.NumClosedMilestones } repo1OpenCount, repo1ClosedCount := milestonesCount(1) @@ -295,9 +296,9 @@ func TestCountMilestonesByRepoIDs(t *testing.T) { } func TestGetMilestonesByRepoIDs(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) + assert.NoError(t, db.PrepareTestDatabase()) + repo1 := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + repo2 := db.AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) test := func(sortType string, sortCond func(*Milestone) int) { for _, page := range []int{0, 1} { openMilestones, err := GetMilestonesByRepoIDs([]int64{repo1.ID, repo2.ID}, page, false, sortType) @@ -340,8 +341,8 @@ func TestGetMilestonesByRepoIDs(t *testing.T) { } func TestLoadTotalTrackedTime(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - milestone := AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone) + assert.NoError(t, db.PrepareTestDatabase()) + milestone := db.AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone) assert.NoError(t, milestone.LoadTotalTrackedTime()) @@ -349,10 +350,10 @@ func TestLoadTotalTrackedTime(t *testing.T) { } func TestGetMilestonesStats(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(repoID int64) { - repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) stats, err := GetMilestonesStatsByRepoCond(builder.And(builder.Eq{"repo_id": repoID})) assert.NoError(t, err) assert.EqualValues(t, repo.NumMilestones-repo.NumClosedMilestones, stats.OpenCount) @@ -362,13 +363,13 @@ func TestGetMilestonesStats(t *testing.T) { test(2) test(3) - stats, err := GetMilestonesStatsByRepoCond(builder.And(builder.Eq{"repo_id": NonexistentID})) + stats, err := GetMilestonesStatsByRepoCond(builder.And(builder.Eq{"repo_id": db.NonexistentID})) assert.NoError(t, err) assert.EqualValues(t, 0, stats.OpenCount) assert.EqualValues(t, 0, stats.ClosedCount) - repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) + repo1 := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + repo2 := db.AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) milestoneStats, err := GetMilestonesStatsByRepoCond(builder.In("repo_id", []int64{repo1.ID, repo2.ID})) assert.NoError(t, err) diff --git a/models/issue_reaction.go b/models/issue_reaction.go index 80fae1ca5f9a2..423eb8b96cf69 100644 --- a/models/issue_reaction.go +++ b/models/issue_reaction.go @@ -8,6 +8,7 @@ import ( "bytes" "fmt" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" @@ -28,9 +29,13 @@ type Reaction struct { CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` } +func init() { + db.RegisterModel(new(Reaction)) +} + // FindReactionsOptions describes the conditions to Find reactions type FindReactionsOptions struct { - ListOptions + db.ListOptions IssueID int64 CommentID int64 UserID int64 @@ -66,28 +71,28 @@ func (opts *FindReactionsOptions) toConds() builder.Cond { // FindCommentReactions returns a ReactionList of all reactions from an comment func FindCommentReactions(comment *Comment) (ReactionList, error) { - return findReactions(x, FindReactionsOptions{ + return findReactions(db.GetEngine(db.DefaultContext), FindReactionsOptions{ IssueID: comment.IssueID, CommentID: comment.ID, }) } // FindIssueReactions returns a ReactionList of all reactions from an issue -func FindIssueReactions(issue *Issue, listOptions ListOptions) (ReactionList, error) { - return findReactions(x, FindReactionsOptions{ +func FindIssueReactions(issue *Issue, listOptions db.ListOptions) (ReactionList, error) { + return findReactions(db.GetEngine(db.DefaultContext), FindReactionsOptions{ ListOptions: listOptions, IssueID: issue.ID, CommentID: -1, }) } -func findReactions(e Engine, opts FindReactionsOptions) ([]*Reaction, error) { +func findReactions(e db.Engine, opts FindReactionsOptions) ([]*Reaction, error) { e = e. Where(opts.toConds()). In("reaction.`type`", setting.UI.Reactions). Asc("reaction.issue_id", "reaction.comment_id", "reaction.created_unix", "reaction.id") if opts.Page != 0 { - e = opts.setEnginePagination(e) + e = db.SetEnginePagination(e, &opts) reactions := make([]*Reaction, 0, opts.PageSize) return reactions, e.Find(&reactions) @@ -143,7 +148,7 @@ func CreateReaction(opts *ReactionOptions) (*Reaction, error) { return nil, ErrForbiddenIssueReaction{opts.Type} } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return nil, err @@ -179,7 +184,7 @@ func CreateCommentReaction(doer *User, issue *Issue, comment *Comment, content s }) } -func deleteReaction(e Engine, opts *ReactionOptions) error { +func deleteReaction(e db.Engine, opts *ReactionOptions) error { reaction := &Reaction{ Type: opts.Type, } @@ -198,7 +203,7 @@ func deleteReaction(e Engine, opts *ReactionOptions) error { // DeleteReaction deletes reaction for issue or comment. func DeleteReaction(opts *ReactionOptions) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -235,7 +240,7 @@ func (r *Reaction) LoadUser() (*User, error) { if r.User != nil { return r.User, nil } - user, err := getUserByID(x, r.UserID) + user, err := getUserByID(db.GetEngine(db.DefaultContext), r.UserID) if err != nil { return nil, err } @@ -281,7 +286,7 @@ func (list ReactionList) getUserIDs() []int64 { return keysInt64(userIDs) } -func (list ReactionList) loadUsers(e Engine, repo *Repository) ([]*User, error) { +func (list ReactionList) loadUsers(e db.Engine, repo *Repository) ([]*User, error) { if len(list) == 0 { return nil, nil } @@ -309,7 +314,7 @@ func (list ReactionList) loadUsers(e Engine, repo *Repository) ([]*User, error) // LoadUsers loads reactions' all users func (list ReactionList) LoadUsers(repo *Repository) ([]*User, error) { - return list.loadUsers(x, repo) + return list.loadUsers(db.GetEngine(db.DefaultContext), repo) } // GetFirstUsers returns first reacted user display names separated by comma diff --git a/models/issue_reaction_test.go b/models/issue_reaction_test.go index e7aa45e4c3810..dd15b816c73f8 100644 --- a/models/issue_reaction_test.go +++ b/models/issue_reaction_test.go @@ -6,6 +6,7 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" @@ -24,23 +25,23 @@ func addReaction(t *testing.T, doer *User, issue *Issue, comment *Comment, conte } func TestIssueAddReaction(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user1 := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + user1 := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) - issue1 := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) + issue1 := db.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) addReaction(t, user1, issue1, nil, "heart") - AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID}) + db.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID}) } func TestIssueAddDuplicateReaction(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user1 := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + user1 := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) - issue1 := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) + issue1 := db.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) addReaction(t, user1, issue1, nil, "heart") @@ -52,37 +53,37 @@ func TestIssueAddDuplicateReaction(t *testing.T) { assert.Error(t, err) assert.Equal(t, ErrReactionAlreadyExist{Reaction: "heart"}, err) - existingR := AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID}).(*Reaction) + existingR := db.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID}).(*Reaction) assert.Equal(t, existingR.ID, reaction.ID) } func TestIssueDeleteReaction(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user1 := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + user1 := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) - issue1 := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) + issue1 := db.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) addReaction(t, user1, issue1, nil, "heart") err := DeleteIssueReaction(user1, issue1, "heart") assert.NoError(t, err) - AssertNotExistsBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID}) + db.AssertNotExistsBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID}) } func TestIssueReactionCount(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) setting.UI.ReactionMaxUserNum = 2 - user1 := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) - user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - user3 := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) - user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) + user1 := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + user2 := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + user3 := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + user4 := db.AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) ghost := NewGhostUser() - issue := AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue) + issue := db.AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue) addReaction(t, user1, issue, nil, "heart") addReaction(t, user2, issue, nil, "heart") @@ -92,7 +93,7 @@ func TestIssueReactionCount(t *testing.T) { addReaction(t, user4, issue, nil, "heart") addReaction(t, ghost, issue, nil, "-1") - err := issue.loadReactions(x) + err := issue.loadReactions(db.GetEngine(db.DefaultContext)) assert.NoError(t, err) assert.Len(t, issue.Reactions, 7) @@ -110,31 +111,31 @@ func TestIssueReactionCount(t *testing.T) { } func TestIssueCommentAddReaction(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user1 := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + user1 := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) - issue1 := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) + issue1 := db.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) - comment1 := AssertExistsAndLoadBean(t, &Comment{ID: 1}).(*Comment) + comment1 := db.AssertExistsAndLoadBean(t, &Comment{ID: 1}).(*Comment) addReaction(t, user1, issue1, comment1, "heart") - AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID, CommentID: comment1.ID}) + db.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID, CommentID: comment1.ID}) } func TestIssueCommentDeleteReaction(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user1 := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) - user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - user3 := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) - user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) + user1 := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + user2 := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + user3 := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + user4 := db.AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) - issue1 := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) - repo1 := AssertExistsAndLoadBean(t, &Repository{ID: issue1.RepoID}).(*Repository) + issue1 := db.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) + repo1 := db.AssertExistsAndLoadBean(t, &Repository{ID: issue1.RepoID}).(*Repository) - comment1 := AssertExistsAndLoadBean(t, &Comment{ID: 1}).(*Comment) + comment1 := db.AssertExistsAndLoadBean(t, &Comment{ID: 1}).(*Comment) addReaction(t, user1, issue1, comment1, "heart") addReaction(t, user2, issue1, comment1, "heart") @@ -151,16 +152,16 @@ func TestIssueCommentDeleteReaction(t *testing.T) { } func TestIssueCommentReactionCount(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user1 := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + user1 := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) - issue1 := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) + issue1 := db.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) - comment1 := AssertExistsAndLoadBean(t, &Comment{ID: 1}).(*Comment) + comment1 := db.AssertExistsAndLoadBean(t, &Comment{ID: 1}).(*Comment) addReaction(t, user1, issue1, comment1, "heart") assert.NoError(t, DeleteCommentReaction(user1, issue1, comment1, "heart")) - AssertNotExistsBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID, CommentID: comment1.ID}) + db.AssertNotExistsBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID, CommentID: comment1.ID}) } diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go index 9322e26bf239b..e8f19dd738c4e 100644 --- a/models/issue_stopwatch.go +++ b/models/issue_stopwatch.go @@ -8,6 +8,7 @@ import ( "fmt" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" "xorm.io/xorm" @@ -21,6 +22,10 @@ type Stopwatch struct { CreatedUnix timeutil.TimeStamp `xorm:"created"` } +func init() { + db.RegisterModel(new(Stopwatch)) +} + // Seconds returns the amount of time passed since creation, based on local server time func (s Stopwatch) Seconds() int64 { return int64(timeutil.TimeStampNow() - s.CreatedUnix) @@ -31,7 +36,7 @@ func (s Stopwatch) Duration() string { return SecToTime(s.Seconds()) } -func getStopwatch(e Engine, userID, issueID int64) (sw *Stopwatch, exists bool, err error) { +func getStopwatch(e db.Engine, userID, issueID int64) (sw *Stopwatch, exists bool, err error) { sw = new(Stopwatch) exists, err = e. Where("user_id = ?", userID). @@ -41,11 +46,11 @@ func getStopwatch(e Engine, userID, issueID int64) (sw *Stopwatch, exists bool, } // GetUserStopwatches return list of all stopwatches of a user -func GetUserStopwatches(userID int64, listOptions ListOptions) ([]*Stopwatch, error) { +func GetUserStopwatches(userID int64, listOptions db.ListOptions) ([]*Stopwatch, error) { sws := make([]*Stopwatch, 0, 8) - sess := x.Where("stopwatch.user_id = ?", userID) + sess := db.GetEngine(db.DefaultContext).Where("stopwatch.user_id = ?", userID) if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, &listOptions) } err := sess.Find(&sws) @@ -57,21 +62,21 @@ func GetUserStopwatches(userID int64, listOptions ListOptions) ([]*Stopwatch, er // CountUserStopwatches return count of all stopwatches of a user func CountUserStopwatches(userID int64) (int64, error) { - return x.Where("user_id = ?", userID).Count(&Stopwatch{}) + return db.GetEngine(db.DefaultContext).Where("user_id = ?", userID).Count(&Stopwatch{}) } // StopwatchExists returns true if the stopwatch exists func StopwatchExists(userID, issueID int64) bool { - _, exists, _ := getStopwatch(x, userID, issueID) + _, exists, _ := getStopwatch(db.GetEngine(db.DefaultContext), userID, issueID) return exists } // HasUserStopwatch returns true if the user has a stopwatch func HasUserStopwatch(userID int64) (exists bool, sw *Stopwatch, err error) { - return hasUserStopwatch(x, userID) + return hasUserStopwatch(db.GetEngine(db.DefaultContext), userID) } -func hasUserStopwatch(e Engine, userID int64) (exists bool, sw *Stopwatch, err error) { +func hasUserStopwatch(e db.Engine, userID int64) (exists bool, sw *Stopwatch, err error) { sw = new(Stopwatch) exists, err = e. Where("user_id = ?", userID). @@ -81,7 +86,7 @@ func hasUserStopwatch(e Engine, userID int64) (exists bool, sw *Stopwatch, err e // CreateOrStopIssueStopwatch will create or remove a stopwatch and will log it into issue's timeline. func CreateOrStopIssueStopwatch(user *User, issue *Issue) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -170,7 +175,7 @@ func createOrStopIssueStopwatch(e *xorm.Session, user *User, issue *Issue) error // CancelStopwatch removes the given stopwatch and logs it into issue's timeline. func CancelStopwatch(user *User, issue *Issue) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err diff --git a/models/issue_stopwatch_test.go b/models/issue_stopwatch_test.go index b6af5e93b51a7..11306efcf53ff 100644 --- a/models/issue_stopwatch_test.go +++ b/models/issue_stopwatch_test.go @@ -7,13 +7,14 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" "github.com/stretchr/testify/assert" ) func TestCancelStopwatch(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) user1, err := GetUserByID(1) assert.NoError(t, err) @@ -25,22 +26,22 @@ func TestCancelStopwatch(t *testing.T) { err = CancelStopwatch(user1, issue1) assert.NoError(t, err) - AssertNotExistsBean(t, &Stopwatch{UserID: user1.ID, IssueID: issue1.ID}) + db.AssertNotExistsBean(t, &Stopwatch{UserID: user1.ID, IssueID: issue1.ID}) - _ = AssertExistsAndLoadBean(t, &Comment{Type: CommentTypeCancelTracking, PosterID: user1.ID, IssueID: issue1.ID}) + _ = db.AssertExistsAndLoadBean(t, &Comment{Type: CommentTypeCancelTracking, PosterID: user1.ID, IssueID: issue1.ID}) assert.Nil(t, CancelStopwatch(user1, issue2)) } func TestStopwatchExists(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) assert.True(t, StopwatchExists(1, 1)) assert.False(t, StopwatchExists(1, 2)) } func TestHasUserStopwatch(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) exists, sw, err := HasUserStopwatch(1) assert.NoError(t, err) @@ -53,7 +54,7 @@ func TestHasUserStopwatch(t *testing.T) { } func TestCreateOrStopIssueStopwatch(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) user2, err := GetUserByID(2) assert.NoError(t, err) @@ -66,10 +67,10 @@ func TestCreateOrStopIssueStopwatch(t *testing.T) { assert.NoError(t, err) assert.NoError(t, CreateOrStopIssueStopwatch(user3, issue1)) - sw := AssertExistsAndLoadBean(t, &Stopwatch{UserID: 3, IssueID: 1}).(*Stopwatch) + sw := db.AssertExistsAndLoadBean(t, &Stopwatch{UserID: 3, IssueID: 1}).(*Stopwatch) assert.LessOrEqual(t, sw.CreatedUnix, timeutil.TimeStampNow()) assert.NoError(t, CreateOrStopIssueStopwatch(user2, issue2)) - AssertNotExistsBean(t, &Stopwatch{UserID: 2, IssueID: 2}) - AssertExistsAndLoadBean(t, &TrackedTime{UserID: 2, IssueID: 2}) + db.AssertNotExistsBean(t, &Stopwatch{UserID: 2, IssueID: 2}) + db.AssertExistsAndLoadBean(t, &TrackedTime{UserID: 2, IssueID: 2}) } diff --git a/models/issue_test.go b/models/issue_test.go index f2c9b7a68f8e5..d726a24344766 100644 --- a/models/issue_test.go +++ b/models/issue_test.go @@ -5,29 +5,32 @@ package models import ( + "fmt" "sort" + "sync" "testing" "time" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestIssue_ReplaceLabels(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testSuccess := func(issueID int64, labelIDs []int64) { - issue := AssertExistsAndLoadBean(t, &Issue{ID: issueID}).(*Issue) - repo := AssertExistsAndLoadBean(t, &Repository{ID: issue.RepoID}).(*Repository) - doer := AssertExistsAndLoadBean(t, &User{ID: repo.OwnerID}).(*User) + issue := db.AssertExistsAndLoadBean(t, &Issue{ID: issueID}).(*Issue) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: issue.RepoID}).(*Repository) + doer := db.AssertExistsAndLoadBean(t, &User{ID: repo.OwnerID}).(*User) labels := make([]*Label, len(labelIDs)) for i, labelID := range labelIDs { - labels[i] = AssertExistsAndLoadBean(t, &Label{ID: labelID, RepoID: repo.ID}).(*Label) + labels[i] = db.AssertExistsAndLoadBean(t, &Label{ID: labelID, RepoID: repo.ID}).(*Label) } assert.NoError(t, issue.ReplaceLabels(labels, doer)) - AssertCount(t, &IssueLabel{IssueID: issueID}, len(labelIDs)) + db.AssertCount(t, &IssueLabel{IssueID: issueID}, len(labelIDs)) for _, labelID := range labelIDs { - AssertExistsAndLoadBean(t, &IssueLabel{IssueID: issueID, LabelID: labelID}) + db.AssertExistsAndLoadBean(t, &IssueLabel{IssueID: issueID, LabelID: labelID}) } } @@ -37,7 +40,7 @@ func TestIssue_ReplaceLabels(t *testing.T) { } func Test_GetIssueIDsByRepoID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) ids, err := GetIssueIDsByRepoID(1) assert.NoError(t, err) @@ -45,8 +48,8 @@ func Test_GetIssueIDsByRepoID(t *testing.T) { } func TestIssueAPIURL(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) + assert.NoError(t, db.PrepareTestDatabase()) + issue := db.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) err := issue.LoadAttributes() assert.NoError(t, err) @@ -54,7 +57,7 @@ func TestIssueAPIURL(t *testing.T) { } func TestGetIssuesByIDs(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testSuccess := func(expectedIssueIDs, nonExistentIssueIDs []int64) { issues, err := GetIssuesByIDs(append(expectedIssueIDs, nonExistentIssueIDs...)) assert.NoError(t, err) @@ -65,16 +68,16 @@ func TestGetIssuesByIDs(t *testing.T) { assert.Equal(t, expectedIssueIDs, actualIssueIDs) } testSuccess([]int64{1, 2, 3}, []int64{}) - testSuccess([]int64{1, 2, 3}, []int64{NonexistentID}) + testSuccess([]int64{1, 2, 3}, []int64{db.NonexistentID}) } func TestGetParticipantIDsByIssue(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) checkParticipants := func(issueID int64, userIDs []int) { issue, err := GetIssueByID(issueID) assert.NoError(t, err) - participants, err := issue.getParticipantIDsByIssue(x) + participants, err := issue.getParticipantIDsByIssue(db.GetEngine(db.DefaultContext)) if assert.NoError(t, err) { participantsIDs := make([]int, len(participants)) for i, uid := range participants { @@ -103,17 +106,17 @@ func TestIssue_ClearLabels(t *testing.T) { {3, 2}, // pull-request, has no labels } for _, test := range tests { - assert.NoError(t, PrepareTestDatabase()) - issue := AssertExistsAndLoadBean(t, &Issue{ID: test.issueID}).(*Issue) - doer := AssertExistsAndLoadBean(t, &User{ID: test.doerID}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + issue := db.AssertExistsAndLoadBean(t, &Issue{ID: test.issueID}).(*Issue) + doer := db.AssertExistsAndLoadBean(t, &User{ID: test.doerID}).(*User) assert.NoError(t, issue.ClearLabels(doer)) - AssertNotExistsBean(t, &IssueLabel{IssueID: test.issueID}) + db.AssertNotExistsBean(t, &IssueLabel{IssueID: test.issueID}) } } func TestUpdateIssueCols(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - issue := AssertExistsAndLoadBean(t, &Issue{}).(*Issue) + assert.NoError(t, db.PrepareTestDatabase()) + issue := db.AssertExistsAndLoadBean(t, &Issue{}).(*Issue) const newTitle = "New Title for unit test" issue.Title = newTitle @@ -122,17 +125,17 @@ func TestUpdateIssueCols(t *testing.T) { issue.Content = "This should have no effect" now := time.Now().Unix() - assert.NoError(t, updateIssueCols(x, issue, "name")) + assert.NoError(t, updateIssueCols(db.GetEngine(db.DefaultContext), issue, "name")) then := time.Now().Unix() - updatedIssue := AssertExistsAndLoadBean(t, &Issue{ID: issue.ID}).(*Issue) + updatedIssue := db.AssertExistsAndLoadBean(t, &Issue{ID: issue.ID}).(*Issue) assert.EqualValues(t, newTitle, updatedIssue.Title) assert.EqualValues(t, prevContent, updatedIssue.Content) - AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix)) + db.AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix)) } func TestIssues(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) for _, test := range []struct { Opts IssuesOptions ExpectedIssueIDs []int64 @@ -148,7 +151,7 @@ func TestIssues(t *testing.T) { IssuesOptions{ RepoIDs: []int64{1, 3}, SortType: "oldest", - ListOptions: ListOptions{ + ListOptions: db.ListOptions{ Page: 1, PageSize: 4, }, @@ -158,7 +161,7 @@ func TestIssues(t *testing.T) { { IssuesOptions{ LabelIDs: []int64{1}, - ListOptions: ListOptions{ + ListOptions: db.ListOptions{ Page: 1, PageSize: 4, }, @@ -168,7 +171,7 @@ func TestIssues(t *testing.T) { { IssuesOptions{ LabelIDs: []int64{1, 2}, - ListOptions: ListOptions{ + ListOptions: db.ListOptions{ Page: 1, PageSize: 4, }, @@ -187,7 +190,7 @@ func TestIssues(t *testing.T) { } func TestGetUserIssueStats(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) for _, test := range []struct { Opts UserIssueStatsOptions ExpectedIssueStats IssueStats @@ -284,15 +287,15 @@ func TestGetUserIssueStats(t *testing.T) { } func TestIssue_loadTotalTimes(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) ms, err := GetIssueByID(2) assert.NoError(t, err) - assert.NoError(t, ms.loadTotalTimes(x)) + assert.NoError(t, ms.loadTotalTimes(db.GetEngine(db.DefaultContext))) assert.Equal(t, int64(3682), ms.TotalTrackedTime) } func TestIssue_SearchIssueIDsByKeyword(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) total, ids, err := SearchIssueIDsByKeyword("issue2", []int64{1}, 10, 0) assert.NoError(t, err) assert.EqualValues(t, 1, total) @@ -316,8 +319,8 @@ func TestIssue_SearchIssueIDsByKeyword(t *testing.T) { } func TestGetRepoIDsForIssuesOptions(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + user := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) for _, test := range []struct { Opts IssuesOptions ExpectedRepoIDs []int64 @@ -348,8 +351,8 @@ func TestGetRepoIDsForIssuesOptions(t *testing.T) { func testInsertIssue(t *testing.T, title, content string, expectIndex int64) *Issue { var newIssue Issue t.Run(title, func(t *testing.T) { - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + user := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) issue := Issue{ RepoID: repo.ID, @@ -360,7 +363,7 @@ func testInsertIssue(t *testing.T, title, content string, expectIndex int64) *Is err := NewIssue(repo, &issue, nil, nil) assert.NoError(t, err) - has, err := x.ID(issue.ID).Get(&newIssue) + has, err := db.GetEngine(db.DefaultContext).ID(issue.ID).Get(&newIssue) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, issue.Title, newIssue.Title) @@ -373,28 +376,28 @@ func testInsertIssue(t *testing.T, title, content string, expectIndex int64) *Is } func TestIssue_InsertIssue(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // there are 5 issues and max index is 5 on repository 1, so this one should 6 issue := testInsertIssue(t, "my issue1", "special issue's comments?", 6) - _, err := x.ID(issue.ID).Delete(new(Issue)) + _, err := db.GetEngine(db.DefaultContext).ID(issue.ID).Delete(new(Issue)) assert.NoError(t, err) issue = testInsertIssue(t, `my issue2, this is my son's love \n \r \ `, "special issue's '' comments?", 7) - _, err = x.ID(issue.ID).Delete(new(Issue)) + _, err = db.GetEngine(db.DefaultContext).ID(issue.ID).Delete(new(Issue)) assert.NoError(t, err) } func TestIssue_ResolveMentions(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testSuccess := func(owner, repo, doer string, mentions []string, expected []int64) { - o := AssertExistsAndLoadBean(t, &User{LowerName: owner}).(*User) - r := AssertExistsAndLoadBean(t, &Repository{OwnerID: o.ID, LowerName: repo}).(*Repository) + o := db.AssertExistsAndLoadBean(t, &User{LowerName: owner}).(*User) + r := db.AssertExistsAndLoadBean(t, &Repository{OwnerID: o.ID, LowerName: repo}).(*Repository) issue := &Issue{RepoID: r.ID} - d := AssertExistsAndLoadBean(t, &User{LowerName: doer}).(*User) - resolved, err := issue.ResolveMentionsByVisibility(DefaultDBContext(), d, mentions) + d := db.AssertExistsAndLoadBean(t, &User{LowerName: doer}).(*User) + resolved, err := issue.ResolveMentionsByVisibility(db.DefaultContext, d, mentions) assert.NoError(t, err) ids := make([]int64, len(resolved)) for i, user := range resolved { @@ -417,3 +420,17 @@ func TestIssue_ResolveMentions(t *testing.T) { // Private repo, whole team testSuccess("user17", "big_test_private_4", "user15", []string{"user17/owners"}, []int64{18}) } + +func TestResourceIndex(t *testing.T) { + assert.NoError(t, db.PrepareTestDatabase()) + + var wg sync.WaitGroup + for i := 0; i < 100; i++ { + wg.Add(1) + go func(i int) { + testInsertIssue(t, fmt.Sprintf("issue %d", i+1), "my issue", 0) + wg.Done() + }(i) + } + wg.Wait() +} diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index e7769b41dd415..79de8910194a5 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -7,6 +7,7 @@ package models import ( "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "xorm.io/builder" @@ -25,6 +26,10 @@ type TrackedTime struct { Deleted bool `xorm:"NOT NULL DEFAULT false"` } +func init() { + db.RegisterModel(new(TrackedTime)) +} + // TrackedTimeList is a List of TrackedTime's type TrackedTimeList []*TrackedTime @@ -35,10 +40,10 @@ func (t *TrackedTime) AfterLoad() { // LoadAttributes load Issue, User func (t *TrackedTime) LoadAttributes() (err error) { - return t.loadAttributes(x) + return t.loadAttributes(db.GetEngine(db.DefaultContext)) } -func (t *TrackedTime) loadAttributes(e Engine) (err error) { +func (t *TrackedTime) loadAttributes(e db.Engine) (err error) { if t.Issue == nil { t.Issue, err = getIssueByID(e, t.IssueID) if err != nil { @@ -70,7 +75,7 @@ func (tl TrackedTimeList) LoadAttributes() (err error) { // FindTrackedTimesOptions represent the filters for tracked times. If an ID is 0 it will be ignored. type FindTrackedTimesOptions struct { - ListOptions + db.ListOptions IssueID int64 UserID int64 RepositoryID int64 @@ -104,7 +109,7 @@ func (opts *FindTrackedTimesOptions) toCond() builder.Cond { } // toSession will convert the given options to a xorm Session by using the conditions from toCond and joining with issue table if required -func (opts *FindTrackedTimesOptions) toSession(e Engine) Engine { +func (opts *FindTrackedTimesOptions) toSession(e db.Engine) db.Engine { sess := e if opts.RepositoryID > 0 || opts.MilestoneID > 0 { sess = e.Join("INNER", "issue", "issue.id = tracked_time.issue_id") @@ -113,43 +118,43 @@ func (opts *FindTrackedTimesOptions) toSession(e Engine) Engine { sess = sess.Where(opts.toCond()) if opts.Page != 0 { - sess = opts.setEnginePagination(sess) + sess = db.SetEnginePagination(sess, opts) } return sess } -func getTrackedTimes(e Engine, options *FindTrackedTimesOptions) (trackedTimes TrackedTimeList, err error) { +func getTrackedTimes(e db.Engine, options *FindTrackedTimesOptions) (trackedTimes TrackedTimeList, err error) { err = options.toSession(e).Find(&trackedTimes) return } // GetTrackedTimes returns all tracked times that fit to the given options. func GetTrackedTimes(opts *FindTrackedTimesOptions) (TrackedTimeList, error) { - return getTrackedTimes(x, opts) + return getTrackedTimes(db.GetEngine(db.DefaultContext), opts) } // CountTrackedTimes returns count of tracked times that fit to the given options. func CountTrackedTimes(opts *FindTrackedTimesOptions) (int64, error) { - sess := x.Where(opts.toCond()) + sess := db.GetEngine(db.DefaultContext).Where(opts.toCond()) if opts.RepositoryID > 0 || opts.MilestoneID > 0 { sess = sess.Join("INNER", "issue", "issue.id = tracked_time.issue_id") } return sess.Count(&TrackedTime{}) } -func getTrackedSeconds(e Engine, opts FindTrackedTimesOptions) (trackedSeconds int64, err error) { +func getTrackedSeconds(e db.Engine, opts FindTrackedTimesOptions) (trackedSeconds int64, err error) { return opts.toSession(e).SumInt(&TrackedTime{}, "time") } // GetTrackedSeconds return sum of seconds func GetTrackedSeconds(opts FindTrackedTimesOptions) (int64, error) { - return getTrackedSeconds(x, opts) + return getTrackedSeconds(db.GetEngine(db.DefaultContext), opts) } // AddTime will add the given time (in seconds) to the issue func AddTime(user *User, issue *Issue, amount int64, created time.Time) (*TrackedTime, error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { @@ -179,7 +184,7 @@ func AddTime(user *User, issue *Issue, amount int64, created time.Time) (*Tracke return t, sess.Commit() } -func addTime(e Engine, user *User, issue *Issue, amount int64, created time.Time) (*TrackedTime, error) { +func addTime(e db.Engine, user *User, issue *Issue, amount int64, created time.Time) (*TrackedTime, error) { if created.IsZero() { created = time.Now() } @@ -225,7 +230,7 @@ func TotalTimes(options *FindTrackedTimesOptions) (map[*User]string, error) { // DeleteIssueUserTimes deletes times for issue func DeleteIssueUserTimes(issue *Issue, user *User) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { @@ -263,7 +268,7 @@ func DeleteIssueUserTimes(issue *Issue, user *User) error { // DeleteTime delete a specific Time func DeleteTime(t *TrackedTime) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { @@ -291,7 +296,7 @@ func DeleteTime(t *TrackedTime) error { return sess.Commit() } -func deleteTimes(e Engine, opts FindTrackedTimesOptions) (removedTime int64, err error) { +func deleteTimes(e db.Engine, opts FindTrackedTimesOptions) (removedTime int64, err error) { removedTime, err = getTrackedSeconds(e, opts) if err != nil || removedTime == 0 { return @@ -301,7 +306,7 @@ func deleteTimes(e Engine, opts FindTrackedTimesOptions) (removedTime int64, err return } -func deleteTime(e Engine, t *TrackedTime) error { +func deleteTime(e db.Engine, t *TrackedTime) error { if t.Deleted { return ErrNotExist{ID: t.ID} } @@ -313,7 +318,7 @@ func deleteTime(e Engine, t *TrackedTime) error { // GetTrackedTimeByID returns raw TrackedTime without loading attributes by id func GetTrackedTimeByID(id int64) (*TrackedTime, error) { time := new(TrackedTime) - has, err := x.ID(id).Get(time) + has, err := db.GetEngine(db.DefaultContext).ID(id).Get(time) if err != nil { return nil, err } else if !has { diff --git a/models/issue_tracked_time_test.go b/models/issue_tracked_time_test.go index 9de1dd699d6f4..7643360727fc2 100644 --- a/models/issue_tracked_time_test.go +++ b/models/issue_tracked_time_test.go @@ -8,11 +8,12 @@ import ( "testing" "time" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestAddTime(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) user3, err := GetUserByID(3) assert.NoError(t, err) @@ -27,15 +28,15 @@ func TestAddTime(t *testing.T) { assert.Equal(t, int64(1), trackedTime.IssueID) assert.Equal(t, int64(3661), trackedTime.Time) - tt := AssertExistsAndLoadBean(t, &TrackedTime{UserID: 3, IssueID: 1}).(*TrackedTime) + tt := db.AssertExistsAndLoadBean(t, &TrackedTime{UserID: 3, IssueID: 1}).(*TrackedTime) assert.Equal(t, int64(3661), tt.Time) - comment := AssertExistsAndLoadBean(t, &Comment{Type: CommentTypeAddTimeManual, PosterID: 3, IssueID: 1}).(*Comment) + comment := db.AssertExistsAndLoadBean(t, &Comment{Type: CommentTypeAddTimeManual, PosterID: 3, IssueID: 1}).(*Comment) assert.Equal(t, comment.Content, "1h 1min 1s") } func TestGetTrackedTimes(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // by Issue times, err := GetTrackedTimes(&FindTrackedTimesOptions{IssueID: 1}) @@ -76,7 +77,7 @@ func TestGetTrackedTimes(t *testing.T) { } func TestTotalTimes(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) total, err := TotalTimes(&FindTrackedTimesOptions{IssueID: 1}) assert.NoError(t, err) diff --git a/models/issue_user.go b/models/issue_user.go index 67a118fe57929..b112441e5b168 100644 --- a/models/issue_user.go +++ b/models/issue_user.go @@ -5,7 +5,10 @@ package models import ( + "context" "fmt" + + "code.gitea.io/gitea/models/db" ) // IssueUser represents an issue-user relation. @@ -17,7 +20,11 @@ type IssueUser struct { IsMentioned bool } -func newIssueUsers(e Engine, repo *Repository, issue *Issue) error { +func init() { + db.RegisterModel(new(IssueUser)) +} + +func newIssueUsers(e db.Engine, repo *Repository, issue *Issue) error { assignees, err := repo.getAssignees(e) if err != nil { return fmt.Errorf("getAssignees: %v", err) @@ -51,27 +58,27 @@ func newIssueUsers(e Engine, repo *Repository, issue *Issue) error { // UpdateIssueUserByRead updates issue-user relation for reading. func UpdateIssueUserByRead(uid, issueID int64) error { - _, err := x.Exec("UPDATE `issue_user` SET is_read=? WHERE uid=? AND issue_id=?", true, uid, issueID) + _, err := db.GetEngine(db.DefaultContext).Exec("UPDATE `issue_user` SET is_read=? WHERE uid=? AND issue_id=?", true, uid, issueID) return err } // UpdateIssueUsersByMentions updates issue-user pairs by mentioning. -func UpdateIssueUsersByMentions(ctx DBContext, issueID int64, uids []int64) error { +func UpdateIssueUsersByMentions(ctx context.Context, issueID int64, uids []int64) error { for _, uid := range uids { iu := &IssueUser{ UID: uid, IssueID: issueID, } - has, err := ctx.e.Get(iu) + has, err := db.GetEngine(ctx).Get(iu) if err != nil { return err } iu.IsMentioned = true if has { - _, err = ctx.e.ID(iu.ID).Cols("is_mentioned").Update(iu) + _, err = db.GetEngine(ctx).ID(iu.ID).Cols("is_mentioned").Update(iu) } else { - _, err = ctx.e.Insert(iu) + _, err = db.GetEngine(ctx).Insert(iu) } if err != nil { return err diff --git a/models/issue_user_test.go b/models/issue_user_test.go index 01e0bdc6444f0..d4e504719fc66 100644 --- a/models/issue_user_test.go +++ b/models/issue_user_test.go @@ -7,13 +7,14 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func Test_newIssueUsers(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) newIssue := &Issue{ RepoID: repo.ID, PosterID: 4, @@ -23,35 +24,35 @@ func Test_newIssueUsers(t *testing.T) { } // artificially insert new issue - AssertSuccessfulInsert(t, newIssue) + db.AssertSuccessfulInsert(t, newIssue) - assert.NoError(t, newIssueUsers(x, repo, newIssue)) + assert.NoError(t, newIssueUsers(db.GetEngine(db.DefaultContext), repo, newIssue)) // issue_user table should now have entries for new issue - AssertExistsAndLoadBean(t, &IssueUser{IssueID: newIssue.ID, UID: newIssue.PosterID}) - AssertExistsAndLoadBean(t, &IssueUser{IssueID: newIssue.ID, UID: repo.OwnerID}) + db.AssertExistsAndLoadBean(t, &IssueUser{IssueID: newIssue.ID, UID: newIssue.PosterID}) + db.AssertExistsAndLoadBean(t, &IssueUser{IssueID: newIssue.ID, UID: repo.OwnerID}) } func TestUpdateIssueUserByRead(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) + assert.NoError(t, db.PrepareTestDatabase()) + issue := db.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) assert.NoError(t, UpdateIssueUserByRead(4, issue.ID)) - AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1") + db.AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1") assert.NoError(t, UpdateIssueUserByRead(4, issue.ID)) - AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1") + db.AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1") - assert.NoError(t, UpdateIssueUserByRead(NonexistentID, NonexistentID)) + assert.NoError(t, UpdateIssueUserByRead(db.NonexistentID, db.NonexistentID)) } func TestUpdateIssueUsersByMentions(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) + assert.NoError(t, db.PrepareTestDatabase()) + issue := db.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) uids := []int64{2, 5} - assert.NoError(t, UpdateIssueUsersByMentions(DefaultDBContext(), issue.ID, uids)) + assert.NoError(t, UpdateIssueUsersByMentions(db.DefaultContext, issue.ID, uids)) for _, uid := range uids { - AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: uid}, "is_mentioned=1") + db.AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: uid}, "is_mentioned=1") } } diff --git a/models/issue_watch.go b/models/issue_watch.go index a3cbbf2c1d965..5bac406ad01bd 100644 --- a/models/issue_watch.go +++ b/models/issue_watch.go @@ -5,6 +5,7 @@ package models import ( + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" ) @@ -18,12 +19,16 @@ type IssueWatch struct { UpdatedUnix timeutil.TimeStamp `xorm:"updated NOT NULL"` } +func init() { + db.RegisterModel(new(IssueWatch)) +} + // IssueWatchList contains IssueWatch type IssueWatchList []*IssueWatch // CreateOrUpdateIssueWatch set watching for a user and issue func CreateOrUpdateIssueWatch(userID, issueID int64, isWatching bool) error { - iw, exists, err := getIssueWatch(x, userID, issueID) + iw, exists, err := getIssueWatch(db.GetEngine(db.DefaultContext), userID, issueID) if err != nil { return err } @@ -35,13 +40,13 @@ func CreateOrUpdateIssueWatch(userID, issueID int64, isWatching bool) error { IsWatching: isWatching, } - if _, err := x.Insert(iw); err != nil { + if _, err := db.GetEngine(db.DefaultContext).Insert(iw); err != nil { return err } } else { iw.IsWatching = isWatching - if _, err := x.ID(iw.ID).Cols("is_watching", "updated_unix").Update(iw); err != nil { + if _, err := db.GetEngine(db.DefaultContext).ID(iw.ID).Cols("is_watching", "updated_unix").Update(iw); err != nil { return err } } @@ -51,11 +56,11 @@ func CreateOrUpdateIssueWatch(userID, issueID int64, isWatching bool) error { // GetIssueWatch returns all IssueWatch objects from db by user and issue // the current Web-UI need iw object for watchers AND explicit non-watchers func GetIssueWatch(userID, issueID int64) (iw *IssueWatch, exists bool, err error) { - return getIssueWatch(x, userID, issueID) + return getIssueWatch(db.GetEngine(db.DefaultContext), userID, issueID) } // Return watcher AND explicit non-watcher if entry in db exist -func getIssueWatch(e Engine, userID, issueID int64) (iw *IssueWatch, exists bool, err error) { +func getIssueWatch(e db.Engine, userID, issueID int64) (iw *IssueWatch, exists bool, err error) { iw = new(IssueWatch) exists, err = e. Where("user_id = ?", userID). @@ -67,14 +72,14 @@ func getIssueWatch(e Engine, userID, issueID int64) (iw *IssueWatch, exists bool // CheckIssueWatch check if an user is watching an issue // it takes participants and repo watch into account func CheckIssueWatch(user *User, issue *Issue) (bool, error) { - iw, exist, err := getIssueWatch(x, user.ID, issue.ID) + iw, exist, err := getIssueWatch(db.GetEngine(db.DefaultContext), user.ID, issue.ID) if err != nil { return false, err } if exist { return iw.IsWatching, nil } - w, err := getWatch(x, user.ID, issue.RepoID) + w, err := getWatch(db.GetEngine(db.DefaultContext), user.ID, issue.RepoID) if err != nil { return false, err } @@ -85,10 +90,10 @@ func CheckIssueWatch(user *User, issue *Issue) (bool, error) { // but avoids joining with `user` for performance reasons // User permissions must be verified elsewhere if required func GetIssueWatchersIDs(issueID int64, watching bool) ([]int64, error) { - return getIssueWatchersIDs(x, issueID, watching) + return getIssueWatchersIDs(db.GetEngine(db.DefaultContext), issueID, watching) } -func getIssueWatchersIDs(e Engine, issueID int64, watching bool) ([]int64, error) { +func getIssueWatchersIDs(e db.Engine, issueID int64, watching bool) ([]int64, error) { ids := make([]int64, 0, 64) return ids, e.Table("issue_watch"). Where("issue_id=?", issueID). @@ -98,11 +103,11 @@ func getIssueWatchersIDs(e Engine, issueID int64, watching bool) ([]int64, error } // GetIssueWatchers returns watchers/unwatchers of a given issue -func GetIssueWatchers(issueID int64, listOptions ListOptions) (IssueWatchList, error) { - return getIssueWatchers(x, issueID, listOptions) +func GetIssueWatchers(issueID int64, listOptions db.ListOptions) (IssueWatchList, error) { + return getIssueWatchers(db.GetEngine(db.DefaultContext), issueID, listOptions) } -func getIssueWatchers(e Engine, issueID int64, listOptions ListOptions) (IssueWatchList, error) { +func getIssueWatchers(e db.Engine, issueID int64, listOptions db.ListOptions) (IssueWatchList, error) { sess := e. Where("`issue_watch`.issue_id = ?", issueID). And("`issue_watch`.is_watching = ?", true). @@ -111,7 +116,7 @@ func getIssueWatchers(e Engine, issueID int64, listOptions ListOptions) (IssueWa Join("INNER", "`user`", "`user`.id = `issue_watch`.user_id") if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, &listOptions) watches := make([]*IssueWatch, 0, listOptions.PageSize) return watches, sess.Find(&watches) } @@ -119,7 +124,7 @@ func getIssueWatchers(e Engine, issueID int64, listOptions ListOptions) (IssueWa return watches, sess.Find(&watches) } -func removeIssueWatchersByRepoID(e Engine, userID, repoID int64) error { +func removeIssueWatchersByRepoID(e db.Engine, userID, repoID int64) error { _, err := e. Join("INNER", "issue", "`issue`.id = `issue_watch`.issue_id AND `issue`.repo_id = ?", repoID). Where("`issue_watch`.user_id = ?", userID). diff --git a/models/issue_watch_test.go b/models/issue_watch_test.go index 013ca67e1efc8..139ed41cb608a 100644 --- a/models/issue_watch_test.go +++ b/models/issue_watch_test.go @@ -7,23 +7,24 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestCreateOrUpdateIssueWatch(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) assert.NoError(t, CreateOrUpdateIssueWatch(3, 1, true)) - iw := AssertExistsAndLoadBean(t, &IssueWatch{UserID: 3, IssueID: 1}).(*IssueWatch) + iw := db.AssertExistsAndLoadBean(t, &IssueWatch{UserID: 3, IssueID: 1}).(*IssueWatch) assert.True(t, iw.IsWatching) assert.NoError(t, CreateOrUpdateIssueWatch(1, 1, false)) - iw = AssertExistsAndLoadBean(t, &IssueWatch{UserID: 1, IssueID: 1}).(*IssueWatch) + iw = db.AssertExistsAndLoadBean(t, &IssueWatch{UserID: 1, IssueID: 1}).(*IssueWatch) assert.False(t, iw.IsWatching) } func TestGetIssueWatch(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) _, exists, err := GetIssueWatch(9, 1) assert.True(t, exists) @@ -40,24 +41,24 @@ func TestGetIssueWatch(t *testing.T) { } func TestGetIssueWatchers(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - iws, err := GetIssueWatchers(1, ListOptions{}) + iws, err := GetIssueWatchers(1, db.ListOptions{}) assert.NoError(t, err) // Watcher is inactive, thus 0 assert.Len(t, iws, 0) - iws, err = GetIssueWatchers(2, ListOptions{}) + iws, err = GetIssueWatchers(2, db.ListOptions{}) assert.NoError(t, err) // Watcher is explicit not watching assert.Len(t, iws, 0) - iws, err = GetIssueWatchers(5, ListOptions{}) + iws, err = GetIssueWatchers(5, db.ListOptions{}) assert.NoError(t, err) // Issue has no Watchers assert.Len(t, iws, 0) - iws, err = GetIssueWatchers(7, ListOptions{}) + iws, err = GetIssueWatchers(7, db.ListOptions{}) assert.NoError(t, err) // Issue has one watcher assert.Len(t, iws, 1) diff --git a/models/issue_xref.go b/models/issue_xref.go index fc8dd70f775f6..4630f4d3a4838 100644 --- a/models/issue_xref.go +++ b/models/issue_xref.go @@ -7,10 +7,9 @@ package models import ( "fmt" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/references" - - "xorm.io/xorm" ) type crossReference struct { @@ -27,7 +26,7 @@ type crossReferencesContext struct { RemoveOld bool } -func findOldCrossReferences(e Engine, issueID, commentID int64) ([]*Comment, error) { +func findOldCrossReferences(e db.Engine, issueID, commentID int64) ([]*Comment, error) { active := make([]*Comment, 0, 10) return active, e.Where("`ref_action` IN (?, ?, ?)", references.XRefActionNone, references.XRefActionCloses, references.XRefActionReopens). And("`ref_issue_id` = ?", issueID). @@ -35,7 +34,7 @@ func findOldCrossReferences(e Engine, issueID, commentID int64) ([]*Comment, err Find(&active) } -func neuterCrossReferences(e Engine, issueID, commentID int64) error { +func neuterCrossReferences(e db.Engine, issueID, commentID int64) error { active, err := findOldCrossReferences(e, issueID, commentID) if err != nil { return err @@ -47,7 +46,7 @@ func neuterCrossReferences(e Engine, issueID, commentID int64) error { return neuterCrossReferencesIds(e, ids) } -func neuterCrossReferencesIds(e Engine, ids []int64) error { +func neuterCrossReferencesIds(e db.Engine, ids []int64) error { _, err := e.In("id", ids).Cols("`ref_action`").Update(&Comment{RefAction: references.XRefActionNeutered}) return err } @@ -60,7 +59,7 @@ func neuterCrossReferencesIds(e Engine, ids []int64) error { // \/ \/ \/ // -func (issue *Issue) addCrossReferences(e *xorm.Session, doer *User, removeOld bool) error { +func (issue *Issue) addCrossReferences(e db.Engine, doer *User, removeOld bool) error { var commentType CommentType if issue.IsPull { commentType = CommentTypePullRef @@ -76,7 +75,7 @@ func (issue *Issue) addCrossReferences(e *xorm.Session, doer *User, removeOld bo return issue.createCrossReferences(e, ctx, issue.Title, issue.Content) } -func (issue *Issue) createCrossReferences(e *xorm.Session, ctx *crossReferencesContext, plaincontent, mdcontent string) error { +func (issue *Issue) createCrossReferences(e db.Engine, ctx *crossReferencesContext, plaincontent, mdcontent string) error { xreflist, err := ctx.OrigIssue.getCrossReferences(e, ctx, plaincontent, mdcontent) if err != nil { return err @@ -134,7 +133,7 @@ func (issue *Issue) createCrossReferences(e *xorm.Session, ctx *crossReferencesC return nil } -func (issue *Issue) getCrossReferences(e *xorm.Session, ctx *crossReferencesContext, plaincontent, mdcontent string) ([]*crossReference, error) { +func (issue *Issue) getCrossReferences(e db.Engine, ctx *crossReferencesContext, plaincontent, mdcontent string) ([]*crossReference, error) { xreflist := make([]*crossReference, 0, 5) var ( refRepo *Repository @@ -192,7 +191,7 @@ func (issue *Issue) updateCrossReferenceList(list []*crossReference, xref *cross } // verifyReferencedIssue will check if the referenced issue exists, and whether the doer has permission to do what -func (issue *Issue) verifyReferencedIssue(e Engine, ctx *crossReferencesContext, repo *Repository, +func (issue *Issue) verifyReferencedIssue(e db.Engine, ctx *crossReferencesContext, repo *Repository, ref references.IssueReference) (*Issue, references.XRefAction, error) { refIssue := &Issue{RepoID: repo.ID, Index: ref.Index} refAction := ref.Action @@ -241,7 +240,7 @@ func (issue *Issue) verifyReferencedIssue(e Engine, ctx *crossReferencesContext, // \/ \/ \/ \/ \/ // -func (comment *Comment) addCrossReferences(e *xorm.Session, doer *User, removeOld bool) error { +func (comment *Comment) addCrossReferences(e db.Engine, doer *User, removeOld bool) error { if comment.Type != CommentTypeCode && comment.Type != CommentTypeComment { return nil } @@ -258,7 +257,7 @@ func (comment *Comment) addCrossReferences(e *xorm.Session, doer *User, removeOl return comment.Issue.createCrossReferences(e, ctx, "", comment.Content) } -func (comment *Comment) neuterCrossReferences(e Engine) error { +func (comment *Comment) neuterCrossReferences(e db.Engine) error { return neuterCrossReferences(e, comment.IssueID, comment.ID) } @@ -278,7 +277,7 @@ func (comment *Comment) LoadRefIssue() (err error) { } comment.RefIssue, err = GetIssueByID(comment.RefIssueID) if err == nil { - err = comment.RefIssue.loadRepo(x) + err = comment.RefIssue.loadRepo(db.GetEngine(db.DefaultContext)) } return } @@ -338,7 +337,7 @@ func (comment *Comment) RefIssueIdent() string { // ResolveCrossReferences will return the list of references to close/reopen by this PR func (pr *PullRequest) ResolveCrossReferences() ([]*Comment, error) { unfiltered := make([]*Comment, 0, 5) - if err := x. + if err := db.GetEngine(db.DefaultContext). 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 a2d1a4b11e1f1..bf498e4710730 100644 --- a/models/issue_xref_test.go +++ b/models/issue_xref_test.go @@ -8,13 +8,14 @@ import ( "fmt" "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/references" "github.com/stretchr/testify/assert" ) func TestXRef_AddCrossReferences(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // Issue #1 to test against itarget := testCreateIssue(t, 1, 2, "title1", "content1", false) @@ -22,7 +23,7 @@ func TestXRef_AddCrossReferences(t *testing.T) { // PR to close issue #1 content := fmt.Sprintf("content2, closes #%d", itarget.Index) pr := testCreateIssue(t, 1, 2, "title2", content, true) - ref := AssertExistsAndLoadBean(t, &Comment{IssueID: itarget.ID, RefIssueID: pr.ID, RefCommentID: 0}).(*Comment) + ref := db.AssertExistsAndLoadBean(t, &Comment{IssueID: itarget.ID, RefIssueID: pr.ID, RefCommentID: 0}).(*Comment) assert.Equal(t, CommentTypePullRef, ref.Type) assert.Equal(t, pr.RepoID, ref.RefRepoID) assert.True(t, ref.RefIsPull) @@ -31,7 +32,7 @@ func TestXRef_AddCrossReferences(t *testing.T) { // Comment on PR to reopen issue #1 content = fmt.Sprintf("content2, reopens #%d", itarget.Index) c := testCreateComment(t, 1, 2, pr.ID, content) - ref = AssertExistsAndLoadBean(t, &Comment{IssueID: itarget.ID, RefIssueID: pr.ID, RefCommentID: c.ID}).(*Comment) + ref = db.AssertExistsAndLoadBean(t, &Comment{IssueID: itarget.ID, RefIssueID: pr.ID, RefCommentID: c.ID}).(*Comment) assert.Equal(t, CommentTypeCommentRef, ref.Type) assert.Equal(t, pr.RepoID, ref.RefRepoID) assert.True(t, ref.RefIsPull) @@ -40,7 +41,7 @@ func TestXRef_AddCrossReferences(t *testing.T) { // Issue mentioning issue #1 content = fmt.Sprintf("content3, mentions #%d", itarget.Index) i := testCreateIssue(t, 1, 2, "title3", content, false) - ref = AssertExistsAndLoadBean(t, &Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0}).(*Comment) + ref = db.AssertExistsAndLoadBean(t, &Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0}).(*Comment) assert.Equal(t, CommentTypeIssueRef, ref.Type) assert.Equal(t, pr.RepoID, ref.RefRepoID) assert.False(t, ref.RefIsPull) @@ -52,7 +53,7 @@ func TestXRef_AddCrossReferences(t *testing.T) { // Cross-reference to issue #4 by admin content = fmt.Sprintf("content5, mentions user3/repo3#%d", itarget.Index) i = testCreateIssue(t, 2, 1, "title5", content, false) - ref = AssertExistsAndLoadBean(t, &Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0}).(*Comment) + ref = db.AssertExistsAndLoadBean(t, &Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0}).(*Comment) assert.Equal(t, CommentTypeIssueRef, ref.Type) assert.Equal(t, i.RepoID, ref.RefRepoID) assert.False(t, ref.RefIsPull) @@ -61,11 +62,11 @@ func TestXRef_AddCrossReferences(t *testing.T) { // Cross-reference to issue #4 with no permission content = fmt.Sprintf("content6, mentions user3/repo3#%d", itarget.Index) i = testCreateIssue(t, 4, 5, "title6", content, false) - AssertNotExistsBean(t, &Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0}) + db.AssertNotExistsBean(t, &Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0}) } func TestXRef_NeuterCrossReferences(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // Issue #1 to test against itarget := testCreateIssue(t, 1, 2, "title1", "content1", false) @@ -73,23 +74,23 @@ func TestXRef_NeuterCrossReferences(t *testing.T) { // Issue mentioning issue #1 title := fmt.Sprintf("title2, mentions #%d", itarget.Index) i := testCreateIssue(t, 1, 2, title, "content2", false) - ref := AssertExistsAndLoadBean(t, &Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0}).(*Comment) + ref := db.AssertExistsAndLoadBean(t, &Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0}).(*Comment) assert.Equal(t, CommentTypeIssueRef, ref.Type) assert.Equal(t, references.XRefActionNone, ref.RefAction) - d := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + d := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) i.Title = "title2, no mentions" assert.NoError(t, i.ChangeTitle(d, title)) - ref = AssertExistsAndLoadBean(t, &Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0}).(*Comment) + ref = db.AssertExistsAndLoadBean(t, &Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0}).(*Comment) assert.Equal(t, CommentTypeIssueRef, ref.Type) assert.Equal(t, references.XRefActionNeutered, ref.RefAction) } func TestXRef_ResolveCrossReferences(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - d := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + d := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) i1 := testCreateIssue(t, 1, 2, "title1", "content1", false) i2 := testCreateIssue(t, 1, 2, "title2", "content2", false) @@ -98,21 +99,21 @@ func TestXRef_ResolveCrossReferences(t *testing.T) { assert.NoError(t, err) pr := testCreatePR(t, 1, 2, "titlepr", fmt.Sprintf("closes #%d", i1.Index)) - rp := AssertExistsAndLoadBean(t, &Comment{IssueID: i1.ID, RefIssueID: pr.Issue.ID, RefCommentID: 0}).(*Comment) + rp := db.AssertExistsAndLoadBean(t, &Comment{IssueID: i1.ID, RefIssueID: pr.Issue.ID, RefCommentID: 0}).(*Comment) c1 := testCreateComment(t, 1, 2, pr.Issue.ID, fmt.Sprintf("closes #%d", i2.Index)) - r1 := AssertExistsAndLoadBean(t, &Comment{IssueID: i2.ID, RefIssueID: pr.Issue.ID, RefCommentID: c1.ID}).(*Comment) + r1 := db.AssertExistsAndLoadBean(t, &Comment{IssueID: i2.ID, RefIssueID: pr.Issue.ID, RefCommentID: c1.ID}).(*Comment) // Must be ignored c2 := testCreateComment(t, 1, 2, pr.Issue.ID, fmt.Sprintf("mentions #%d", i2.Index)) - AssertExistsAndLoadBean(t, &Comment{IssueID: i2.ID, RefIssueID: pr.Issue.ID, RefCommentID: c2.ID}) + db.AssertExistsAndLoadBean(t, &Comment{IssueID: i2.ID, RefIssueID: pr.Issue.ID, RefCommentID: c2.ID}) // Must be superseded by c4/r4 c3 := testCreateComment(t, 1, 2, pr.Issue.ID, fmt.Sprintf("reopens #%d", i3.Index)) - AssertExistsAndLoadBean(t, &Comment{IssueID: i3.ID, RefIssueID: pr.Issue.ID, RefCommentID: c3.ID}) + db.AssertExistsAndLoadBean(t, &Comment{IssueID: i3.ID, RefIssueID: pr.Issue.ID, RefCommentID: c3.ID}) c4 := testCreateComment(t, 1, 2, pr.Issue.ID, fmt.Sprintf("closes #%d", i3.Index)) - r4 := AssertExistsAndLoadBean(t, &Comment{IssueID: i3.ID, RefIssueID: pr.Issue.ID, RefCommentID: c4.ID}).(*Comment) + r4 := db.AssertExistsAndLoadBean(t, &Comment{IssueID: i3.ID, RefIssueID: pr.Issue.ID, RefCommentID: c4.ID}).(*Comment) refs, err := pr.ResolveCrossReferences() assert.NoError(t, err) @@ -123,10 +124,10 @@ func TestXRef_ResolveCrossReferences(t *testing.T) { } func testCreateIssue(t *testing.T, repo, doer int64, title, content string, ispull bool) *Issue { - r := AssertExistsAndLoadBean(t, &Repository{ID: repo}).(*Repository) - d := AssertExistsAndLoadBean(t, &User{ID: doer}).(*User) + r := db.AssertExistsAndLoadBean(t, &Repository{ID: repo}).(*Repository) + d := db.AssertExistsAndLoadBean(t, &User{ID: doer}).(*User) - idx, err := GetNextResourceIndex("issue_index", r.ID) + idx, err := db.GetNextResourceIndex("issue_index", r.ID) assert.NoError(t, err) i := &Issue{ RepoID: r.ID, @@ -138,7 +139,7 @@ func testCreateIssue(t *testing.T, repo, doer int64, title, content string, ispu Index: idx, } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() assert.NoError(t, sess.Begin()) @@ -155,8 +156,8 @@ func testCreateIssue(t *testing.T, repo, doer int64, title, content string, ispu } func testCreatePR(t *testing.T, repo, doer int64, title, content string) *PullRequest { - r := AssertExistsAndLoadBean(t, &Repository{ID: repo}).(*Repository) - d := AssertExistsAndLoadBean(t, &User{ID: doer}).(*User) + r := db.AssertExistsAndLoadBean(t, &Repository{ID: repo}).(*Repository) + d := db.AssertExistsAndLoadBean(t, &User{ID: doer}).(*User) i := &Issue{RepoID: r.ID, PosterID: d.ID, Poster: d, Title: title, Content: content, IsPull: true} pr := &PullRequest{HeadRepoID: repo, BaseRepoID: repo, HeadBranch: "head", BaseBranch: "base", Status: PullRequestStatusMergeable} assert.NoError(t, NewPullRequest(r, i, nil, nil, pr)) @@ -165,11 +166,11 @@ func testCreatePR(t *testing.T, repo, doer int64, title, content string) *PullRe } func testCreateComment(t *testing.T, repo, doer, issue int64, content string) *Comment { - d := AssertExistsAndLoadBean(t, &User{ID: doer}).(*User) - i := AssertExistsAndLoadBean(t, &Issue{ID: issue}).(*Issue) + d := db.AssertExistsAndLoadBean(t, &User{ID: doer}).(*User) + i := db.AssertExistsAndLoadBean(t, &Issue{ID: issue}).(*Issue) c := &Comment{Type: CommentTypeComment, PosterID: doer, Poster: d, IssueID: issue, Issue: i, Content: content} - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() assert.NoError(t, sess.Begin()) _, err := sess.Insert(c) diff --git a/models/lfs.go b/models/lfs.go index c8abb1cd49ce5..87f7a2871f8a3 100644 --- a/models/lfs.go +++ b/models/lfs.go @@ -7,6 +7,7 @@ package models import ( "errors" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/timeutil" @@ -22,6 +23,10 @@ type LFSMetaObject struct { CreatedUnix timeutil.TimeStamp `xorm:"created"` } +func init() { + db.RegisterModel(new(LFSMetaObject)) +} + // LFSTokenResponse defines the JSON structure in which the JWT token is stored. // This structure is fetched via SSH and passed by the Git LFS client to the server // endpoint for authorization. @@ -39,7 +44,7 @@ var ErrLFSObjectNotExist = errors.New("LFS Meta object does not exist") func NewLFSMetaObject(m *LFSMetaObject) (*LFSMetaObject, error) { var err error - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return nil, err @@ -71,7 +76,7 @@ func (repo *Repository) GetLFSMetaObjectByOid(oid string) (*LFSMetaObject, error } m := &LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}, RepositoryID: repo.ID} - has, err := x.Get(m) + has, err := db.GetEngine(db.DefaultContext).Get(m) if err != nil { return nil, err } else if !has { @@ -87,7 +92,7 @@ func (repo *Repository) RemoveLFSMetaObjectByOid(oid string) (int64, error) { return 0, ErrLFSObjectNotExist } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return -1, err @@ -108,7 +113,7 @@ func (repo *Repository) RemoveLFSMetaObjectByOid(oid string) (int64, error) { // GetLFSMetaObjects returns all LFSMetaObjects associated with a repository func (repo *Repository) GetLFSMetaObjects(page, pageSize int) ([]*LFSMetaObject, error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if page >= 0 && pageSize > 0 { @@ -124,23 +129,23 @@ func (repo *Repository) GetLFSMetaObjects(page, pageSize int) ([]*LFSMetaObject, // CountLFSMetaObjects returns a count of all LFSMetaObjects associated with a repository func (repo *Repository) CountLFSMetaObjects() (int64, error) { - return x.Count(&LFSMetaObject{RepositoryID: repo.ID}) + return db.GetEngine(db.DefaultContext).Count(&LFSMetaObject{RepositoryID: repo.ID}) } // LFSObjectAccessible checks if a provided Oid is accessible to the user func LFSObjectAccessible(user *User, oid string) (bool, error) { if user.IsAdmin { - count, err := x.Count(&LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}}) + count, err := db.GetEngine(db.DefaultContext).Count(&LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}}) return count > 0, err } cond := accessibleRepositoryCondition(user) - count, err := x.Where(cond).Join("INNER", "repository", "`lfs_meta_object`.repository_id = `repository`.id").Count(&LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}}) + count, err := db.GetEngine(db.DefaultContext).Where(cond).Join("INNER", "repository", "`lfs_meta_object`.repository_id = `repository`.id").Count(&LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}}) return count > 0, err } // LFSAutoAssociate auto associates accessible LFSMetaObjects func LFSAutoAssociate(metas []*LFSMetaObject, user *User, repoID int64) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -179,7 +184,7 @@ func IterateLFS(f func(mo *LFSMetaObject) error) error { const batchSize = 100 for { mos := make([]*LFSMetaObject, 0, batchSize) - if err := x.Limit(batchSize, start).Find(&mos); err != nil { + if err := db.GetEngine(db.DefaultContext).Limit(batchSize, start).Find(&mos); err != nil { return err } if len(mos) == 0 { diff --git a/models/lfs_lock.go b/models/lfs_lock.go index 6c3d7c87c9fa2..ca49ab8a6a049 100644 --- a/models/lfs_lock.go +++ b/models/lfs_lock.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "xorm.io/xorm" @@ -26,6 +27,10 @@ type LFSLock struct { Created time.Time `xorm:"created"` } +func init() { + db.RegisterModel(new(LFSLock)) +} + // BeforeInsert is invoked from XORM before inserting an object of this type. func (l *LFSLock) BeforeInsert() { l.OwnerID = l.Owner.ID @@ -67,7 +72,7 @@ func CreateLFSLock(lock *LFSLock) (*LFSLock, error) { return nil, err } - _, err = x.InsertOne(lock) + _, err = db.GetEngine(db.DefaultContext).InsertOne(lock) return lock, err } @@ -75,7 +80,7 @@ func CreateLFSLock(lock *LFSLock) (*LFSLock, error) { func GetLFSLock(repo *Repository, path string) (*LFSLock, error) { path = cleanPath(path) rel := &LFSLock{RepoID: repo.ID} - has, err := x.Where("lower(path) = ?", strings.ToLower(path)).Get(rel) + has, err := db.GetEngine(db.DefaultContext).Where("lower(path) = ?", strings.ToLower(path)).Get(rel) if err != nil { return nil, err } @@ -88,7 +93,7 @@ func GetLFSLock(repo *Repository, path string) (*LFSLock, error) { // GetLFSLockByID returns release by given id. func GetLFSLockByID(id int64) (*LFSLock, error) { lock := new(LFSLock) - has, err := x.ID(id).Get(lock) + has, err := db.GetEngine(db.DefaultContext).ID(id).Get(lock) if err != nil { return nil, err } else if !has { @@ -99,7 +104,7 @@ func GetLFSLockByID(id int64) (*LFSLock, error) { // GetLFSLockByRepoID returns a list of locks of repository. func GetLFSLockByRepoID(repoID int64, page, pageSize int) ([]*LFSLock, error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if page >= 0 && pageSize > 0 { @@ -115,7 +120,7 @@ func GetLFSLockByRepoID(repoID int64, page, pageSize int) ([]*LFSLock, error) { // CountLFSLockByRepoID returns a count of all LFSLocks associated with a repository. func CountLFSLockByRepoID(repoID int64) (int64, error) { - return x.Count(&LFSLock{RepoID: repoID}) + return db.GetEngine(db.DefaultContext).Count(&LFSLock{RepoID: repoID}) } // DeleteLFSLockByID deletes a lock by given ID. @@ -134,7 +139,7 @@ func DeleteLFSLockByID(id int64, u *User, force bool) (*LFSLock, error) { return nil, fmt.Errorf("user doesn't own lock and force flag is not set") } - _, err = x.ID(id).Delete(new(LFSLock)) + _, err = db.GetEngine(db.DefaultContext).ID(id).Delete(new(LFSLock)) return lock, err } diff --git a/models/list_options.go b/models/list_options.go deleted file mode 100644 index ff02933f9bac4..0000000000000 --- a/models/list_options.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 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 models - -import ( - "code.gitea.io/gitea/modules/setting" - - "xorm.io/xorm" -) - -// ListOptions options to paginate results -type ListOptions struct { - PageSize int - Page int // start from 1 -} - -func (opts *ListOptions) getPaginatedSession() *xorm.Session { - opts.setDefaultValues() - - return x.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) -} - -func (opts *ListOptions) setSessionPagination(sess *xorm.Session) *xorm.Session { - opts.setDefaultValues() - - if opts.PageSize <= 0 { - return sess - } - return sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) -} - -func (opts *ListOptions) setEnginePagination(e Engine) Engine { - opts.setDefaultValues() - - return e.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) -} - -// GetStartEnd returns the start and end of the ListOptions -func (opts *ListOptions) GetStartEnd() (start, end int) { - opts.setDefaultValues() - start = (opts.Page - 1) * opts.PageSize - end = start + opts.PageSize - return -} - -func (opts *ListOptions) setDefaultValues() { - if opts.PageSize <= 0 { - opts.PageSize = setting.API.DefaultPagingNum - } - if opts.PageSize > setting.API.MaxResponseItems { - opts.PageSize = setting.API.MaxResponseItems - } - if opts.Page <= 0 { - opts.Page = 1 - } -} diff --git a/models/login/main_test.go b/models/login/main_test.go new file mode 100644 index 0000000000000..141952a5941de --- /dev/null +++ b/models/login/main_test.go @@ -0,0 +1,22 @@ +// Copyright 2020 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 login + +import ( + "path/filepath" + "testing" + + "code.gitea.io/gitea/models/db" +) + +func TestMain(m *testing.M) { + db.MainTest(m, filepath.Join("..", ".."), + "login_source.yml", + "oauth2_application.yml", + "oauth2_authorization_code.yml", + "oauth2_grant.yml", + "u2f_registration.yml", + ) +} diff --git a/models/login/oauth2.go b/models/login/oauth2.go new file mode 100644 index 0000000000000..45ab59dd78ca8 --- /dev/null +++ b/models/login/oauth2.go @@ -0,0 +1,70 @@ +// Copyright 2017 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 login + +import ( + "fmt" + + "code.gitea.io/gitea/models/db" +) + +// ________ _____ __ .__ +// \_____ \ / _ \ __ ___/ |_| |__ +// / | \ / /_\ \| | \ __\ | \ +// / | \/ | \ | /| | | Y \ +// \_______ /\____|__ /____/ |__| |___| / +// \/ \/ \/ + +// ErrOAuthClientIDInvalid will be thrown if client id cannot be found +type ErrOAuthClientIDInvalid struct { + ClientID string +} + +// IsErrOauthClientIDInvalid checks if an error is a ErrReviewNotExist. +func IsErrOauthClientIDInvalid(err error) bool { + _, ok := err.(ErrOAuthClientIDInvalid) + return ok +} + +// Error returns the error message +func (err ErrOAuthClientIDInvalid) Error() string { + return fmt.Sprintf("Client ID invalid [Client ID: %s]", err.ClientID) +} + +// ErrOAuthApplicationNotFound will be thrown if id cannot be found +type ErrOAuthApplicationNotFound struct { + ID int64 +} + +// IsErrOAuthApplicationNotFound checks if an error is a ErrReviewNotExist. +func IsErrOAuthApplicationNotFound(err error) bool { + _, ok := err.(ErrOAuthApplicationNotFound) + return ok +} + +// Error returns the error message +func (err ErrOAuthApplicationNotFound) Error() string { + return fmt.Sprintf("OAuth application not found [ID: %d]", err.ID) +} + +// GetActiveOAuth2ProviderLoginSources returns all actived LoginOAuth2 sources +func GetActiveOAuth2ProviderLoginSources() ([]*Source, error) { + sources := make([]*Source, 0, 1) + if err := db.GetEngine(db.DefaultContext).Where("is_active = ? and type = ?", true, OAuth2).Find(&sources); err != nil { + return nil, err + } + return sources, nil +} + +// GetActiveOAuth2LoginSourceByName returns a OAuth2 LoginSource based on the given name +func GetActiveOAuth2LoginSourceByName(name string) (*Source, error) { + loginSource := new(Source) + has, err := db.GetEngine(db.DefaultContext).Where("name = ? and type = ? and is_active = ?", name, OAuth2, true).Get(loginSource) + if !has || err != nil { + return nil, err + } + + return loginSource, nil +} diff --git a/models/oauth2_application.go b/models/login/oauth2_application.go similarity index 79% rename from models/oauth2_application.go rename to models/login/oauth2_application.go index 9cf236f0cb427..060bfe5bc3b0f 100644 --- a/models/oauth2_application.go +++ b/models/login/oauth2_application.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package login import ( "crypto/sha256" @@ -11,6 +11,7 @@ import ( "net/url" "strings" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/secret" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -22,19 +23,20 @@ import ( // OAuth2Application represents an OAuth2 client (RFC 6749) type OAuth2Application struct { - ID int64 `xorm:"pk autoincr"` - UID int64 `xorm:"INDEX"` - User *User `xorm:"-"` - - Name string - + ID int64 `xorm:"pk autoincr"` + UID int64 `xorm:"INDEX"` + Name string ClientID string `xorm:"unique"` ClientSecret string + RedirectURIs []string `xorm:"redirect_uris JSON TEXT"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` +} - RedirectURIs []string `xorm:"redirect_uris JSON TEXT"` - - CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` - UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` +func init() { + db.RegisterModel(new(OAuth2Application)) + db.RegisterModel(new(OAuth2AuthorizationCode)) + db.RegisterModel(new(OAuth2Grant)) } // TableName sets the table name to `oauth2_application` @@ -50,14 +52,6 @@ func (app *OAuth2Application) PrimaryRedirectURI() string { return app.RedirectURIs[0] } -// LoadUser will load User by UID -func (app *OAuth2Application) LoadUser() (err error) { - if app.User == nil { - app.User, err = GetUserByID(app.UID) - } - return -} - // ContainsRedirectURI checks if redirectURI is allowed for app func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool { return util.IsStringInSlice(redirectURI, app.RedirectURIs, true) @@ -74,7 +68,7 @@ func (app *OAuth2Application) GenerateClientSecret() (string, error) { return "", err } app.ClientSecret = string(hashedSecret) - if _, err := x.ID(app.ID).Cols("client_secret").Update(app); err != nil { + if _, err := db.GetEngine(db.DefaultContext).ID(app.ID).Cols("client_secret").Update(app); err != nil { return "", err } return clientSecret, nil @@ -87,10 +81,10 @@ func (app *OAuth2Application) ValidateClientSecret(secret []byte) bool { // GetGrantByUserID returns a OAuth2Grant by its user and application ID func (app *OAuth2Application) GetGrantByUserID(userID int64) (*OAuth2Grant, error) { - return app.getGrantByUserID(x, userID) + return app.getGrantByUserID(db.GetEngine(db.DefaultContext), userID) } -func (app *OAuth2Application) getGrantByUserID(e Engine, userID int64) (grant *OAuth2Grant, err error) { +func (app *OAuth2Application) getGrantByUserID(e db.Engine, userID int64) (grant *OAuth2Grant, err error) { grant = new(OAuth2Grant) if has, err := e.Where("user_id = ? AND application_id = ?", userID, app.ID).Get(grant); err != nil { return nil, err @@ -102,10 +96,10 @@ func (app *OAuth2Application) getGrantByUserID(e Engine, userID int64) (grant *O // CreateGrant generates a grant for an user func (app *OAuth2Application) CreateGrant(userID int64, scope string) (*OAuth2Grant, error) { - return app.createGrant(x, userID, scope) + return app.createGrant(db.GetEngine(db.DefaultContext), userID, scope) } -func (app *OAuth2Application) createGrant(e Engine, userID int64, scope string) (*OAuth2Grant, error) { +func (app *OAuth2Application) createGrant(e db.Engine, userID int64, scope string) (*OAuth2Grant, error) { grant := &OAuth2Grant{ ApplicationID: app.ID, UserID: userID, @@ -120,10 +114,10 @@ func (app *OAuth2Application) createGrant(e Engine, userID int64, scope string) // GetOAuth2ApplicationByClientID returns the oauth2 application with the given client_id. Returns an error if not found. func GetOAuth2ApplicationByClientID(clientID string) (app *OAuth2Application, err error) { - return getOAuth2ApplicationByClientID(x, clientID) + return getOAuth2ApplicationByClientID(db.GetEngine(db.DefaultContext), clientID) } -func getOAuth2ApplicationByClientID(e Engine, clientID string) (app *OAuth2Application, err error) { +func getOAuth2ApplicationByClientID(e db.Engine, clientID string) (app *OAuth2Application, err error) { app = new(OAuth2Application) has, err := e.Where("client_id = ?", clientID).Get(app) if !has { @@ -134,10 +128,10 @@ func getOAuth2ApplicationByClientID(e Engine, clientID string) (app *OAuth2Appli // GetOAuth2ApplicationByID returns the oauth2 application with the given id. Returns an error if not found. func GetOAuth2ApplicationByID(id int64) (app *OAuth2Application, err error) { - return getOAuth2ApplicationByID(x, id) + return getOAuth2ApplicationByID(db.GetEngine(db.DefaultContext), id) } -func getOAuth2ApplicationByID(e Engine, id int64) (app *OAuth2Application, err error) { +func getOAuth2ApplicationByID(e db.Engine, id int64) (app *OAuth2Application, err error) { app = new(OAuth2Application) has, err := e.ID(id).Get(app) if err != nil { @@ -151,10 +145,10 @@ func getOAuth2ApplicationByID(e Engine, id int64) (app *OAuth2Application, err e // GetOAuth2ApplicationsByUserID returns all oauth2 applications owned by the user func GetOAuth2ApplicationsByUserID(userID int64) (apps []*OAuth2Application, err error) { - return getOAuth2ApplicationsByUserID(x, userID) + return getOAuth2ApplicationsByUserID(db.GetEngine(db.DefaultContext), userID) } -func getOAuth2ApplicationsByUserID(e Engine, userID int64) (apps []*OAuth2Application, err error) { +func getOAuth2ApplicationsByUserID(e db.Engine, userID int64) (apps []*OAuth2Application, err error) { apps = make([]*OAuth2Application, 0) err = e.Where("uid = ?", userID).Find(&apps) return @@ -169,10 +163,10 @@ type CreateOAuth2ApplicationOptions struct { // CreateOAuth2Application inserts a new oauth2 application func CreateOAuth2Application(opts CreateOAuth2ApplicationOptions) (*OAuth2Application, error) { - return createOAuth2Application(x, opts) + return createOAuth2Application(db.GetEngine(db.DefaultContext), opts) } -func createOAuth2Application(e Engine, opts CreateOAuth2ApplicationOptions) (*OAuth2Application, error) { +func createOAuth2Application(e db.Engine, opts CreateOAuth2ApplicationOptions) (*OAuth2Application, error) { clientID := uuid.New().String() app := &OAuth2Application{ UID: opts.UserID, @@ -196,7 +190,7 @@ type UpdateOAuth2ApplicationOptions struct { // UpdateOAuth2Application updates an oauth2 application func UpdateOAuth2Application(opts UpdateOAuth2ApplicationOptions) (*OAuth2Application, error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) if err := sess.Begin(); err != nil { return nil, err } @@ -221,7 +215,7 @@ func UpdateOAuth2Application(opts UpdateOAuth2ApplicationOptions) (*OAuth2Applic return app, sess.Commit() } -func updateOAuth2Application(e Engine, app *OAuth2Application) error { +func updateOAuth2Application(e db.Engine, app *OAuth2Application) error { if _, err := e.ID(app.ID).Update(app); err != nil { return err } @@ -257,7 +251,7 @@ func deleteOAuth2Application(sess *xorm.Session, id, userid int64) error { // DeleteOAuth2Application deletes the application with the given id and the grants and auth codes related to it. It checks if the userid was the creator of the app. func DeleteOAuth2Application(id, userid int64) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -269,13 +263,13 @@ func DeleteOAuth2Application(id, userid int64) error { } // ListOAuth2Applications returns a list of oauth2 applications belongs to given user. -func ListOAuth2Applications(uid int64, listOptions ListOptions) ([]*OAuth2Application, int64, error) { - sess := x. +func ListOAuth2Applications(uid int64, listOptions db.ListOptions) ([]*OAuth2Application, int64, error) { + sess := db.GetEngine(db.DefaultContext). Where("uid=?", uid). Desc("id") if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, &listOptions) apps := make([]*OAuth2Application, 0, listOptions.PageSize) total, err := sess.FindAndCount(&apps) @@ -322,10 +316,10 @@ func (code *OAuth2AuthorizationCode) GenerateRedirectURI(state string) (redirect // Invalidate deletes the auth code from the database to invalidate this code func (code *OAuth2AuthorizationCode) Invalidate() error { - return code.invalidate(x) + return code.invalidate(db.GetEngine(db.DefaultContext)) } -func (code *OAuth2AuthorizationCode) invalidate(e Engine) error { +func (code *OAuth2AuthorizationCode) invalidate(e db.Engine) error { _, err := e.Delete(code) return err } @@ -354,10 +348,10 @@ func (code *OAuth2AuthorizationCode) validateCodeChallenge(verifier string) bool // GetOAuth2AuthorizationByCode returns an authorization by its code func GetOAuth2AuthorizationByCode(code string) (*OAuth2AuthorizationCode, error) { - return getOAuth2AuthorizationByCode(x, code) + return getOAuth2AuthorizationByCode(db.GetEngine(db.DefaultContext), code) } -func getOAuth2AuthorizationByCode(e Engine, code string) (auth *OAuth2AuthorizationCode, err error) { +func getOAuth2AuthorizationByCode(e db.Engine, code string) (auth *OAuth2AuthorizationCode, err error) { auth = new(OAuth2AuthorizationCode) if has, err := e.Where("code = ?", code).Get(auth); err != nil { return nil, err @@ -395,10 +389,10 @@ func (grant *OAuth2Grant) TableName() string { // GenerateNewAuthorizationCode generates a new authorization code for a grant and saves it to the database func (grant *OAuth2Grant) GenerateNewAuthorizationCode(redirectURI, codeChallenge, codeChallengeMethod string) (*OAuth2AuthorizationCode, error) { - return grant.generateNewAuthorizationCode(x, redirectURI, codeChallenge, codeChallengeMethod) + return grant.generateNewAuthorizationCode(db.GetEngine(db.DefaultContext), redirectURI, codeChallenge, codeChallengeMethod) } -func (grant *OAuth2Grant) generateNewAuthorizationCode(e Engine, redirectURI, codeChallenge, codeChallengeMethod string) (code *OAuth2AuthorizationCode, err error) { +func (grant *OAuth2Grant) generateNewAuthorizationCode(e db.Engine, redirectURI, codeChallenge, codeChallengeMethod string) (code *OAuth2AuthorizationCode, err error) { var codeSecret string if codeSecret, err = secret.New(); err != nil { return &OAuth2AuthorizationCode{}, err @@ -419,10 +413,10 @@ func (grant *OAuth2Grant) generateNewAuthorizationCode(e Engine, redirectURI, co // IncreaseCounter increases the counter and updates the grant func (grant *OAuth2Grant) IncreaseCounter() error { - return grant.increaseCount(x) + return grant.increaseCount(db.GetEngine(db.DefaultContext)) } -func (grant *OAuth2Grant) increaseCount(e Engine) error { +func (grant *OAuth2Grant) increaseCount(e db.Engine) error { _, err := e.ID(grant.ID).Incr("counter").Update(new(OAuth2Grant)) if err != nil { return err @@ -447,10 +441,10 @@ func (grant *OAuth2Grant) ScopeContains(scope string) bool { // SetNonce updates the current nonce value of a grant func (grant *OAuth2Grant) SetNonce(nonce string) error { - return grant.setNonce(x, nonce) + return grant.setNonce(db.GetEngine(db.DefaultContext), nonce) } -func (grant *OAuth2Grant) setNonce(e Engine, nonce string) error { +func (grant *OAuth2Grant) setNonce(e db.Engine, nonce string) error { grant.Nonce = nonce _, err := e.ID(grant.ID).Cols("nonce").Update(grant) if err != nil { @@ -461,10 +455,10 @@ func (grant *OAuth2Grant) setNonce(e Engine, nonce string) error { // GetOAuth2GrantByID returns the grant with the given ID func GetOAuth2GrantByID(id int64) (*OAuth2Grant, error) { - return getOAuth2GrantByID(x, id) + return getOAuth2GrantByID(db.GetEngine(db.DefaultContext), id) } -func getOAuth2GrantByID(e Engine, id int64) (grant *OAuth2Grant, err error) { +func getOAuth2GrantByID(e db.Engine, id int64) (grant *OAuth2Grant, err error) { grant = new(OAuth2Grant) if has, err := e.ID(id).Get(grant); err != nil { return nil, err @@ -476,10 +470,10 @@ func getOAuth2GrantByID(e Engine, id int64) (grant *OAuth2Grant, err error) { // GetOAuth2GrantsByUserID lists all grants of a certain user func GetOAuth2GrantsByUserID(uid int64) ([]*OAuth2Grant, error) { - return getOAuth2GrantsByUserID(x, uid) + return getOAuth2GrantsByUserID(db.GetEngine(db.DefaultContext), uid) } -func getOAuth2GrantsByUserID(e Engine, uid int64) ([]*OAuth2Grant, error) { +func getOAuth2GrantsByUserID(e db.Engine, uid int64) ([]*OAuth2Grant, error) { type joinedOAuth2Grant struct { Grant *OAuth2Grant `xorm:"extends"` Application *OAuth2Application `xorm:"extends"` @@ -508,10 +502,10 @@ func getOAuth2GrantsByUserID(e Engine, uid int64) ([]*OAuth2Grant, error) { // RevokeOAuth2Grant deletes the grant with grantID and userID func RevokeOAuth2Grant(grantID, userID int64) error { - return revokeOAuth2Grant(x, grantID, userID) + return revokeOAuth2Grant(db.GetEngine(db.DefaultContext), grantID, userID) } -func revokeOAuth2Grant(e Engine, grantID, userID int64) error { +func revokeOAuth2Grant(e db.Engine, grantID, userID int64) error { _, err := e.Delete(&OAuth2Grant{ID: grantID, UserID: userID}) return err } diff --git a/models/oauth2_application_test.go b/models/login/oauth2_application_test.go similarity index 75% rename from models/oauth2_application_test.go rename to models/login/oauth2_application_test.go index 7a4bce85c005b..cb064cef1b4d0 100644 --- a/models/oauth2_application_test.go +++ b/models/login/oauth2_application_test.go @@ -2,28 +2,30 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package login import ( "testing" + "code.gitea.io/gitea/models/db" + "github.com/stretchr/testify/assert" ) //////////////////// Application func TestOAuth2Application_GenerateClientSecret(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - app := AssertExistsAndLoadBean(t, &OAuth2Application{ID: 1}).(*OAuth2Application) + assert.NoError(t, db.PrepareTestDatabase()) + app := db.AssertExistsAndLoadBean(t, &OAuth2Application{ID: 1}).(*OAuth2Application) secret, err := app.GenerateClientSecret() assert.NoError(t, err) assert.True(t, len(secret) > 0) - AssertExistsAndLoadBean(t, &OAuth2Application{ID: 1, ClientSecret: app.ClientSecret}) + db.AssertExistsAndLoadBean(t, &OAuth2Application{ID: 1, ClientSecret: app.ClientSecret}) } func BenchmarkOAuth2Application_GenerateClientSecret(b *testing.B) { - assert.NoError(b, PrepareTestDatabase()) - app := AssertExistsAndLoadBean(b, &OAuth2Application{ID: 1}).(*OAuth2Application) + assert.NoError(b, db.PrepareTestDatabase()) + app := db.AssertExistsAndLoadBean(b, &OAuth2Application{ID: 1}).(*OAuth2Application) for i := 0; i < b.N; i++ { _, _ = app.GenerateClientSecret() } @@ -40,8 +42,8 @@ func TestOAuth2Application_ContainsRedirectURI(t *testing.T) { } func TestOAuth2Application_ValidateClientSecret(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - app := AssertExistsAndLoadBean(t, &OAuth2Application{ID: 1}).(*OAuth2Application) + assert.NoError(t, db.PrepareTestDatabase()) + app := db.AssertExistsAndLoadBean(t, &OAuth2Application{ID: 1}).(*OAuth2Application) secret, err := app.GenerateClientSecret() assert.NoError(t, err) assert.True(t, app.ValidateClientSecret([]byte(secret))) @@ -49,7 +51,7 @@ func TestOAuth2Application_ValidateClientSecret(t *testing.T) { } func TestGetOAuth2ApplicationByClientID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) app, err := GetOAuth2ApplicationByClientID("da7da3ba-9a13-4167-856f-3899de0b0138") assert.NoError(t, err) assert.Equal(t, "da7da3ba-9a13-4167-856f-3899de0b0138", app.ClientID) @@ -60,19 +62,12 @@ func TestGetOAuth2ApplicationByClientID(t *testing.T) { } func TestCreateOAuth2Application(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) app, err := CreateOAuth2Application(CreateOAuth2ApplicationOptions{Name: "newapp", UserID: 1}) assert.NoError(t, err) assert.Equal(t, "newapp", app.Name) assert.Len(t, app.ClientID, 36) - AssertExistsAndLoadBean(t, &OAuth2Application{Name: "newapp"}) -} - -func TestOAuth2Application_LoadUser(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - app := AssertExistsAndLoadBean(t, &OAuth2Application{ID: 1}).(*OAuth2Application) - assert.NoError(t, app.LoadUser()) - assert.NotNil(t, app.User) + db.AssertExistsAndLoadBean(t, &OAuth2Application{Name: "newapp"}) } func TestOAuth2Application_TableName(t *testing.T) { @@ -80,8 +75,8 @@ func TestOAuth2Application_TableName(t *testing.T) { } func TestOAuth2Application_GetGrantByUserID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - app := AssertExistsAndLoadBean(t, &OAuth2Application{ID: 1}).(*OAuth2Application) + assert.NoError(t, db.PrepareTestDatabase()) + app := db.AssertExistsAndLoadBean(t, &OAuth2Application{ID: 1}).(*OAuth2Application) grant, err := app.GetGrantByUserID(1) assert.NoError(t, err) assert.Equal(t, int64(1), grant.UserID) @@ -92,8 +87,8 @@ func TestOAuth2Application_GetGrantByUserID(t *testing.T) { } func TestOAuth2Application_CreateGrant(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - app := AssertExistsAndLoadBean(t, &OAuth2Application{ID: 1}).(*OAuth2Application) + assert.NoError(t, db.PrepareTestDatabase()) + app := db.AssertExistsAndLoadBean(t, &OAuth2Application{ID: 1}).(*OAuth2Application) grant, err := app.CreateGrant(2, "") assert.NoError(t, err) assert.NotNil(t, grant) @@ -105,7 +100,7 @@ func TestOAuth2Application_CreateGrant(t *testing.T) { //////////////////// Grant func TestGetOAuth2GrantByID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) grant, err := GetOAuth2GrantByID(1) assert.NoError(t, err) assert.Equal(t, int64(1), grant.ID) @@ -116,16 +111,16 @@ func TestGetOAuth2GrantByID(t *testing.T) { } func TestOAuth2Grant_IncreaseCounter(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - grant := AssertExistsAndLoadBean(t, &OAuth2Grant{ID: 1, Counter: 1}).(*OAuth2Grant) + assert.NoError(t, db.PrepareTestDatabase()) + grant := db.AssertExistsAndLoadBean(t, &OAuth2Grant{ID: 1, Counter: 1}).(*OAuth2Grant) assert.NoError(t, grant.IncreaseCounter()) assert.Equal(t, int64(2), grant.Counter) - AssertExistsAndLoadBean(t, &OAuth2Grant{ID: 1, Counter: 2}) + db.AssertExistsAndLoadBean(t, &OAuth2Grant{ID: 1, Counter: 2}) } func TestOAuth2Grant_ScopeContains(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - grant := AssertExistsAndLoadBean(t, &OAuth2Grant{ID: 1, Scope: "openid profile"}).(*OAuth2Grant) + assert.NoError(t, db.PrepareTestDatabase()) + grant := db.AssertExistsAndLoadBean(t, &OAuth2Grant{ID: 1, Scope: "openid profile"}).(*OAuth2Grant) assert.True(t, grant.ScopeContains("openid")) assert.True(t, grant.ScopeContains("profile")) assert.False(t, grant.ScopeContains("profil")) @@ -133,8 +128,8 @@ func TestOAuth2Grant_ScopeContains(t *testing.T) { } func TestOAuth2Grant_GenerateNewAuthorizationCode(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - grant := AssertExistsAndLoadBean(t, &OAuth2Grant{ID: 1}).(*OAuth2Grant) + assert.NoError(t, db.PrepareTestDatabase()) + grant := db.AssertExistsAndLoadBean(t, &OAuth2Grant{ID: 1}).(*OAuth2Grant) code, err := grant.GenerateNewAuthorizationCode("https://example2.com/callback", "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg", "S256") assert.NoError(t, err) assert.NotNil(t, code) @@ -146,7 +141,7 @@ func TestOAuth2Grant_TableName(t *testing.T) { } func TestGetOAuth2GrantsByUserID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) result, err := GetOAuth2GrantsByUserID(1) assert.NoError(t, err) assert.Len(t, result, 1) @@ -159,15 +154,15 @@ func TestGetOAuth2GrantsByUserID(t *testing.T) { } func TestRevokeOAuth2Grant(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) assert.NoError(t, RevokeOAuth2Grant(1, 1)) - AssertNotExistsBean(t, &OAuth2Grant{ID: 1, UserID: 1}) + db.AssertNotExistsBean(t, &OAuth2Grant{ID: 1, UserID: 1}) } //////////////////// Authorization Code func TestGetOAuth2AuthorizationByCode(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) code, err := GetOAuth2AuthorizationByCode("authcode") assert.NoError(t, err) assert.NotNil(t, code) @@ -227,10 +222,10 @@ func TestOAuth2AuthorizationCode_GenerateRedirectURI(t *testing.T) { } func TestOAuth2AuthorizationCode_Invalidate(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - code := AssertExistsAndLoadBean(t, &OAuth2AuthorizationCode{Code: "authcode"}).(*OAuth2AuthorizationCode) + assert.NoError(t, db.PrepareTestDatabase()) + code := db.AssertExistsAndLoadBean(t, &OAuth2AuthorizationCode{Code: "authcode"}).(*OAuth2AuthorizationCode) assert.NoError(t, code.Invalidate()) - AssertNotExistsBean(t, &OAuth2AuthorizationCode{Code: "authcode"}) + db.AssertNotExistsBean(t, &OAuth2AuthorizationCode{Code: "authcode"}) } func TestOAuth2AuthorizationCode_TableName(t *testing.T) { diff --git a/models/login/source.go b/models/login/source.go new file mode 100644 index 0000000000000..1001d49b51b96 --- /dev/null +++ b/models/login/source.go @@ -0,0 +1,411 @@ +// Copyright 2014 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 login + +import ( + "fmt" + "reflect" + "strconv" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" + "xorm.io/xorm/convert" +) + +// Type represents an login type. +type Type int + +// Note: new type must append to the end of list to maintain compatibility. +const ( + NoType Type = iota + Plain // 1 + LDAP // 2 + SMTP // 3 + PAM // 4 + DLDAP // 5 + OAuth2 // 6 + SSPI // 7 +) + +// String returns the string name of the LoginType +func (typ Type) String() string { + return Names[typ] +} + +// Int returns the int value of the LoginType +func (typ Type) Int() int { + return int(typ) +} + +// Names contains the name of LoginType values. +var Names = map[Type]string{ + LDAP: "LDAP (via BindDN)", + DLDAP: "LDAP (simple auth)", // Via direct bind + SMTP: "SMTP", + PAM: "PAM", + OAuth2: "OAuth2", + SSPI: "SPNEGO with SSPI", +} + +// Config represents login config as far as the db is concerned +type Config interface { + convert.Conversion +} + +// SkipVerifiable configurations provide a IsSkipVerify to check if SkipVerify is set +type SkipVerifiable interface { + IsSkipVerify() bool +} + +// HasTLSer configurations provide a HasTLS to check if TLS can be enabled +type HasTLSer interface { + HasTLS() bool +} + +// UseTLSer configurations provide a HasTLS to check if TLS is enabled +type UseTLSer interface { + UseTLS() bool +} + +// SSHKeyProvider configurations provide ProvidesSSHKeys to check if they provide SSHKeys +type SSHKeyProvider interface { + ProvidesSSHKeys() bool +} + +// RegisterableSource configurations provide RegisterSource which needs to be run on creation +type RegisterableSource interface { + RegisterSource() error + UnregisterSource() error +} + +// SourceSettable configurations can have their loginSource set on them +type SourceSettable interface { + SetLoginSource(*Source) +} + +// RegisterTypeConfig register a config for a provided type +func RegisterTypeConfig(typ Type, exemplar Config) { + if reflect.TypeOf(exemplar).Kind() == reflect.Ptr { + // Pointer: + registeredConfigs[typ] = func() Config { + return reflect.New(reflect.ValueOf(exemplar).Elem().Type()).Interface().(Config) + } + return + } + + // Not a Pointer + registeredConfigs[typ] = func() Config { + return reflect.New(reflect.TypeOf(exemplar)).Elem().Interface().(Config) + } +} + +var registeredConfigs = map[Type]func() Config{} + +// Source represents an external way for authorizing users. +type Source struct { + ID int64 `xorm:"pk autoincr"` + Type Type + Name string `xorm:"UNIQUE"` + IsActive bool `xorm:"INDEX NOT NULL DEFAULT false"` + IsSyncEnabled bool `xorm:"INDEX NOT NULL DEFAULT false"` + Cfg convert.Conversion `xorm:"TEXT"` + + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` +} + +// TableName xorm will read the table name from this method +func (Source) TableName() string { + return "login_source" +} + +func init() { + db.RegisterModel(new(Source)) +} + +// Cell2Int64 converts a xorm.Cell type to int64, +// and handles possible irregular cases. +func Cell2Int64(val xorm.Cell) int64 { + switch (*val).(type) { + case []uint8: + log.Trace("Cell2Int64 ([]uint8): %v", *val) + + v, _ := strconv.ParseInt(string((*val).([]uint8)), 10, 64) + return v + } + return (*val).(int64) +} + +// BeforeSet is invoked from XORM before setting the value of a field of this object. +func (source *Source) BeforeSet(colName string, val xorm.Cell) { + if colName == "type" { + typ := Type(Cell2Int64(val)) + constructor, ok := registeredConfigs[typ] + if !ok { + return + } + source.Cfg = constructor() + if settable, ok := source.Cfg.(SourceSettable); ok { + settable.SetLoginSource(source) + } + } +} + +// TypeName return name of this login source type. +func (source *Source) TypeName() string { + return Names[source.Type] +} + +// IsLDAP returns true of this source is of the LDAP type. +func (source *Source) IsLDAP() bool { + return source.Type == LDAP +} + +// IsDLDAP returns true of this source is of the DLDAP type. +func (source *Source) IsDLDAP() bool { + return source.Type == DLDAP +} + +// IsSMTP returns true of this source is of the SMTP type. +func (source *Source) IsSMTP() bool { + return source.Type == SMTP +} + +// IsPAM returns true of this source is of the PAM type. +func (source *Source) IsPAM() bool { + return source.Type == PAM +} + +// IsOAuth2 returns true of this source is of the OAuth2 type. +func (source *Source) IsOAuth2() bool { + return source.Type == OAuth2 +} + +// IsSSPI returns true of this source is of the SSPI type. +func (source *Source) IsSSPI() bool { + return source.Type == SSPI +} + +// HasTLS returns true of this source supports TLS. +func (source *Source) HasTLS() bool { + hasTLSer, ok := source.Cfg.(HasTLSer) + return ok && hasTLSer.HasTLS() +} + +// UseTLS returns true of this source is configured to use TLS. +func (source *Source) UseTLS() bool { + useTLSer, ok := source.Cfg.(UseTLSer) + return ok && useTLSer.UseTLS() +} + +// SkipVerify returns true if this source is configured to skip SSL +// verification. +func (source *Source) SkipVerify() bool { + skipVerifiable, ok := source.Cfg.(SkipVerifiable) + return ok && skipVerifiable.IsSkipVerify() +} + +// CreateSource inserts a LoginSource in the DB if not already +// existing with the given name. +func CreateSource(source *Source) error { + has, err := db.GetEngine(db.DefaultContext).Where("name=?", source.Name).Exist(new(Source)) + if err != nil { + return err + } else if has { + return ErrSourceAlreadyExist{source.Name} + } + // Synchronization is only available with LDAP for now + if !source.IsLDAP() { + source.IsSyncEnabled = false + } + + _, err = db.GetEngine(db.DefaultContext).Insert(source) + if err != nil { + return err + } + + if !source.IsActive { + return nil + } + + if settable, ok := source.Cfg.(SourceSettable); ok { + settable.SetLoginSource(source) + } + + registerableSource, ok := source.Cfg.(RegisterableSource) + if !ok { + return nil + } + + err = registerableSource.RegisterSource() + if err != nil { + // remove the LoginSource in case of errors while registering configuration + if _, err := db.GetEngine(db.DefaultContext).Delete(source); err != nil { + log.Error("CreateSource: Error while wrapOpenIDConnectInitializeError: %v", err) + } + } + return err +} + +// Sources returns a slice of all login sources found in DB. +func Sources() ([]*Source, error) { + auths := make([]*Source, 0, 6) + return auths, db.GetEngine(db.DefaultContext).Find(&auths) +} + +// SourcesByType returns all sources of the specified type +func SourcesByType(loginType Type) ([]*Source, error) { + sources := make([]*Source, 0, 1) + if err := db.GetEngine(db.DefaultContext).Where("type = ?", loginType).Find(&sources); err != nil { + return nil, err + } + return sources, nil +} + +// AllActiveSources returns all active sources +func AllActiveSources() ([]*Source, error) { + sources := make([]*Source, 0, 5) + if err := db.GetEngine(db.DefaultContext).Where("is_active = ?", true).Find(&sources); err != nil { + return nil, err + } + return sources, nil +} + +// ActiveSources returns all active sources of the specified type +func ActiveSources(tp Type) ([]*Source, error) { + sources := make([]*Source, 0, 1) + if err := db.GetEngine(db.DefaultContext).Where("is_active = ? and type = ?", true, tp).Find(&sources); err != nil { + return nil, err + } + return sources, nil +} + +// IsSSPIEnabled returns true if there is at least one activated login +// source of type LoginSSPI +func IsSSPIEnabled() bool { + if !db.HasEngine { + return false + } + sources, err := ActiveSources(SSPI) + if err != nil { + log.Error("ActiveSources: %v", err) + return false + } + return len(sources) > 0 +} + +// GetSourceByID returns login source by given ID. +func GetSourceByID(id int64) (*Source, error) { + source := new(Source) + if id == 0 { + source.Cfg = registeredConfigs[NoType]() + // Set this source to active + // FIXME: allow disabling of db based password authentication in future + source.IsActive = true + return source, nil + } + + has, err := db.GetEngine(db.DefaultContext).ID(id).Get(source) + if err != nil { + return nil, err + } else if !has { + return nil, ErrSourceNotExist{id} + } + return source, nil +} + +// UpdateSource updates a Source record in DB. +func UpdateSource(source *Source) error { + var originalLoginSource *Source + if source.IsOAuth2() { + // keep track of the original values so we can restore in case of errors while registering OAuth2 providers + var err error + if originalLoginSource, err = GetSourceByID(source.ID); err != nil { + return err + } + } + + _, err := db.GetEngine(db.DefaultContext).ID(source.ID).AllCols().Update(source) + if err != nil { + return err + } + + if !source.IsActive { + return nil + } + + if settable, ok := source.Cfg.(SourceSettable); ok { + settable.SetLoginSource(source) + } + + registerableSource, ok := source.Cfg.(RegisterableSource) + if !ok { + return nil + } + + err = registerableSource.RegisterSource() + if err != nil { + // restore original values since we cannot update the provider it self + if _, err := db.GetEngine(db.DefaultContext).ID(source.ID).AllCols().Update(originalLoginSource); err != nil { + log.Error("UpdateSource: Error while wrapOpenIDConnectInitializeError: %v", err) + } + } + return err +} + +// CountSources returns number of login sources. +func CountSources() int64 { + count, _ := db.GetEngine(db.DefaultContext).Count(new(Source)) + return count +} + +// ErrSourceNotExist represents a "SourceNotExist" kind of error. +type ErrSourceNotExist struct { + ID int64 +} + +// IsErrSourceNotExist checks if an error is a ErrSourceNotExist. +func IsErrSourceNotExist(err error) bool { + _, ok := err.(ErrSourceNotExist) + return ok +} + +func (err ErrSourceNotExist) Error() string { + return fmt.Sprintf("login source does not exist [id: %d]", err.ID) +} + +// ErrSourceAlreadyExist represents a "SourceAlreadyExist" kind of error. +type ErrSourceAlreadyExist struct { + Name string +} + +// IsErrSourceAlreadyExist checks if an error is a ErrSourceAlreadyExist. +func IsErrSourceAlreadyExist(err error) bool { + _, ok := err.(ErrSourceAlreadyExist) + return ok +} + +func (err ErrSourceAlreadyExist) Error() string { + return fmt.Sprintf("login source already exists [name: %s]", err.Name) +} + +// ErrSourceInUse represents a "SourceInUse" kind of error. +type ErrSourceInUse struct { + ID int64 +} + +// IsErrSourceInUse checks if an error is a ErrSourceInUse. +func IsErrSourceInUse(err error) bool { + _, ok := err.(ErrSourceInUse) + return ok +} + +func (err ErrSourceInUse) Error() string { + return fmt.Sprintf("login source is still used by some users [id: %d]", err.ID) +} diff --git a/models/models_test.go b/models/login/source_test.go similarity index 56% rename from models/models_test.go rename to models/login/source_test.go index c75c299e4fdbb..d98609037cd5f 100644 --- a/models/models_test.go +++ b/models/login/source_test.go @@ -2,40 +2,19 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package login import ( - "encoding/json" - "io/ioutil" - "os" - "path/filepath" "strings" "testing" - "code.gitea.io/gitea/modules/setting" - "xorm.io/xorm/schemas" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/json" "github.com/stretchr/testify/assert" + "xorm.io/xorm/schemas" ) -func TestDumpDatabase(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - - dir, err := ioutil.TempDir(os.TempDir(), "dump") - assert.NoError(t, err) - - type Version struct { - ID int64 `xorm:"pk autoincr"` - Version int64 - } - assert.NoError(t, x.Sync2(new(Version))) - - for _, dbName := range setting.SupportedDatabases { - dbType := setting.GetDBTypeByName(dbName) - assert.NoError(t, DumpDatabase(filepath.Join(dir, dbType+".sql"), dbType)) - } -} - type TestSource struct { Provider string ClientID string @@ -55,15 +34,15 @@ func (source *TestSource) ToDB() ([]byte, error) { } func TestDumpLoginSource(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - loginSourceSchema, err := x.TableInfo(new(LoginSource)) + loginSourceSchema, err := db.TableInfo(new(Source)) assert.NoError(t, err) - RegisterLoginTypeConfig(LoginOAuth2, new(TestSource)) + RegisterTypeConfig(OAuth2, new(TestSource)) - CreateLoginSource(&LoginSource{ - Type: LoginOAuth2, + CreateSource(&Source{ + Type: OAuth2, Name: "TestSource", IsActive: false, Cfg: &TestSource{ @@ -74,7 +53,7 @@ func TestDumpLoginSource(t *testing.T) { sb := new(strings.Builder) - x.DumpTables([]*schemas.Table{loginSourceSchema}, sb) + db.DumpTables([]*schemas.Table{loginSourceSchema}, sb) assert.Contains(t, sb.String(), `"Provider":"ConvertibleSourceName"`) } diff --git a/models/twofactor.go b/models/login/twofactor.go similarity index 75% rename from models/twofactor.go rename to models/login/twofactor.go index c19c5d120fd96..1c4d2734fca02 100644 --- a/models/twofactor.go +++ b/models/login/twofactor.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package login import ( "crypto/md5" @@ -11,6 +11,7 @@ import ( "encoding/base64" "fmt" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/secret" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" @@ -20,6 +21,25 @@ import ( "golang.org/x/crypto/pbkdf2" ) +// +// Two-factor authentication +// + +// ErrTwoFactorNotEnrolled indicates that a user is not enrolled in two-factor authentication. +type ErrTwoFactorNotEnrolled struct { + UID int64 +} + +// IsErrTwoFactorNotEnrolled checks if an error is a ErrTwoFactorNotEnrolled. +func IsErrTwoFactorNotEnrolled(err error) bool { + _, ok := err.(ErrTwoFactorNotEnrolled) + return ok +} + +func (err ErrTwoFactorNotEnrolled) Error() string { + return fmt.Sprintf("user not enrolled in 2FA [uid: %d]", err.UID) +} + // TwoFactor represents a two-factor authentication token. type TwoFactor struct { ID int64 `xorm:"pk autoincr"` @@ -32,6 +52,10 @@ type TwoFactor struct { UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } +func init() { + db.RegisterModel(new(TwoFactor)) +} + // GenerateScratchToken recreates the scratch token the user is using. func (t *TwoFactor) GenerateScratchToken() (string, error) { token, err := util.RandomString(8) @@ -39,11 +63,12 @@ func (t *TwoFactor) GenerateScratchToken() (string, error) { return "", err } t.ScratchSalt, _ = util.RandomString(10) - t.ScratchHash = hashToken(token, t.ScratchSalt) + t.ScratchHash = HashToken(token, t.ScratchSalt) return token, nil } -func hashToken(token, salt string) string { +// HashToken return the hashable salt +func HashToken(token, salt string) string { tempHash := pbkdf2.Key([]byte(token), []byte(salt), 10000, 50, sha256.New) return fmt.Sprintf("%x", tempHash) } @@ -53,7 +78,7 @@ func (t *TwoFactor) VerifyScratchToken(token string) bool { if len(token) == 0 { return false } - tempHash := hashToken(token, t.ScratchSalt) + tempHash := HashToken(token, t.ScratchSalt) return subtle.ConstantTimeCompare([]byte(t.ScratchHash), []byte(tempHash)) == 1 } @@ -88,13 +113,13 @@ func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) { // NewTwoFactor creates a new two-factor authentication token. func NewTwoFactor(t *TwoFactor) error { - _, err := x.Insert(t) + _, err := db.GetEngine(db.DefaultContext).Insert(t) return err } // UpdateTwoFactor updates a two-factor authentication token. func UpdateTwoFactor(t *TwoFactor) error { - _, err := x.ID(t.ID).AllCols().Update(t) + _, err := db.GetEngine(db.DefaultContext).ID(t.ID).AllCols().Update(t) return err } @@ -102,7 +127,7 @@ func UpdateTwoFactor(t *TwoFactor) error { // the user, if any. func GetTwoFactorByUID(uid int64) (*TwoFactor, error) { twofa := &TwoFactor{} - has, err := x.Where("uid=?", uid).Get(twofa) + has, err := db.GetEngine(db.DefaultContext).Where("uid=?", uid).Get(twofa) if err != nil { return nil, err } else if !has { @@ -113,7 +138,7 @@ func GetTwoFactorByUID(uid int64) (*TwoFactor, error) { // DeleteTwoFactorByID deletes two-factor authentication token by given ID. func DeleteTwoFactorByID(id, userID int64) error { - cnt, err := x.ID(id).Delete(&TwoFactor{ + cnt, err := db.GetEngine(db.DefaultContext).ID(id).Delete(&TwoFactor{ UID: userID, }) if err != nil { diff --git a/models/u2f.go b/models/login/u2f.go similarity index 56% rename from models/u2f.go rename to models/login/u2f.go index 28341906face6..64b1fb322ac85 100644 --- a/models/u2f.go +++ b/models/login/u2f.go @@ -2,15 +2,40 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package login import ( + "fmt" + + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" "github.com/tstranex/u2f" ) +// ____ ________________________________ .__ __ __ .__ +// | | \_____ \_ _____/\______ \ ____ ____ |__| _______/ |_____________ _/ |_|__| ____ ____ +// | | // ____/| __) | _// __ \ / ___\| |/ ___/\ __\_ __ \__ \\ __\ |/ _ \ / \ +// | | // \| \ | | \ ___// /_/ > |\___ \ | | | | \// __ \| | | ( <_> ) | \ +// |______/ \_______ \___ / |____|_ /\___ >___ /|__/____ > |__| |__| (____ /__| |__|\____/|___| / +// \/ \/ \/ \/_____/ \/ \/ \/ + +// ErrU2FRegistrationNotExist represents a "ErrU2FRegistrationNotExist" kind of error. +type ErrU2FRegistrationNotExist struct { + ID int64 +} + +func (err ErrU2FRegistrationNotExist) Error() string { + return fmt.Sprintf("U2F registration does not exist [id: %d]", err.ID) +} + +// IsErrU2FRegistrationNotExist checks if an error is a ErrU2FRegistrationNotExist. +func IsErrU2FRegistrationNotExist(err error) bool { + _, ok := err.(ErrU2FRegistrationNotExist) + return ok +} + // U2FRegistration represents the registration data and counter of a security key type U2FRegistration struct { ID int64 `xorm:"pk autoincr"` @@ -22,6 +47,10 @@ type U2FRegistration struct { UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } +func init() { + db.RegisterModel(new(U2FRegistration)) +} + // TableName returns a better table name for U2FRegistration func (reg U2FRegistration) TableName() string { return "u2f_registration" @@ -33,14 +62,14 @@ func (reg *U2FRegistration) Parse() (*u2f.Registration, error) { return r, r.UnmarshalBinary(reg.Raw) } -func (reg *U2FRegistration) updateCounter(e Engine) error { +func (reg *U2FRegistration) updateCounter(e db.Engine) error { _, err := e.ID(reg.ID).Cols("counter").Update(reg) return err } // UpdateCounter will update the database value of counter func (reg *U2FRegistration) UpdateCounter() error { - return reg.updateCounter(x) + return reg.updateCounter(db.GetEngine(db.DefaultContext)) } // U2FRegistrationList is a list of *U2FRegistration @@ -61,17 +90,17 @@ func (list U2FRegistrationList) ToRegistrations() []u2f.Registration { return regs } -func getU2FRegistrationsByUID(e Engine, uid int64) (U2FRegistrationList, error) { +func getU2FRegistrationsByUID(e db.Engine, uid int64) (U2FRegistrationList, error) { regs := make(U2FRegistrationList, 0) return regs, e.Where("user_id = ?", uid).Find(®s) } // GetU2FRegistrationByID returns U2F registration by id func GetU2FRegistrationByID(id int64) (*U2FRegistration, error) { - return getU2FRegistrationByID(x, id) + return getU2FRegistrationByID(db.GetEngine(db.DefaultContext), id) } -func getU2FRegistrationByID(e Engine, id int64) (*U2FRegistration, error) { +func getU2FRegistrationByID(e db.Engine, id int64) (*U2FRegistration, error) { reg := new(U2FRegistration) if found, err := e.ID(id).Get(reg); err != nil { return nil, err @@ -83,16 +112,16 @@ func getU2FRegistrationByID(e Engine, id int64) (*U2FRegistration, error) { // GetU2FRegistrationsByUID returns all U2F registrations of the given user func GetU2FRegistrationsByUID(uid int64) (U2FRegistrationList, error) { - return getU2FRegistrationsByUID(x, uid) + return getU2FRegistrationsByUID(db.GetEngine(db.DefaultContext), uid) } -func createRegistration(e Engine, user *User, name string, reg *u2f.Registration) (*U2FRegistration, error) { +func createRegistration(e db.Engine, userID int64, name string, reg *u2f.Registration) (*U2FRegistration, error) { raw, err := reg.MarshalBinary() if err != nil { return nil, err } r := &U2FRegistration{ - UserID: user.ID, + UserID: userID, Name: name, Counter: 0, Raw: raw, @@ -105,16 +134,16 @@ func createRegistration(e Engine, user *User, name string, reg *u2f.Registration } // CreateRegistration will create a new U2FRegistration from the given Registration -func CreateRegistration(user *User, name string, reg *u2f.Registration) (*U2FRegistration, error) { - return createRegistration(x, user, name, reg) +func CreateRegistration(userID int64, name string, reg *u2f.Registration) (*U2FRegistration, error) { + return createRegistration(db.GetEngine(db.DefaultContext), userID, name, reg) } // DeleteRegistration will delete U2FRegistration func DeleteRegistration(reg *U2FRegistration) error { - return deleteRegistration(x, reg) + return deleteRegistration(db.GetEngine(db.DefaultContext), reg) } -func deleteRegistration(e Engine, reg *U2FRegistration) error { +func deleteRegistration(e db.Engine, reg *U2FRegistration) error { _, err := e.Delete(reg) return err } diff --git a/models/u2f_test.go b/models/login/u2f_test.go similarity index 58% rename from models/u2f_test.go rename to models/login/u2f_test.go index 7a38334cf8c43..b0305775caf56 100644 --- a/models/u2f_test.go +++ b/models/login/u2f_test.go @@ -2,17 +2,19 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package login import ( "testing" + "code.gitea.io/gitea/models/db" + "github.com/stretchr/testify/assert" "github.com/tstranex/u2f" ) func TestGetU2FRegistrationByID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) res, err := GetU2FRegistrationByID(1) assert.NoError(t, err) @@ -24,7 +26,7 @@ func TestGetU2FRegistrationByID(t *testing.T) { } func TestGetU2FRegistrationsByUID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) res, err := GetU2FRegistrationsByUID(1) assert.NoError(t, err) @@ -37,37 +39,36 @@ func TestU2FRegistration_TableName(t *testing.T) { } func TestU2FRegistration_UpdateCounter(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - reg := AssertExistsAndLoadBean(t, &U2FRegistration{ID: 1}).(*U2FRegistration) + assert.NoError(t, db.PrepareTestDatabase()) + reg := db.AssertExistsAndLoadBean(t, &U2FRegistration{ID: 1}).(*U2FRegistration) reg.Counter = 1 assert.NoError(t, reg.UpdateCounter()) - AssertExistsIf(t, true, &U2FRegistration{ID: 1, Counter: 1}) + db.AssertExistsIf(t, true, &U2FRegistration{ID: 1, Counter: 1}) } func TestU2FRegistration_UpdateLargeCounter(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - reg := AssertExistsAndLoadBean(t, &U2FRegistration{ID: 1}).(*U2FRegistration) + assert.NoError(t, db.PrepareTestDatabase()) + reg := db.AssertExistsAndLoadBean(t, &U2FRegistration{ID: 1}).(*U2FRegistration) reg.Counter = 0xffffffff assert.NoError(t, reg.UpdateCounter()) - AssertExistsIf(t, true, &U2FRegistration{ID: 1, Counter: 0xffffffff}) + db.AssertExistsIf(t, true, &U2FRegistration{ID: 1, Counter: 0xffffffff}) } func TestCreateRegistration(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - user := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) - res, err := CreateRegistration(user, "U2F Created Key", &u2f.Registration{Raw: []byte("Test")}) + res, err := CreateRegistration(1, "U2F Created Key", &u2f.Registration{Raw: []byte("Test")}) assert.NoError(t, err) assert.Equal(t, "U2F Created Key", res.Name) assert.Equal(t, []byte("Test"), res.Raw) - AssertExistsIf(t, true, &U2FRegistration{Name: "U2F Created Key", UserID: user.ID}) + db.AssertExistsIf(t, true, &U2FRegistration{Name: "U2F Created Key", UserID: 1}) } func TestDeleteRegistration(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - reg := AssertExistsAndLoadBean(t, &U2FRegistration{ID: 1}).(*U2FRegistration) + assert.NoError(t, db.PrepareTestDatabase()) + reg := db.AssertExistsAndLoadBean(t, &U2FRegistration{ID: 1}).(*U2FRegistration) assert.NoError(t, DeleteRegistration(reg)) - AssertNotExistsBean(t, &U2FRegistration{ID: 1}) + db.AssertNotExistsBean(t, &U2FRegistration{ID: 1}) } diff --git a/models/login_source.go b/models/login_source.go deleted file mode 100644 index 3a48074e9a7da..0000000000000 --- a/models/login_source.go +++ /dev/null @@ -1,381 +0,0 @@ -// Copyright 2014 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 models - -import ( - "reflect" - "strconv" - - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/timeutil" - - "xorm.io/xorm" - "xorm.io/xorm/convert" -) - -// LoginType represents an login type. -type LoginType int - -// Note: new type must append to the end of list to maintain compatibility. -const ( - LoginNoType LoginType = iota - LoginPlain // 1 - LoginLDAP // 2 - LoginSMTP // 3 - LoginPAM // 4 - LoginDLDAP // 5 - LoginOAuth2 // 6 - LoginSSPI // 7 -) - -// String returns the string name of the LoginType -func (typ LoginType) String() string { - return LoginNames[typ] -} - -// Int returns the int value of the LoginType -func (typ LoginType) Int() int { - return int(typ) -} - -// LoginNames contains the name of LoginType values. -var LoginNames = map[LoginType]string{ - LoginLDAP: "LDAP (via BindDN)", - LoginDLDAP: "LDAP (simple auth)", // Via direct bind - LoginSMTP: "SMTP", - LoginPAM: "PAM", - LoginOAuth2: "OAuth2", - LoginSSPI: "SPNEGO with SSPI", -} - -// LoginConfig represents login config as far as the db is concerned -type LoginConfig interface { - convert.Conversion -} - -// SkipVerifiable configurations provide a IsSkipVerify to check if SkipVerify is set -type SkipVerifiable interface { - IsSkipVerify() bool -} - -// HasTLSer configurations provide a HasTLS to check if TLS can be enabled -type HasTLSer interface { - HasTLS() bool -} - -// UseTLSer configurations provide a HasTLS to check if TLS is enabled -type UseTLSer interface { - UseTLS() bool -} - -// SSHKeyProvider configurations provide ProvidesSSHKeys to check if they provide SSHKeys -type SSHKeyProvider interface { - ProvidesSSHKeys() bool -} - -// RegisterableSource configurations provide RegisterSource which needs to be run on creation -type RegisterableSource interface { - RegisterSource() error - UnregisterSource() error -} - -// LoginSourceSettable configurations can have their loginSource set on them -type LoginSourceSettable interface { - SetLoginSource(*LoginSource) -} - -// RegisterLoginTypeConfig register a config for a provided type -func RegisterLoginTypeConfig(typ LoginType, exemplar LoginConfig) { - if reflect.TypeOf(exemplar).Kind() == reflect.Ptr { - // Pointer: - registeredLoginConfigs[typ] = func() LoginConfig { - return reflect.New(reflect.ValueOf(exemplar).Elem().Type()).Interface().(LoginConfig) - } - return - } - - // Not a Pointer - registeredLoginConfigs[typ] = func() LoginConfig { - return reflect.New(reflect.TypeOf(exemplar)).Elem().Interface().(LoginConfig) - } -} - -var registeredLoginConfigs = map[LoginType]func() LoginConfig{} - -// LoginSource represents an external way for authorizing users. -type LoginSource struct { - ID int64 `xorm:"pk autoincr"` - Type LoginType - Name string `xorm:"UNIQUE"` - IsActive bool `xorm:"INDEX NOT NULL DEFAULT false"` - IsSyncEnabled bool `xorm:"INDEX NOT NULL DEFAULT false"` - Cfg convert.Conversion `xorm:"TEXT"` - - CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` - UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` -} - -// Cell2Int64 converts a xorm.Cell type to int64, -// and handles possible irregular cases. -func Cell2Int64(val xorm.Cell) int64 { - switch (*val).(type) { - case []uint8: - log.Trace("Cell2Int64 ([]uint8): %v", *val) - - v, _ := strconv.ParseInt(string((*val).([]uint8)), 10, 64) - return v - } - return (*val).(int64) -} - -// BeforeSet is invoked from XORM before setting the value of a field of this object. -func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) { - if colName == "type" { - typ := LoginType(Cell2Int64(val)) - constructor, ok := registeredLoginConfigs[typ] - if !ok { - return - } - source.Cfg = constructor() - if settable, ok := source.Cfg.(LoginSourceSettable); ok { - settable.SetLoginSource(source) - } - } -} - -// TypeName return name of this login source type. -func (source *LoginSource) TypeName() string { - return LoginNames[source.Type] -} - -// IsLDAP returns true of this source is of the LDAP type. -func (source *LoginSource) IsLDAP() bool { - return source.Type == LoginLDAP -} - -// IsDLDAP returns true of this source is of the DLDAP type. -func (source *LoginSource) IsDLDAP() bool { - return source.Type == LoginDLDAP -} - -// IsSMTP returns true of this source is of the SMTP type. -func (source *LoginSource) IsSMTP() bool { - return source.Type == LoginSMTP -} - -// IsPAM returns true of this source is of the PAM type. -func (source *LoginSource) IsPAM() bool { - return source.Type == LoginPAM -} - -// IsOAuth2 returns true of this source is of the OAuth2 type. -func (source *LoginSource) IsOAuth2() bool { - return source.Type == LoginOAuth2 -} - -// IsSSPI returns true of this source is of the SSPI type. -func (source *LoginSource) IsSSPI() bool { - return source.Type == LoginSSPI -} - -// HasTLS returns true of this source supports TLS. -func (source *LoginSource) HasTLS() bool { - hasTLSer, ok := source.Cfg.(HasTLSer) - return ok && hasTLSer.HasTLS() -} - -// UseTLS returns true of this source is configured to use TLS. -func (source *LoginSource) UseTLS() bool { - useTLSer, ok := source.Cfg.(UseTLSer) - return ok && useTLSer.UseTLS() -} - -// SkipVerify returns true if this source is configured to skip SSL -// verification. -func (source *LoginSource) SkipVerify() bool { - skipVerifiable, ok := source.Cfg.(SkipVerifiable) - return ok && skipVerifiable.IsSkipVerify() -} - -// CreateLoginSource inserts a LoginSource in the DB if not already -// existing with the given name. -func CreateLoginSource(source *LoginSource) error { - has, err := x.Where("name=?", source.Name).Exist(new(LoginSource)) - if err != nil { - return err - } else if has { - return ErrLoginSourceAlreadyExist{source.Name} - } - // Synchronization is only available with LDAP for now - if !source.IsLDAP() { - source.IsSyncEnabled = false - } - - _, err = x.Insert(source) - if err != nil { - return err - } - - if !source.IsActive { - return nil - } - - if settable, ok := source.Cfg.(LoginSourceSettable); ok { - settable.SetLoginSource(source) - } - - registerableSource, ok := source.Cfg.(RegisterableSource) - if !ok { - return nil - } - - err = registerableSource.RegisterSource() - if err != nil { - // remove the LoginSource in case of errors while registering configuration - if _, err := x.Delete(source); err != nil { - log.Error("CreateLoginSource: Error while wrapOpenIDConnectInitializeError: %v", err) - } - } - return err -} - -// LoginSources returns a slice of all login sources found in DB. -func LoginSources() ([]*LoginSource, error) { - auths := make([]*LoginSource, 0, 6) - return auths, x.Find(&auths) -} - -// LoginSourcesByType returns all sources of the specified type -func LoginSourcesByType(loginType LoginType) ([]*LoginSource, error) { - sources := make([]*LoginSource, 0, 1) - if err := x.Where("type = ?", loginType).Find(&sources); err != nil { - return nil, err - } - return sources, nil -} - -// AllActiveLoginSources returns all active sources -func AllActiveLoginSources() ([]*LoginSource, error) { - sources := make([]*LoginSource, 0, 5) - if err := x.Where("is_active = ?", true).Find(&sources); err != nil { - return nil, err - } - return sources, nil -} - -// ActiveLoginSources returns all active sources of the specified type -func ActiveLoginSources(loginType LoginType) ([]*LoginSource, error) { - sources := make([]*LoginSource, 0, 1) - if err := x.Where("is_active = ? and type = ?", true, loginType).Find(&sources); err != nil { - return nil, err - } - return sources, nil -} - -// IsSSPIEnabled returns true if there is at least one activated login -// source of type LoginSSPI -func IsSSPIEnabled() bool { - if !HasEngine { - return false - } - sources, err := ActiveLoginSources(LoginSSPI) - if err != nil { - log.Error("ActiveLoginSources: %v", err) - return false - } - return len(sources) > 0 -} - -// GetLoginSourceByID returns login source by given ID. -func GetLoginSourceByID(id int64) (*LoginSource, error) { - source := new(LoginSource) - if id == 0 { - source.Cfg = registeredLoginConfigs[LoginNoType]() - // Set this source to active - // FIXME: allow disabling of db based password authentication in future - source.IsActive = true - return source, nil - } - - has, err := x.ID(id).Get(source) - if err != nil { - return nil, err - } else if !has { - return nil, ErrLoginSourceNotExist{id} - } - return source, nil -} - -// UpdateSource updates a LoginSource record in DB. -func UpdateSource(source *LoginSource) error { - var originalLoginSource *LoginSource - if source.IsOAuth2() { - // keep track of the original values so we can restore in case of errors while registering OAuth2 providers - var err error - if originalLoginSource, err = GetLoginSourceByID(source.ID); err != nil { - return err - } - } - - _, err := x.ID(source.ID).AllCols().Update(source) - if err != nil { - return err - } - - if !source.IsActive { - return nil - } - - if settable, ok := source.Cfg.(LoginSourceSettable); ok { - settable.SetLoginSource(source) - } - - registerableSource, ok := source.Cfg.(RegisterableSource) - if !ok { - return nil - } - - err = registerableSource.RegisterSource() - if err != nil { - // restore original values since we cannot update the provider it self - if _, err := x.ID(source.ID).AllCols().Update(originalLoginSource); err != nil { - log.Error("UpdateSource: Error while wrapOpenIDConnectInitializeError: %v", err) - } - } - return err -} - -// DeleteSource deletes a LoginSource record in DB. -func DeleteSource(source *LoginSource) error { - count, err := x.Count(&User{LoginSource: source.ID}) - if err != nil { - return err - } else if count > 0 { - return ErrLoginSourceInUse{source.ID} - } - - count, err = x.Count(&ExternalLoginUser{LoginSourceID: source.ID}) - if err != nil { - return err - } else if count > 0 { - return ErrLoginSourceInUse{source.ID} - } - - if registerableSource, ok := source.Cfg.(RegisterableSource); ok { - if err := registerableSource.UnregisterSource(); err != nil { - return err - } - } - - _, err = x.ID(source.ID).Delete(new(LoginSource)) - return err -} - -// CountLoginSources returns number of login sources. -func CountLoginSources() int64 { - count, _ := x.Count(new(LoginSource)) - return count -} diff --git a/models/main_test.go b/models/main_test.go index 78fc78b4d0a60..ad9276330f1a6 100644 --- a/models/main_test.go +++ b/models/main_test.go @@ -7,15 +7,17 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" + "github.com/stretchr/testify/assert" ) // TestFixturesAreConsistent assert that test fixtures are consistent func TestFixturesAreConsistent(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) CheckConsistencyForAll(t) } func TestMain(m *testing.M) { - MainTest(m, "..") + db.MainTest(m, "..") } diff --git a/models/migrate.go b/models/migrate.go index 28b6707473733..18b1b11e468a5 100644 --- a/models/migrate.go +++ b/models/migrate.go @@ -5,6 +5,7 @@ package models import ( + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/structs" "xorm.io/builder" @@ -17,7 +18,7 @@ func InsertMilestones(ms ...*Milestone) (err error) { return nil } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -38,7 +39,7 @@ func InsertMilestones(ms ...*Milestone) (err error) { // InsertIssues insert issues to database func InsertIssues(issues ...*Issue) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -143,7 +144,7 @@ func InsertIssueComments(comments []*Comment) error { issueIDs[comment.IssueID] = true } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -174,7 +175,7 @@ func InsertIssueComments(comments []*Comment) error { // InsertPullRequests inserted pull requests func InsertPullRequests(prs ...*PullRequest) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -194,7 +195,7 @@ func InsertPullRequests(prs ...*PullRequest) error { // InsertReleases migrates release func InsertReleases(rels ...*Release) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -232,7 +233,7 @@ func migratedIssueCond(tp structs.GitServiceType) builder.Cond { // UpdateReviewsMigrationsByType updates reviews' migrations information via given git service type and original id and poster id func UpdateReviewsMigrationsByType(tp structs.GitServiceType, originalAuthorID string, posterID int64) error { - _, err := x.Table("review"). + _, err := db.GetEngine(db.DefaultContext).Table("review"). Where("original_author_id = ?", originalAuthorID). And(migratedIssueCond(tp)). Update(map[string]interface{}{ diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 7960edc80bf3e..33b094e48ccbb 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -340,6 +340,12 @@ var migrations = []Migration{ NewMigration("RecreateIssueResourceIndexTable to have a primary key instead of an unique index", recreateIssueResourceIndexTable), // v193 -> v194 NewMigration("Add repo id column for attachment table", addRepoIDForAttachment), + // v194 -> v195 + NewMigration("Add Branch Protection Unprotected Files Column", addBranchProtectionUnprotectedFilesColumn), + // v195 -> v196 + NewMigration("Add table commit_status_index", addTableCommitStatusIndex), + // v196 -> v197 + NewMigration("Add Color to ProjectBoard table", addColorColToProjectBoard), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/migrations_test.go b/models/migrations/migrations_test.go index 634bfc84865dc..78624f1e27156 100644 --- a/models/migrations/migrations_test.go +++ b/models/migrations/migrations_test.go @@ -14,12 +14,13 @@ import ( "testing" "time" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" + "github.com/stretchr/testify/assert" "github.com/unknwon/com" "xorm.io/xorm" @@ -85,7 +86,7 @@ func removeAllWithRetry(dir string) error { // SetEngine sets the xorm.Engine func SetEngine() (*xorm.Engine, error) { - x, err := models.GetNewEngine() + x, err := db.GetNewEngine() if err != nil { return x, fmt.Errorf("Failed to connect to database: %v", err) } @@ -93,7 +94,7 @@ func SetEngine() (*xorm.Engine, error) { x.SetMapper(names.GonicMapper{}) // WARNING: for serv command, MUST remove the output to os.stdout, // so use log file to instead print to stdout. - x.SetLogger(models.NewXORMLogger(setting.Database.LogSQL)) + x.SetLogger(db.NewXORMLogger(setting.Database.LogSQL)) x.ShowSQL(setting.Database.LogSQL) x.SetMaxOpenConns(setting.Database.MaxOpenConns) x.SetMaxIdleConns(setting.Database.MaxIdleConns) @@ -240,11 +241,14 @@ func prepareTestEnv(t *testing.T, skip int, syncModels ...interface{}) (*xorm.En if _, err := os.Stat(fixturesDir); err == nil { t.Logf("initializing fixtures from: %s", fixturesDir) - if err := models.InitFixtures(fixturesDir, x); err != nil { + if err := db.InitFixtures( + db.FixturesOptions{ + Dir: fixturesDir, + }, x); err != nil { t.Errorf("error whilst initializing fixtures from %s: %v", fixturesDir, err) return x, deferFn } - if err := models.LoadFixtures(x); err != nil { + if err := db.LoadFixtures(x); err != nil { t.Errorf("error whilst loading fixtures from %s: %v", fixturesDir, err) return x, deferFn } diff --git a/models/migrations/v115.go b/models/migrations/v115.go index 8222a8ceea9fd..7708ed5e28afd 100644 --- a/models/migrations/v115.go +++ b/models/migrations/v115.go @@ -7,7 +7,7 @@ package migrations import ( "crypto/md5" "fmt" - "io/ioutil" + "io" "math" "os" "path/filepath" @@ -141,9 +141,9 @@ func copyOldAvatarToNewLocation(userID int64, oldAvatar string) (string, error) } defer fr.Close() - data, err := ioutil.ReadAll(fr) + data, err := io.ReadAll(fr) if err != nil { - return "", fmt.Errorf("ioutil.ReadAll: %v", err) + return "", fmt.Errorf("io.ReadAll: %v", err) } newAvatar := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", userID, md5.Sum(data))))) @@ -151,8 +151,8 @@ func copyOldAvatarToNewLocation(userID int64, oldAvatar string) (string, error) return newAvatar, nil } - if err := ioutil.WriteFile(filepath.Join(setting.Avatar.Path, newAvatar), data, 0o666); err != nil { - return "", fmt.Errorf("ioutil.WriteFile: %v", err) + if err := os.WriteFile(filepath.Join(setting.Avatar.Path, newAvatar), data, 0o666); err != nil { + return "", fmt.Errorf("os.WriteFile: %v", err) } return newAvatar, nil diff --git a/models/migrations/v194.go b/models/migrations/v194.go new file mode 100644 index 0000000000000..7ea160e2b26bd --- /dev/null +++ b/models/migrations/v194.go @@ -0,0 +1,22 @@ +// Copyright 2021 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 ( + "fmt" + + "xorm.io/xorm" +) + +func addBranchProtectionUnprotectedFilesColumn(x *xorm.Engine) error { + type ProtectedBranch struct { + UnprotectedFilePatterns string `xorm:"TEXT"` + } + + if err := x.Sync2(new(ProtectedBranch)); err != nil { + return fmt.Errorf("Sync2: %v", err) + } + return nil +} diff --git a/models/migrations/v195.go b/models/migrations/v195.go new file mode 100644 index 0000000000000..06694eb57df9f --- /dev/null +++ b/models/migrations/v195.go @@ -0,0 +1,47 @@ +// Copyright 2021 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 ( + "fmt" + + "xorm.io/xorm" +) + +func addTableCommitStatusIndex(x *xorm.Engine) error { + // CommitStatusIndex represents a table for commit status index + type CommitStatusIndex struct { + ID int64 + RepoID int64 `xorm:"unique(repo_sha)"` + SHA string `xorm:"unique(repo_sha)"` + MaxIndex int64 `xorm:"index"` + } + + if err := x.Sync2(new(CommitStatusIndex)); err != nil { + return fmt.Errorf("Sync2: %v", err) + } + + sess := x.NewSession() + defer sess.Close() + + if err := sess.Begin(); err != nil { + return err + } + + // Remove data we're goint to rebuild + if _, err := sess.Table("commit_status_index").Where("1=1").Delete(&CommitStatusIndex{}); err != nil { + return err + } + + // Create current data for all repositories with issues and PRs + if _, err := sess.Exec("INSERT INTO commit_status_index (repo_id, sha, max_index) " + + "SELECT max_data.repo_id, max_data.sha, max_data.max_index " + + "FROM ( SELECT commit_status.repo_id AS repo_id, commit_status.sha AS sha, max(commit_status.`index`) AS max_index " + + "FROM commit_status GROUP BY commit_status.repo_id, commit_status.sha) AS max_data"); err != nil { + return err + } + + return sess.Commit() +} diff --git a/models/migrations/v195_test.go b/models/migrations/v195_test.go new file mode 100644 index 0000000000000..baf9cb61c2a0c --- /dev/null +++ b/models/migrations/v195_test.go @@ -0,0 +1,62 @@ +// Copyright 2021 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 ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_addTableCommitStatusIndex(t *testing.T) { + // Create the models used in the migration + type CommitStatus struct { + ID int64 `xorm:"pk autoincr"` + Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"` + RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"` + SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"` + } + + // Prepare and load the testing database + x, deferable := prepareTestEnv(t, 0, new(CommitStatus)) + if x == nil || t.Failed() { + defer deferable() + return + } + defer deferable() + + // Run the migration + if err := addTableCommitStatusIndex(x); err != nil { + assert.NoError(t, err) + return + } + + type CommitStatusIndex struct { + ID int64 + RepoID int64 `xorm:"unique(repo_sha)"` + SHA string `xorm:"unique(repo_sha)"` + MaxIndex int64 `xorm:"index"` + } + + var start = 0 + const batchSize = 1000 + for { + var indexes = make([]CommitStatusIndex, 0, batchSize) + err := x.Table("commit_status_index").Limit(batchSize, start).Find(&indexes) + assert.NoError(t, err) + + for _, idx := range indexes { + var maxIndex int + has, err := x.SQL("SELECT max(`index`) FROM commit_status WHERE repo_id = ? AND sha = ?", idx.RepoID, idx.SHA).Get(&maxIndex) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, maxIndex, idx.MaxIndex) + } + if len(indexes) < batchSize { + break + } + start += len(indexes) + } +} diff --git a/models/migrations/v196.go b/models/migrations/v196.go new file mode 100644 index 0000000000000..c0332c7bb4ee5 --- /dev/null +++ b/models/migrations/v196.go @@ -0,0 +1,22 @@ +// Copyright 2021 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 ( + "fmt" + + "xorm.io/xorm" +) + +func addColorColToProjectBoard(x *xorm.Engine) error { + type ProjectBoard struct { + Color string `xorm:"VARCHAR(7)"` + } + + if err := x.Sync2(new(ProjectBoard)); err != nil { + return fmt.Errorf("Sync2: %v", err) + } + return nil +} diff --git a/models/notification.go b/models/notification.go index 30bb7596a8a1d..bcbe8b0988e86 100644 --- a/models/notification.go +++ b/models/notification.go @@ -8,6 +8,7 @@ import ( "fmt" "strconv" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" @@ -67,9 +68,13 @@ type Notification struct { UpdatedUnix timeutil.TimeStamp `xorm:"updated INDEX NOT NULL"` } +func init() { + db.RegisterModel(new(Notification)) +} + // FindNotificationOptions represent the filters for notifications. If an ID is 0 it will be ignored. type FindNotificationOptions struct { - ListOptions + db.ListOptions UserID int64 RepoID int64 IssueID int64 @@ -107,32 +112,32 @@ func (opts *FindNotificationOptions) ToCond() builder.Cond { } // ToSession will convert the given options to a xorm Session by using the conditions from ToCond and joining with issue table if required -func (opts *FindNotificationOptions) ToSession(e Engine) *xorm.Session { +func (opts *FindNotificationOptions) ToSession(e db.Engine) *xorm.Session { sess := e.Where(opts.ToCond()) if opts.Page != 0 { - sess = opts.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, opts) } return sess } -func getNotifications(e Engine, options *FindNotificationOptions) (nl NotificationList, err error) { +func getNotifications(e db.Engine, options *FindNotificationOptions) (nl NotificationList, err error) { err = options.ToSession(e).OrderBy("notification.updated_unix DESC").Find(&nl) return } // GetNotifications returns all notifications that fit to the given options. func GetNotifications(opts *FindNotificationOptions) (NotificationList, error) { - return getNotifications(x, opts) + return getNotifications(db.GetEngine(db.DefaultContext), opts) } // CountNotifications count all notifications that fit to the given options and ignore pagination. func CountNotifications(opts *FindNotificationOptions) (int64, error) { - return x.Where(opts.ToCond()).Count(&Notification{}) + return db.GetEngine(db.DefaultContext).Where(opts.ToCond()).Count(&Notification{}) } // CreateRepoTransferNotification creates notification for the user a repository was transferred to func CreateRepoTransferNotification(doer, newOwner *User, repo *Repository) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -174,7 +179,7 @@ func CreateRepoTransferNotification(doer, newOwner *User, repo *Repository) erro // for each watcher, or updates it if already exists // receiverID > 0 just send to reciver, else send to all watcher func CreateOrUpdateIssueNotifications(issueID, commentID, notificationAuthorID, receiverID int64) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -187,7 +192,7 @@ func CreateOrUpdateIssueNotifications(issueID, commentID, notificationAuthorID, return sess.Commit() } -func createOrUpdateIssueNotifications(e Engine, issueID, commentID, notificationAuthorID, receiverID int64) error { +func createOrUpdateIssueNotifications(e db.Engine, issueID, commentID, notificationAuthorID, receiverID int64) error { // init var toNotify map[int64]struct{} notifications, err := getNotificationsByIssueID(e, issueID) @@ -277,7 +282,7 @@ func createOrUpdateIssueNotifications(e Engine, issueID, commentID, notification return nil } -func getNotificationsByIssueID(e Engine, issueID int64) (notifications []*Notification, err error) { +func getNotificationsByIssueID(e db.Engine, issueID int64) (notifications []*Notification, err error) { err = e. Where("issue_id = ?", issueID). Find(¬ifications) @@ -294,7 +299,7 @@ func notificationExists(notifications []*Notification, issueID, userID int64) bo return false } -func createIssueNotification(e Engine, userID int64, issue *Issue, commentID, updatedByID int64) error { +func createIssueNotification(e db.Engine, userID int64, issue *Issue, commentID, updatedByID int64) error { notification := &Notification{ UserID: userID, RepoID: issue.RepoID, @@ -314,7 +319,7 @@ func createIssueNotification(e Engine, userID int64, issue *Issue, commentID, up return err } -func updateIssueNotification(e Engine, userID, issueID, commentID, updatedByID int64) error { +func updateIssueNotification(e db.Engine, userID, issueID, commentID, updatedByID int64) error { notification, err := getIssueNotification(e, userID, issueID) if err != nil { return err @@ -336,7 +341,7 @@ func updateIssueNotification(e Engine, userID, issueID, commentID, updatedByID i return err } -func getIssueNotification(e Engine, userID, issueID int64) (*Notification, error) { +func getIssueNotification(e db.Engine, userID, issueID int64) (*Notification, error) { notification := new(Notification) _, err := e. Where("user_id = ?", userID). @@ -347,10 +352,10 @@ func getIssueNotification(e Engine, userID, issueID int64) (*Notification, error // NotificationsForUser returns notifications for a given user and status func NotificationsForUser(user *User, statuses []NotificationStatus, page, perPage int) (NotificationList, error) { - return notificationsForUser(x, user, statuses, page, perPage) + return notificationsForUser(db.GetEngine(db.DefaultContext), user, statuses, page, perPage) } -func notificationsForUser(e Engine, user *User, statuses []NotificationStatus, page, perPage int) (notifications []*Notification, err error) { +func notificationsForUser(e db.Engine, user *User, statuses []NotificationStatus, page, perPage int) (notifications []*Notification, err error) { if len(statuses) == 0 { return } @@ -370,10 +375,10 @@ func notificationsForUser(e Engine, user *User, statuses []NotificationStatus, p // CountUnread count unread notifications for a user func CountUnread(user *User) int64 { - return countUnread(x, user.ID) + return countUnread(db.GetEngine(db.DefaultContext), user.ID) } -func countUnread(e Engine, userID int64) int64 { +func countUnread(e db.Engine, userID int64) int64 { exist, err := e.Where("user_id = ?", userID).And("status = ?", NotificationStatusUnread).Count(new(Notification)) if err != nil { log.Error("countUnread", err) @@ -384,10 +389,10 @@ func countUnread(e Engine, userID int64) int64 { // LoadAttributes load Repo Issue User and Comment if not loaded func (n *Notification) LoadAttributes() (err error) { - return n.loadAttributes(x) + return n.loadAttributes(db.GetEngine(db.DefaultContext)) } -func (n *Notification) loadAttributes(e Engine) (err error) { +func (n *Notification) loadAttributes(e db.Engine) (err error) { if err = n.loadRepo(e); err != nil { return } @@ -403,7 +408,7 @@ func (n *Notification) loadAttributes(e Engine) (err error) { return } -func (n *Notification) loadRepo(e Engine) (err error) { +func (n *Notification) loadRepo(e db.Engine) (err error) { if n.Repository == nil { n.Repository, err = getRepositoryByID(e, n.RepoID) if err != nil { @@ -413,7 +418,7 @@ func (n *Notification) loadRepo(e Engine) (err error) { return nil } -func (n *Notification) loadIssue(e Engine) (err error) { +func (n *Notification) loadIssue(e db.Engine) (err error) { if n.Issue == nil && n.IssueID != 0 { n.Issue, err = getIssueByID(e, n.IssueID) if err != nil { @@ -424,7 +429,7 @@ func (n *Notification) loadIssue(e Engine) (err error) { return nil } -func (n *Notification) loadComment(e Engine) (err error) { +func (n *Notification) loadComment(e db.Engine) (err error) { if n.Comment == nil && n.CommentID != 0 { n.Comment, err = getCommentByID(e, n.CommentID) if err != nil { @@ -434,7 +439,7 @@ func (n *Notification) loadComment(e Engine) (err error) { return nil } -func (n *Notification) loadUser(e Engine) (err error) { +func (n *Notification) loadUser(e db.Engine) (err error) { if n.User == nil { n.User, err = getUserByID(e, n.UserID) if err != nil { @@ -446,12 +451,12 @@ func (n *Notification) loadUser(e Engine) (err error) { // GetRepo returns the repo of the notification func (n *Notification) GetRepo() (*Repository, error) { - return n.Repository, n.loadRepo(x) + return n.Repository, n.loadRepo(db.GetEngine(db.DefaultContext)) } // GetIssue returns the issue of the notification func (n *Notification) GetIssue() (*Issue, error) { - return n.Issue, n.loadIssue(x) + return n.Issue, n.loadIssue(db.GetEngine(db.DefaultContext)) } // HTMLURL formats a URL-string to the notification @@ -516,7 +521,7 @@ func (nl NotificationList) LoadRepos() (RepositoryList, []int, error) { if left < limit { limit = left } - rows, err := x. + rows, err := db.GetEngine(db.DefaultContext). In("id", repoIDs[:limit]). Rows(new(Repository)) if err != nil { @@ -592,7 +597,7 @@ func (nl NotificationList) LoadIssues() ([]int, error) { if left < limit { limit = left } - rows, err := x. + rows, err := db.GetEngine(db.DefaultContext). In("id", issueIDs[:limit]). Rows(new(Issue)) if err != nil { @@ -678,7 +683,7 @@ func (nl NotificationList) LoadComments() ([]int, error) { if left < limit { limit = left } - rows, err := x. + rows, err := db.GetEngine(db.DefaultContext). In("id", commentIDs[:limit]). Rows(new(Comment)) if err != nil { @@ -718,10 +723,10 @@ func (nl NotificationList) LoadComments() ([]int, error) { // GetNotificationCount returns the notification count for user func GetNotificationCount(user *User, status NotificationStatus) (int64, error) { - return getNotificationCount(x, user, status) + return getNotificationCount(db.GetEngine(db.DefaultContext), user, status) } -func getNotificationCount(e Engine, user *User, status NotificationStatus) (count int64, err error) { +func getNotificationCount(e db.Engine, user *User, status NotificationStatus) (count int64, err error) { count, err = e. Where("user_id = ?", user.ID). And("status = ?", status). @@ -741,10 +746,10 @@ func GetUIDsAndNotificationCounts(since, until timeutil.TimeStamp) ([]UserIDCoun `WHERE user_id IN (SELECT user_id FROM notification WHERE updated_unix >= ? AND ` + `updated_unix < ?) AND status = ? GROUP BY user_id` var res []UserIDCount - return res, x.SQL(sql, since, until, NotificationStatusUnread).Find(&res) + return res, db.GetEngine(db.DefaultContext).SQL(sql, since, until, NotificationStatusUnread).Find(&res) } -func setIssueNotificationStatusReadIfUnread(e Engine, userID, issueID int64) error { +func setIssueNotificationStatusReadIfUnread(e db.Engine, userID, issueID int64) error { notification, err := getIssueNotification(e, userID, issueID) // ignore if not exists if err != nil { @@ -761,7 +766,7 @@ func setIssueNotificationStatusReadIfUnread(e Engine, userID, issueID int64) err return err } -func setRepoNotificationStatusReadIfUnread(e Engine, userID, repoID int64) error { +func setRepoNotificationStatusReadIfUnread(e db.Engine, userID, repoID int64) error { _, err := e.Where(builder.Eq{ "user_id": userID, "status": NotificationStatusUnread, @@ -772,28 +777,28 @@ func setRepoNotificationStatusReadIfUnread(e Engine, userID, repoID int64) error } // SetNotificationStatus change the notification status -func SetNotificationStatus(notificationID int64, user *User, status NotificationStatus) error { - notification, err := getNotificationByID(x, notificationID) +func SetNotificationStatus(notificationID int64, user *User, status NotificationStatus) (*Notification, error) { + notification, err := getNotificationByID(db.GetEngine(db.DefaultContext), notificationID) if err != nil { - return err + return notification, err } if notification.UserID != user.ID { - return fmt.Errorf("Can't change notification of another user: %d, %d", notification.UserID, user.ID) + return nil, fmt.Errorf("Can't change notification of another user: %d, %d", notification.UserID, user.ID) } notification.Status = status - _, err = x.ID(notificationID).Update(notification) - return err + _, err = db.GetEngine(db.DefaultContext).ID(notificationID).Update(notification) + return notification, err } // GetNotificationByID return notification by ID func GetNotificationByID(notificationID int64) (*Notification, error) { - return getNotificationByID(x, notificationID) + return getNotificationByID(db.GetEngine(db.DefaultContext), notificationID) } -func getNotificationByID(e Engine, notificationID int64) (*Notification, error) { +func getNotificationByID(e db.Engine, notificationID int64) (*Notification, error) { notification := new(Notification) ok, err := e. Where("id = ?", notificationID). @@ -812,7 +817,7 @@ func getNotificationByID(e Engine, notificationID int64) (*Notification, error) // UpdateNotificationStatuses updates the statuses of all of a user's notifications that are of the currentStatus type to the desiredStatus func UpdateNotificationStatuses(user *User, currentStatus, desiredStatus NotificationStatus) error { n := &Notification{Status: desiredStatus, UpdatedBy: user.ID} - _, err := x. + _, err := db.GetEngine(db.DefaultContext). Where("user_id = ? AND status = ?", user.ID, currentStatus). Cols("status", "updated_by", "updated_unix"). Update(n) diff --git a/models/notification_test.go b/models/notification_test.go index 07b9f97de5af4..588882bed3ffb 100644 --- a/models/notification_test.go +++ b/models/notification_test.go @@ -7,27 +7,28 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestCreateOrUpdateIssueNotifications(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) + assert.NoError(t, db.PrepareTestDatabase()) + issue := db.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) assert.NoError(t, CreateOrUpdateIssueNotifications(issue.ID, 0, 2, 0)) // User 9 is inactive, thus notifications for user 1 and 4 are created - notf := AssertExistsAndLoadBean(t, &Notification{UserID: 1, IssueID: issue.ID}).(*Notification) + notf := db.AssertExistsAndLoadBean(t, &Notification{UserID: 1, IssueID: issue.ID}).(*Notification) assert.Equal(t, NotificationStatusUnread, notf.Status) CheckConsistencyFor(t, &Issue{ID: issue.ID}) - notf = AssertExistsAndLoadBean(t, &Notification{UserID: 4, IssueID: issue.ID}).(*Notification) + notf = db.AssertExistsAndLoadBean(t, &Notification{UserID: 4, IssueID: issue.ID}).(*Notification) assert.Equal(t, NotificationStatusUnread, notf.Status) } func TestNotificationsForUser(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + user := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) statuses := []NotificationStatus{NotificationStatusRead, NotificationStatusUnread} notfs, err := NotificationsForUser(user, statuses, 1, 10) assert.NoError(t, err) @@ -42,8 +43,8 @@ func TestNotificationsForUser(t *testing.T) { } func TestNotification_GetRepo(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - notf := AssertExistsAndLoadBean(t, &Notification{RepoID: 1}).(*Notification) + assert.NoError(t, db.PrepareTestDatabase()) + notf := db.AssertExistsAndLoadBean(t, &Notification{RepoID: 1}).(*Notification) repo, err := notf.GetRepo() assert.NoError(t, err) assert.Equal(t, repo, notf.Repository) @@ -51,8 +52,8 @@ func TestNotification_GetRepo(t *testing.T) { } func TestNotification_GetIssue(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - notf := AssertExistsAndLoadBean(t, &Notification{RepoID: 1}).(*Notification) + assert.NoError(t, db.PrepareTestDatabase()) + notf := db.AssertExistsAndLoadBean(t, &Notification{RepoID: 1}).(*Notification) issue, err := notf.GetIssue() assert.NoError(t, err) assert.Equal(t, issue, notf.Issue) @@ -60,8 +61,8 @@ func TestNotification_GetIssue(t *testing.T) { } func TestGetNotificationCount(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - user := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + user := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) cnt, err := GetNotificationCount(user, NotificationStatusRead) assert.NoError(t, err) assert.EqualValues(t, 0, cnt) @@ -72,32 +73,35 @@ func TestGetNotificationCount(t *testing.T) { } func TestSetNotificationStatus(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - notf := AssertExistsAndLoadBean(t, + assert.NoError(t, db.PrepareTestDatabase()) + user := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + notf := db.AssertExistsAndLoadBean(t, &Notification{UserID: user.ID, Status: NotificationStatusRead}).(*Notification) - assert.NoError(t, SetNotificationStatus(notf.ID, user, NotificationStatusPinned)) - AssertExistsAndLoadBean(t, + _, err := SetNotificationStatus(notf.ID, user, NotificationStatusPinned) + assert.NoError(t, err) + db.AssertExistsAndLoadBean(t, &Notification{ID: notf.ID, Status: NotificationStatusPinned}) - assert.Error(t, SetNotificationStatus(1, user, NotificationStatusRead)) - assert.Error(t, SetNotificationStatus(NonexistentID, user, NotificationStatusRead)) + _, err = SetNotificationStatus(1, user, NotificationStatusRead) + assert.Error(t, err) + _, err = SetNotificationStatus(db.NonexistentID, user, NotificationStatusRead) + assert.Error(t, err) } func TestUpdateNotificationStatuses(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - notfUnread := AssertExistsAndLoadBean(t, + assert.NoError(t, db.PrepareTestDatabase()) + user := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + notfUnread := db.AssertExistsAndLoadBean(t, &Notification{UserID: user.ID, Status: NotificationStatusUnread}).(*Notification) - notfRead := AssertExistsAndLoadBean(t, + notfRead := db.AssertExistsAndLoadBean(t, &Notification{UserID: user.ID, Status: NotificationStatusRead}).(*Notification) - notfPinned := AssertExistsAndLoadBean(t, + notfPinned := db.AssertExistsAndLoadBean(t, &Notification{UserID: user.ID, Status: NotificationStatusPinned}).(*Notification) assert.NoError(t, UpdateNotificationStatuses(user, NotificationStatusUnread, NotificationStatusRead)) - AssertExistsAndLoadBean(t, + db.AssertExistsAndLoadBean(t, &Notification{ID: notfUnread.ID, Status: NotificationStatusRead}) - AssertExistsAndLoadBean(t, + db.AssertExistsAndLoadBean(t, &Notification{ID: notfRead.ID, Status: NotificationStatusRead}) - AssertExistsAndLoadBean(t, + db.AssertExistsAndLoadBean(t, &Notification{ID: notfPinned.ID, Status: NotificationStatusPinned}) } diff --git a/models/oauth2.go b/models/oauth2.go deleted file mode 100644 index 127e8d760321b..0000000000000 --- a/models/oauth2.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2017 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 models - -// GetActiveOAuth2ProviderLoginSources returns all actived LoginOAuth2 sources -func GetActiveOAuth2ProviderLoginSources() ([]*LoginSource, error) { - sources := make([]*LoginSource, 0, 1) - if err := x.Where("is_active = ? and type = ?", true, LoginOAuth2).Find(&sources); err != nil { - return nil, err - } - return sources, nil -} - -// GetActiveOAuth2LoginSourceByName returns a OAuth2 LoginSource based on the given name -func GetActiveOAuth2LoginSourceByName(name string) (*LoginSource, error) { - loginSource := new(LoginSource) - has, err := x.Where("name = ? and type = ? and is_active = ?", name, LoginOAuth2, true).Get(loginSource) - if !has || err != nil { - return nil, err - } - - return loginSource, nil -} diff --git a/models/org.go b/models/org.go index 9540c4240e126..eadd1e157c922 100644 --- a/models/org.go +++ b/models/org.go @@ -9,6 +9,7 @@ import ( "fmt" "strings" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" @@ -34,25 +35,25 @@ func (org *User) CanCreateOrgRepo(uid int64) (bool, error) { return CanCreateOrgRepo(org.ID, uid) } -func (org *User) getTeam(e Engine, name string) (*Team, error) { +func (org *User) getTeam(e db.Engine, name string) (*Team, error) { return getTeam(e, org.ID, name) } // GetTeam returns named team of organization. func (org *User) GetTeam(name string) (*Team, error) { - return org.getTeam(x, name) + return org.getTeam(db.GetEngine(db.DefaultContext), name) } -func (org *User) getOwnerTeam(e Engine) (*Team, error) { +func (org *User) getOwnerTeam(e db.Engine) (*Team, error) { return org.getTeam(e, ownerTeamName) } // GetOwnerTeam returns owner team of organization. func (org *User) GetOwnerTeam() (*Team, error) { - return org.getOwnerTeam(x) + return org.getOwnerTeam(db.GetEngine(db.DefaultContext)) } -func (org *User) loadTeams(e Engine) error { +func (org *User) loadTeams(e db.Engine) error { if org.Teams != nil { return nil } @@ -64,7 +65,7 @@ func (org *User) loadTeams(e Engine) error { // LoadTeams load teams if not loaded. func (org *User) LoadTeams() error { - return org.loadTeams(x) + return org.loadTeams(db.GetEngine(db.DefaultContext)) } // GetMembers returns all members of organization. @@ -77,14 +78,14 @@ func (org *User) GetMembers() (err error) { // FindOrgMembersOpts represensts find org members conditions type FindOrgMembersOpts struct { - ListOptions + db.ListOptions OrgID int64 PublicOnly bool } // CountOrgMembers counts the organization's members func CountOrgMembers(opts *FindOrgMembersOpts) (int64, error) { - sess := x.Where("org_id=?", opts.OrgID) + sess := db.GetEngine(db.DefaultContext).Where("org_id=?", opts.OrgID) if opts.PublicOnly { sess.And("is_public = ?", true) } @@ -122,13 +123,13 @@ func (org *User) RemoveMember(uid int64) error { return RemoveOrgUser(org.ID, uid) } -func (org *User) removeOrgRepo(e Engine, repoID int64) error { +func (org *User) removeOrgRepo(e db.Engine, repoID int64) error { return removeOrgRepo(e, org.ID, repoID) } // RemoveOrgRepo removes all team-repository relations of organization. func (org *User) RemoveOrgRepo(repoID int64) error { - return org.removeOrgRepo(x, repoID) + return org.removeOrgRepo(db.GetEngine(db.DefaultContext), repoID) } // CreateOrganization creates record of a new organization. @@ -161,7 +162,7 @@ func CreateOrganization(org, owner *User) (err error) { org.NumMembers = 1 org.Type = UserTypeOrganization - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -237,7 +238,7 @@ func GetOrgByName(name string) (*User, error) { LowerName: strings.ToLower(name), Type: UserTypeOrganization, } - has, err := x.Get(u) + has, err := db.GetEngine(db.DefaultContext).Get(u) if err != nil { return nil, err } else if !has { @@ -248,7 +249,7 @@ func GetOrgByName(name string) (*User, error) { // CountOrganizations returns number of organizations. func CountOrganizations() int64 { - count, _ := x. + count, _ := db.GetEngine(db.DefaultContext). Where("type=1"). Count(new(User)) return count @@ -260,7 +261,7 @@ func DeleteOrganization(org *User) (err error) { return fmt.Errorf("%s is a user not an organization", org.Name) } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { @@ -334,7 +335,11 @@ type OrgUser struct { IsPublic bool `xorm:"INDEX"` } -func isOrganizationOwner(e Engine, orgID, uid int64) (bool, error) { +func init() { + db.RegisterModel(new(OrgUser)) +} + +func isOrganizationOwner(e db.Engine, orgID, uid int64) (bool, error) { ownerTeam, err := getOwnerTeam(e, orgID) if err != nil { if IsErrTeamNotExist(err) { @@ -348,15 +353,15 @@ func isOrganizationOwner(e Engine, orgID, uid int64) (bool, error) { // IsOrganizationOwner returns true if given user is in the owner team. func IsOrganizationOwner(orgID, uid int64) (bool, error) { - return isOrganizationOwner(x, orgID, uid) + return isOrganizationOwner(db.GetEngine(db.DefaultContext), orgID, uid) } // IsOrganizationMember returns true if given user is member of organization. func IsOrganizationMember(orgID, uid int64) (bool, error) { - return isOrganizationMember(x, orgID, uid) + return isOrganizationMember(db.GetEngine(db.DefaultContext), orgID, uid) } -func isOrganizationMember(e Engine, orgID, uid int64) (bool, error) { +func isOrganizationMember(e db.Engine, orgID, uid int64) (bool, error) { return e. Where("uid=?", uid). And("org_id=?", orgID). @@ -366,7 +371,7 @@ func isOrganizationMember(e Engine, orgID, uid int64) (bool, error) { // IsPublicMembership returns true if given user public his/her membership. func IsPublicMembership(orgID, uid int64) (bool, error) { - return x. + return db.GetEngine(db.DefaultContext). Where("uid=?", uid). And("org_id=?", orgID). And("is_public=?", true). @@ -379,7 +384,7 @@ func CanCreateOrgRepo(orgID, uid int64) (bool, error) { if owner, err := IsOrganizationOwner(orgID, uid); owner || err != nil { return owner, err } - return x. + return db.GetEngine(db.DefaultContext). Where(builder.Eq{"team.can_create_org_repo": true}). Join("INNER", "team_user", "team_user.team_id = team.id"). And("team_user.uid = ?", uid). @@ -389,12 +394,12 @@ func CanCreateOrgRepo(orgID, uid int64) (bool, error) { // GetUsersWhoCanCreateOrgRepo returns users which are able to create repo in organization func GetUsersWhoCanCreateOrgRepo(orgID int64) ([]*User, error) { - return getUsersWhoCanCreateOrgRepo(x, orgID) + return getUsersWhoCanCreateOrgRepo(db.GetEngine(db.DefaultContext), orgID) } -func getUsersWhoCanCreateOrgRepo(e Engine, orgID int64) ([]*User, error) { +func getUsersWhoCanCreateOrgRepo(e db.Engine, orgID int64) ([]*User, error) { users := make([]*User, 0, 10) - return users, x. + return users, db.GetEngine(db.DefaultContext). Join("INNER", "`team_user`", "`team_user`.uid=`user`.id"). Join("INNER", "`team`", "`team`.id=`team_user`.team_id"). Where(builder.Eq{"team.can_create_org_repo": true}.Or(builder.Eq{"team.authorize": AccessModeOwner})). @@ -416,7 +421,7 @@ func getOrgsByUserID(sess *xorm.Session, userID int64, showAll bool) ([]*User, e // GetOrgsByUserID returns a list of organizations that the given user ID // has joined. func GetOrgsByUserID(userID int64, showAll bool) ([]*User, error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() return getOrgsByUserID(sess, userID, showAll) } @@ -426,10 +431,10 @@ type MinimalOrg = User // GetUserOrgsList returns one user's all orgs list func GetUserOrgsList(user *User) ([]*MinimalOrg, error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() - schema, err := x.TableInfo(new(User)) + schema, err := db.TableInfo(new(User)) if err != nil { return nil, err } @@ -451,7 +456,7 @@ func GetUserOrgsList(user *User) ([]*MinimalOrg, error) { groupByStr := groupByCols.String() groupByStr = groupByStr[0 : len(groupByStr)-1] - sess.Select(groupByStr+", count(repo_id) as org_count"). + sess.Select(groupByStr+", count(distinct repo_id) as org_count"). Table("user"). Join("INNER", "team", "`team`.org_id = `user`.id"). Join("INNER", "team_user", "`team`.id = `team_user`.team_id"). @@ -497,10 +502,10 @@ func getOwnedOrgsByUserID(sess *xorm.Session, userID int64) ([]*User, error) { // HasOrgOrUserVisible tells if the given user can see the given org or user func HasOrgOrUserVisible(org, user *User) bool { - return hasOrgOrUserVisible(x, org, user) + return hasOrgOrUserVisible(db.GetEngine(db.DefaultContext), org, user) } -func hasOrgOrUserVisible(e Engine, orgOrUser, user *User) bool { +func hasOrgOrUserVisible(e db.Engine, orgOrUser, user *User) bool { // Not SignedUser if user == nil { return orgOrUser.Visibility == structs.VisibleTypePublic @@ -532,7 +537,7 @@ func HasOrgsVisible(orgs []*User, user *User) bool { // GetOwnedOrgsByUserID returns a list of organizations are owned by given user ID. func GetOwnedOrgsByUserID(userID int64) ([]*User, error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() return getOwnedOrgsByUserID(sess, userID) } @@ -540,7 +545,7 @@ func GetOwnedOrgsByUserID(userID int64) ([]*User, error) { // GetOwnedOrgsByUserIDDesc returns a list of organizations are owned by // given user ID, ordered descending by the given condition. func GetOwnedOrgsByUserIDDesc(userID int64, desc string) ([]*User, error) { - return getOwnedOrgsByUserID(x.Desc(desc), userID) + return getOwnedOrgsByUserID(db.GetEngine(db.DefaultContext).Desc(desc), userID) } // GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID @@ -548,7 +553,7 @@ func GetOwnedOrgsByUserIDDesc(userID int64, desc string) ([]*User, error) { func GetOrgsCanCreateRepoByUserID(userID int64) ([]*User, error) { orgs := make([]*User, 0, 10) - return orgs, x.Where(builder.In("id", builder.Select("`user`.id").From("`user`"). + return orgs, db.GetEngine(db.DefaultContext).Where(builder.In("id", builder.Select("`user`.id").From("`user`"). Join("INNER", "`team_user`", "`team_user`.org_id = `user`.id"). Join("INNER", "`team`", "`team`.id = `team_user`.team_id"). Where(builder.Eq{"`team_user`.uid": userID}). @@ -560,7 +565,7 @@ func GetOrgsCanCreateRepoByUserID(userID int64) ([]*User, error) { // GetOrgUsersByUserID returns all organization-user relations by user ID. func GetOrgUsersByUserID(uid int64, opts *SearchOrganizationsOptions) ([]*OrgUser, error) { ous := make([]*OrgUser, 0, 10) - sess := x. + sess := db.GetEngine(db.DefaultContext). Join("LEFT", "`user`", "`org_user`.org_id=`user`.id"). Where("`org_user`.uid=?", uid) if !opts.All { @@ -569,7 +574,7 @@ func GetOrgUsersByUserID(uid int64, opts *SearchOrganizationsOptions) ([]*OrgUse } if opts.PageSize != 0 { - sess = opts.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, opts) } err := sess. @@ -580,16 +585,16 @@ func GetOrgUsersByUserID(uid int64, opts *SearchOrganizationsOptions) ([]*OrgUse // GetOrgUsersByOrgID returns all organization-user relations by organization ID. func GetOrgUsersByOrgID(opts *FindOrgMembersOpts) ([]*OrgUser, error) { - return getOrgUsersByOrgID(x, opts) + return getOrgUsersByOrgID(db.GetEngine(db.DefaultContext), opts) } -func getOrgUsersByOrgID(e Engine, opts *FindOrgMembersOpts) ([]*OrgUser, error) { +func getOrgUsersByOrgID(e db.Engine, opts *FindOrgMembersOpts) ([]*OrgUser, error) { sess := e.Where("org_id=?", opts.OrgID) if opts.PublicOnly { sess.And("is_public = ?", true) } if opts.ListOptions.PageSize > 0 { - sess = opts.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, opts) ous := make([]*OrgUser, 0, opts.PageSize) return ous, sess.Find(&ous) @@ -602,7 +607,7 @@ func getOrgUsersByOrgID(e Engine, opts *FindOrgMembersOpts) ([]*OrgUser, error) // ChangeOrgUserStatus changes public or private membership status. func ChangeOrgUserStatus(orgID, uid int64, public bool) error { ou := new(OrgUser) - has, err := x. + has, err := db.GetEngine(db.DefaultContext). Where("uid=?", uid). And("org_id=?", orgID). Get(ou) @@ -613,7 +618,7 @@ func ChangeOrgUserStatus(orgID, uid int64, public bool) error { } ou.IsPublic = public - _, err = x.ID(ou.ID).Cols("is_public").Update(ou) + _, err = db.GetEngine(db.DefaultContext).ID(ou.ID).Cols("is_public").Update(ou) return err } @@ -624,7 +629,7 @@ func AddOrgUser(orgID, uid int64) error { return err } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -733,7 +738,7 @@ func removeOrgUser(sess *xorm.Session, orgID, userID int64) error { // RemoveOrgUser removes user from given organization. func RemoveOrgUser(orgID, userID int64) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -744,7 +749,7 @@ func RemoveOrgUser(orgID, userID int64) error { return sess.Commit() } -func removeOrgRepo(e Engine, orgID, repoID int64) error { +func removeOrgRepo(e db.Engine, orgID, repoID int64) error { teamRepos := make([]*TeamRepo, 0, 10) if err := e.Find(&teamRepos, &TeamRepo{OrgID: orgID, RepoID: repoID}); err != nil { return err @@ -770,7 +775,7 @@ func removeOrgRepo(e Engine, orgID, repoID int64) error { return err } -func (org *User) getUserTeams(e Engine, userID int64, cols ...string) ([]*Team, error) { +func (org *User) getUserTeams(e db.Engine, userID int64, cols ...string) ([]*Team, error) { teams := make([]*Team, 0, org.NumTeams) return teams, e. Where("`team_user`.org_id = ?", org.ID). @@ -782,7 +787,7 @@ func (org *User) getUserTeams(e Engine, userID int64, cols ...string) ([]*Team, Find(&teams) } -func (org *User) getUserTeamIDs(e Engine, userID int64) ([]int64, error) { +func (org *User) getUserTeamIDs(e db.Engine, userID int64) ([]int64, error) { teamIDs := make([]int64, 0, org.NumTeams) return teamIDs, e. Table("team"). @@ -800,13 +805,13 @@ func (org *User) TeamsWithAccessToRepo(repoID int64, mode AccessMode) ([]*Team, // GetUserTeamIDs returns of all team IDs of the organization that user is member of. func (org *User) GetUserTeamIDs(userID int64) ([]int64, error) { - return org.getUserTeamIDs(x, userID) + return org.getUserTeamIDs(db.GetEngine(db.DefaultContext), userID) } // GetUserTeams returns all teams that belong to user, // and that the user has joined. func (org *User) GetUserTeams(userID int64) ([]*Team, error) { - return org.getUserTeams(x, userID) + return org.getUserTeams(db.GetEngine(db.DefaultContext), userID) } // AccessibleReposEnvironment operations involving the repositories that are @@ -825,7 +830,7 @@ type accessibleReposEnv struct { user *User team *Team teamIDs []int64 - e Engine + e db.Engine keyword string orderBy SearchOrderBy } @@ -833,10 +838,10 @@ type accessibleReposEnv struct { // AccessibleReposEnv builds an AccessibleReposEnvironment for the repositories in `org` // that are accessible to the specified user. func (org *User) AccessibleReposEnv(userID int64) (AccessibleReposEnvironment, error) { - return org.accessibleReposEnv(x, userID) + return org.accessibleReposEnv(db.GetEngine(db.DefaultContext), userID) } -func (org *User) accessibleReposEnv(e Engine, userID int64) (AccessibleReposEnvironment, error) { +func (org *User) accessibleReposEnv(e db.Engine, userID int64) (AccessibleReposEnvironment, error) { var user *User if userID > 0 { @@ -866,7 +871,7 @@ func (org *User) AccessibleTeamReposEnv(team *Team) AccessibleReposEnvironment { return &accessibleReposEnv{ org: org, team: team, - e: x, + e: db.GetEngine(db.DefaultContext), orderBy: SearchOrderByRecentUpdated, } } diff --git a/models/org_team.go b/models/org_team.go index c380c8cd8ef58..fc6a5f2c3b186 100644 --- a/models/org_team.go +++ b/models/org_team.go @@ -11,6 +11,7 @@ import ( "sort" "strings" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -37,9 +38,16 @@ type Team struct { CanCreateOrgRepo bool `xorm:"NOT NULL DEFAULT false"` } +func init() { + db.RegisterModel(new(Team)) + db.RegisterModel(new(TeamUser)) + db.RegisterModel(new(TeamRepo)) + db.RegisterModel(new(TeamUnit)) +} + // SearchTeamOptions holds the search options type SearchTeamOptions struct { - ListOptions + db.ListOptions UserID int64 Keyword string OrgID int64 @@ -48,7 +56,7 @@ type SearchTeamOptions struct { // SearchMembersOptions holds the search options type SearchMembersOptions struct { - ListOptions + db.ListOptions } // SearchTeam search for teams. Caller is responsible to check permissions. @@ -74,7 +82,7 @@ func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) { cond = cond.And(builder.Eq{"org_id": opts.OrgID}) - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() count, err := sess. @@ -112,10 +120,10 @@ func (t *Team) ColorFormat(s fmt.State) { // GetUnits return a list of available units for a team func (t *Team) GetUnits() error { - return t.getUnits(x) + return t.getUnits(db.GetEngine(db.DefaultContext)) } -func (t *Team) getUnits(e Engine) (err error) { +func (t *Team) getUnits(e db.Engine) (err error) { if t.Units != nil { return nil } @@ -152,7 +160,7 @@ func (t *Team) IsMember(userID int64) bool { return isMember } -func (t *Team) getRepositories(e Engine) error { +func (t *Team) getRepositories(e db.Engine) error { if t.Repos != nil { return nil } @@ -165,13 +173,13 @@ func (t *Team) getRepositories(e Engine) error { // GetRepositories returns paginated repositories in team of organization. func (t *Team) GetRepositories(opts *SearchTeamOptions) error { if opts.Page == 0 { - return t.getRepositories(x) + return t.getRepositories(db.GetEngine(db.DefaultContext)) } - return t.getRepositories(opts.getPaginatedSession()) + return t.getRepositories(db.GetPaginatedSession(opts)) } -func (t *Team) getMembers(e Engine) (err error) { +func (t *Team) getMembers(e db.Engine) (err error) { t.Members, err = getTeamMembers(e, t.ID) return err } @@ -179,10 +187,10 @@ func (t *Team) getMembers(e Engine) (err error) { // GetMembers returns paginated members in team of organization. func (t *Team) GetMembers(opts *SearchMembersOptions) (err error) { if opts.Page == 0 { - return t.getMembers(x) + return t.getMembers(db.GetEngine(db.DefaultContext)) } - return t.getMembers(opts.getPaginatedSession()) + return t.getMembers(db.GetPaginatedSession(opts)) } // AddMember adds new membership of the team to the organization, @@ -196,16 +204,16 @@ func (t *Team) RemoveMember(userID int64) error { return RemoveTeamMember(t, userID) } -func (t *Team) hasRepository(e Engine, repoID int64) bool { +func (t *Team) hasRepository(e db.Engine, repoID int64) bool { return hasTeamRepo(e, t.OrgID, t.ID, repoID) } // HasRepository returns true if given repository belong to team. func (t *Team) HasRepository(repoID int64) bool { - return t.hasRepository(x, repoID) + return t.hasRepository(db.GetEngine(db.DefaultContext), repoID) } -func (t *Team) addRepository(e Engine, repo *Repository) (err error) { +func (t *Team) addRepository(e db.Engine, repo *Repository) (err error) { if err = addTeamRepo(e, t.OrgID, t.ID, repo.ID); err != nil { return err } @@ -237,7 +245,7 @@ func (t *Team) addRepository(e Engine, repo *Repository) (err error) { // addAllRepositories adds all repositories to the team. // If the team already has some repositories they will be left unchanged. -func (t *Team) addAllRepositories(e Engine) error { +func (t *Team) addAllRepositories(e db.Engine) error { var orgRepos []Repository if err := e.Where("owner_id = ?", t.OrgID).Find(&orgRepos); err != nil { return fmt.Errorf("get org repos: %v", err) @@ -256,7 +264,7 @@ func (t *Team) addAllRepositories(e Engine) error { // AddAllRepositories adds all repositories to the team func (t *Team) AddAllRepositories() (err error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -277,7 +285,7 @@ func (t *Team) AddRepository(repo *Repository) (err error) { return nil } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -296,7 +304,7 @@ func (t *Team) RemoveAllRepositories() (err error) { return nil } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -311,7 +319,7 @@ func (t *Team) RemoveAllRepositories() (err error) { // removeAllRepositories removes all repositories from team and recalculates access // Note: Shall not be called if team includes all repositories -func (t *Team) removeAllRepositories(e Engine) (err error) { +func (t *Team) removeAllRepositories(e db.Engine) (err error) { // Delete all accesses. for _, repo := range t.Repos { if err := repo.recalculateTeamAccesses(e, t.ID); err != nil { @@ -355,7 +363,7 @@ func (t *Team) removeAllRepositories(e Engine) (err error) { // removeRepository removes a repository from a team and recalculates access // Note: Repository shall not be removed from team if it includes all repositories (unless the repository is deleted) -func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (err error) { +func (t *Team) removeRepository(e db.Engine, repo *Repository, recalculate bool) (err error) { if err = removeTeamRepo(e, t.ID, repo.ID); err != nil { return err } @@ -413,7 +421,7 @@ func (t *Team) RemoveRepository(repoID int64) error { return err } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -428,10 +436,10 @@ func (t *Team) RemoveRepository(repoID int64) error { // UnitEnabled returns if the team has the given unit type enabled func (t *Team) UnitEnabled(tp UnitType) bool { - return t.unitEnabled(x, tp) + return t.unitEnabled(db.GetEngine(db.DefaultContext), tp) } -func (t *Team) unitEnabled(e Engine, tp UnitType) bool { +func (t *Team) unitEnabled(e db.Engine, tp UnitType) bool { if err := t.getUnits(e); err != nil { log.Warn("Error loading team (ID: %d) units: %s", t.ID, err.Error()) } @@ -465,7 +473,7 @@ func NewTeam(t *Team) (err error) { return err } - has, err := x.ID(t.OrgID).Get(new(User)) + has, err := db.GetEngine(db.DefaultContext).ID(t.OrgID).Get(new(User)) if err != nil { return err } @@ -474,7 +482,7 @@ func NewTeam(t *Team) (err error) { } t.LowerName = strings.ToLower(t.Name) - has, err = x. + has, err = db.GetEngine(db.DefaultContext). Where("org_id=?", t.OrgID). And("lower_name=?", t.LowerName). Get(new(Team)) @@ -485,7 +493,7 @@ func NewTeam(t *Team) (err error) { return ErrTeamAlreadyExist{t.OrgID, t.LowerName} } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -532,7 +540,7 @@ func NewTeam(t *Team) (err error) { return sess.Commit() } -func getTeam(e Engine, orgID int64, name string) (*Team, error) { +func getTeam(e db.Engine, orgID int64, name string) (*Team, error) { t := &Team{ OrgID: orgID, LowerName: strings.ToLower(name), @@ -548,7 +556,7 @@ func getTeam(e Engine, orgID int64, name string) (*Team, error) { // GetTeam returns team by given team name and organization. func GetTeam(orgID int64, name string) (*Team, error) { - return getTeam(x, orgID, name) + return getTeam(db.GetEngine(db.DefaultContext), orgID, name) } // GetTeamIDsByNames returns a slice of team ids corresponds to names. @@ -569,11 +577,11 @@ func GetTeamIDsByNames(orgID int64, names []string, ignoreNonExistent bool) ([]i } // getOwnerTeam returns team by given team name and organization. -func getOwnerTeam(e Engine, orgID int64) (*Team, error) { +func getOwnerTeam(e db.Engine, orgID int64) (*Team, error) { return getTeam(e, orgID, ownerTeamName) } -func getTeamByID(e Engine, teamID int64) (*Team, error) { +func getTeamByID(e db.Engine, teamID int64) (*Team, error) { t := new(Team) has, err := e.ID(teamID).Get(t) if err != nil { @@ -586,7 +594,7 @@ func getTeamByID(e Engine, teamID int64) (*Team, error) { // GetTeamByID returns team by given ID. func GetTeamByID(teamID int64) (*Team, error) { - return getTeamByID(x, teamID) + return getTeamByID(db.GetEngine(db.DefaultContext), teamID) } // GetTeamNamesByID returns team's lower name from a list of team ids. @@ -596,7 +604,7 @@ func GetTeamNamesByID(teamIDs []int64) ([]string, error) { } var teamNames []string - err := x.Table("team"). + err := db.GetEngine(db.DefaultContext).Table("team"). Select("lower_name"). In("id", teamIDs). Asc("name"). @@ -615,7 +623,7 @@ func UpdateTeam(t *Team, authChanged, includeAllChanged bool) (err error) { t.Description = t.Description[:255] } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -689,7 +697,7 @@ func DeleteTeam(t *Team) error { return err } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -745,7 +753,7 @@ type TeamUser struct { UID int64 `xorm:"UNIQUE(s)"` } -func isTeamMember(e Engine, orgID, teamID, userID int64) (bool, error) { +func isTeamMember(e db.Engine, orgID, teamID, userID int64) (bool, error) { return e. Where("org_id=?", orgID). And("team_id=?", teamID). @@ -756,17 +764,17 @@ func isTeamMember(e Engine, orgID, teamID, userID int64) (bool, error) { // IsTeamMember returns true if given user is a member of team. func IsTeamMember(orgID, teamID, userID int64) (bool, error) { - return isTeamMember(x, orgID, teamID, userID) + return isTeamMember(db.GetEngine(db.DefaultContext), orgID, teamID, userID) } -func getTeamUsersByTeamID(e Engine, teamID int64) ([]*TeamUser, error) { +func getTeamUsersByTeamID(e db.Engine, teamID int64) ([]*TeamUser, error) { teamUsers := make([]*TeamUser, 0, 10) return teamUsers, e. Where("team_id=?", teamID). Find(&teamUsers) } -func getTeamMembers(e Engine, teamID int64) (_ []*User, err error) { +func getTeamMembers(e db.Engine, teamID int64) (_ []*User, err error) { teamUsers, err := getTeamUsersByTeamID(e, teamID) if err != nil { return nil, fmt.Errorf("get team-users: %v", err) @@ -787,10 +795,10 @@ func getTeamMembers(e Engine, teamID int64) (_ []*User, err error) { // GetTeamMembers returns all members in given team of organization. func GetTeamMembers(teamID int64) ([]*User, error) { - return getTeamMembers(x, teamID) + return getTeamMembers(db.GetEngine(db.DefaultContext), teamID) } -func getUserOrgTeams(e Engine, orgID, userID int64) (teams []*Team, err error) { +func getUserOrgTeams(e db.Engine, orgID, userID int64) (teams []*Team, err error) { return teams, e. Join("INNER", "team_user", "team_user.team_id = team.id"). Where("team.org_id = ?", orgID). @@ -798,7 +806,7 @@ func getUserOrgTeams(e Engine, orgID, userID int64) (teams []*Team, err error) { Find(&teams) } -func getUserRepoTeams(e Engine, orgID, userID, repoID int64) (teams []*Team, err error) { +func getUserRepoTeams(e db.Engine, orgID, userID, repoID int64) (teams []*Team, err error) { return teams, e. Join("INNER", "team_user", "team_user.team_id = team.id"). Join("INNER", "team_repo", "team_repo.team_id = team.id"). @@ -810,7 +818,7 @@ func getUserRepoTeams(e Engine, orgID, userID, repoID int64) (teams []*Team, err // GetUserOrgTeams returns all teams that user belongs to in given organization. func GetUserOrgTeams(orgID, userID int64) ([]*Team, error) { - return getUserOrgTeams(x, orgID, userID) + return getUserOrgTeams(db.GetEngine(db.DefaultContext), orgID, userID) } // AddTeamMember adds new membership of given team to given organization, @@ -830,7 +838,7 @@ func AddTeamMember(team *Team, userID int64) error { return err } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -925,7 +933,7 @@ func removeTeamMember(e *xorm.Session, team *Team, userID int64) error { // RemoveTeamMember removes member from given team of given organization. func RemoveTeamMember(team *Team, userID int64) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -938,17 +946,17 @@ func RemoveTeamMember(team *Team, userID int64) error { // IsUserInTeams returns if a user in some teams func IsUserInTeams(userID int64, teamIDs []int64) (bool, error) { - return isUserInTeams(x, userID, teamIDs) + return isUserInTeams(db.GetEngine(db.DefaultContext), userID, teamIDs) } -func isUserInTeams(e Engine, userID int64, teamIDs []int64) (bool, error) { +func isUserInTeams(e db.Engine, userID int64, teamIDs []int64) (bool, error) { return e.Where("uid=?", userID).In("team_id", teamIDs).Exist(new(TeamUser)) } // UsersInTeamsCount counts the number of users which are in userIDs and teamIDs func UsersInTeamsCount(userIDs, teamIDs []int64) (int64, error) { var ids []int64 - if err := x.In("uid", userIDs).In("team_id", teamIDs). + if err := db.GetEngine(db.DefaultContext).In("uid", userIDs).In("team_id", teamIDs). Table("team_user"). Cols("uid").GroupBy("uid").Find(&ids); err != nil { return 0, err @@ -971,7 +979,7 @@ type TeamRepo struct { RepoID int64 `xorm:"UNIQUE(s)"` } -func hasTeamRepo(e Engine, orgID, teamID, repoID int64) bool { +func hasTeamRepo(e db.Engine, orgID, teamID, repoID int64) bool { has, _ := e. Where("org_id=?", orgID). And("team_id=?", teamID). @@ -982,10 +990,10 @@ func hasTeamRepo(e Engine, orgID, teamID, repoID int64) bool { // HasTeamRepo returns true if given repository belongs to team. func HasTeamRepo(orgID, teamID, repoID int64) bool { - return hasTeamRepo(x, orgID, teamID, repoID) + return hasTeamRepo(db.GetEngine(db.DefaultContext), orgID, teamID, repoID) } -func addTeamRepo(e Engine, orgID, teamID, repoID int64) error { +func addTeamRepo(e db.Engine, orgID, teamID, repoID int64) error { _, err := e.InsertOne(&TeamRepo{ OrgID: orgID, TeamID: teamID, @@ -994,7 +1002,7 @@ func addTeamRepo(e Engine, orgID, teamID, repoID int64) error { return err } -func removeTeamRepo(e Engine, teamID, repoID int64) error { +func removeTeamRepo(e db.Engine, teamID, repoID int64) error { _, err := e.Delete(&TeamRepo{ TeamID: teamID, RepoID: repoID, @@ -1005,7 +1013,7 @@ func removeTeamRepo(e Engine, 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 AccessMode) ([]*Team, error) { teams := make([]*Team, 0, 5) - return teams, x.Where("team.authorize >= ?", mode). + return teams, db.GetEngine(db.DefaultContext).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). @@ -1032,13 +1040,13 @@ func (t *TeamUnit) Unit() Unit { return Units[t.Type] } -func getUnitsByTeamID(e Engine, teamID int64) (units []*TeamUnit, err error) { +func getUnitsByTeamID(e db.Engine, teamID int64) (units []*TeamUnit, err error) { return units, e.Where("team_id = ?", teamID).Find(&units) } // UpdateTeamUnits updates a teams's units func UpdateTeamUnits(team *Team, units []TeamUnit) (err error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err diff --git a/models/org_team_test.go b/models/org_team_test.go index 9dc26fd814a67..f1c8f88879324 100644 --- a/models/org_team_test.go +++ b/models/org_team_test.go @@ -8,42 +8,43 @@ import ( "strings" "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestTeam_IsOwnerTeam(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - team := AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team) + team := db.AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team) assert.True(t, team.IsOwnerTeam()) - team = AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team) + team = db.AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team) assert.False(t, team.IsOwnerTeam()) } func TestTeam_IsMember(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - team := AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team) + team := db.AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team) assert.True(t, team.IsMember(2)) assert.False(t, team.IsMember(4)) - assert.False(t, team.IsMember(NonexistentID)) + assert.False(t, team.IsMember(db.NonexistentID)) - team = AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team) + team = db.AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team) assert.True(t, team.IsMember(2)) assert.True(t, team.IsMember(4)) - assert.False(t, team.IsMember(NonexistentID)) + assert.False(t, team.IsMember(db.NonexistentID)) } func TestTeam_GetRepositories(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(teamID int64) { - team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) + team := db.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) assert.NoError(t, team.GetRepositories(&SearchTeamOptions{})) assert.Len(t, team.Repos, team.NumRepos) for _, repo := range team.Repos { - AssertExistsAndLoadBean(t, &TeamRepo{TeamID: teamID, RepoID: repo.ID}) + db.AssertExistsAndLoadBean(t, &TeamRepo{TeamID: teamID, RepoID: repo.ID}) } } test(1) @@ -51,14 +52,14 @@ func TestTeam_GetRepositories(t *testing.T) { } func TestTeam_GetMembers(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(teamID int64) { - team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) + team := db.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) assert.NoError(t, team.GetMembers(&SearchMembersOptions{})) assert.Len(t, team.Members, team.NumMembers) for _, member := range team.Members { - AssertExistsAndLoadBean(t, &TeamUser{UID: member.ID, TeamID: teamID}) + db.AssertExistsAndLoadBean(t, &TeamUser{UID: member.ID, TeamID: teamID}) } } test(1) @@ -66,12 +67,12 @@ func TestTeam_GetMembers(t *testing.T) { } func TestTeam_AddMember(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(teamID, userID int64) { - team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) + team := db.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) assert.NoError(t, team.AddMember(userID)) - AssertExistsAndLoadBean(t, &TeamUser{UID: userID, TeamID: teamID}) + db.AssertExistsAndLoadBean(t, &TeamUser{UID: userID, TeamID: teamID}) CheckConsistencyFor(t, &Team{ID: teamID}, &User{ID: team.OrgID}) } test(1, 2) @@ -80,71 +81,71 @@ func TestTeam_AddMember(t *testing.T) { } func TestTeam_RemoveMember(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testSuccess := func(teamID, userID int64) { - team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) + team := db.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) assert.NoError(t, team.RemoveMember(userID)) - AssertNotExistsBean(t, &TeamUser{UID: userID, TeamID: teamID}) + db.AssertNotExistsBean(t, &TeamUser{UID: userID, TeamID: teamID}) CheckConsistencyFor(t, &Team{ID: teamID}) } testSuccess(1, 4) testSuccess(2, 2) testSuccess(3, 2) - testSuccess(3, NonexistentID) + testSuccess(3, db.NonexistentID) - team := AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team) + team := db.AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team) err := team.RemoveMember(2) assert.True(t, IsErrLastOrgOwner(err)) } func TestTeam_HasRepository(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(teamID, repoID int64, expected bool) { - team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) + team := db.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) assert.Equal(t, expected, team.HasRepository(repoID)) } test(1, 1, false) test(1, 3, true) test(1, 5, true) - test(1, NonexistentID, false) + test(1, db.NonexistentID, false) test(2, 3, true) test(2, 5, false) } func TestTeam_AddRepository(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testSuccess := func(teamID, repoID int64) { - team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) - repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) + team := db.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) assert.NoError(t, team.AddRepository(repo)) - AssertExistsAndLoadBean(t, &TeamRepo{TeamID: teamID, RepoID: repoID}) + db.AssertExistsAndLoadBean(t, &TeamRepo{TeamID: teamID, RepoID: repoID}) CheckConsistencyFor(t, &Team{ID: teamID}, &Repository{ID: repoID}) } testSuccess(2, 3) testSuccess(2, 5) - team := AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + team := db.AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) assert.Error(t, team.AddRepository(repo)) CheckConsistencyFor(t, &Team{ID: 1}, &Repository{ID: 1}) } func TestTeam_RemoveRepository(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testSuccess := func(teamID, repoID int64) { - team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) + team := db.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) assert.NoError(t, team.RemoveRepository(repoID)) - AssertNotExistsBean(t, &TeamRepo{TeamID: teamID, RepoID: repoID}) + db.AssertNotExistsBean(t, &TeamRepo{TeamID: teamID, RepoID: repoID}) CheckConsistencyFor(t, &Team{ID: teamID}, &Repository{ID: repoID}) } testSuccess(2, 3) testSuccess(2, 5) - testSuccess(1, NonexistentID) + testSuccess(1, db.NonexistentID) } func TestIsUsableTeamName(t *testing.T) { @@ -153,17 +154,17 @@ func TestIsUsableTeamName(t *testing.T) { } func TestNewTeam(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) const teamName = "newTeamName" team := &Team{Name: teamName, OrgID: 3} assert.NoError(t, NewTeam(team)) - AssertExistsAndLoadBean(t, &Team{Name: teamName}) + db.AssertExistsAndLoadBean(t, &Team{Name: teamName}) CheckConsistencyFor(t, &Team{}, &User{ID: team.OrgID}) } func TestGetTeam(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testSuccess := func(orgID int64, name string) { team, err := GetTeam(orgID, name) @@ -176,12 +177,12 @@ func TestGetTeam(t *testing.T) { _, err := GetTeam(3, "nonexistent") assert.Error(t, err) - _, err = GetTeam(NonexistentID, "Owners") + _, err = GetTeam(db.NonexistentID, "Owners") assert.Error(t, err) } func TestGetTeamByID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testSuccess := func(teamID int64) { team, err := GetTeamByID(teamID) @@ -193,25 +194,25 @@ func TestGetTeamByID(t *testing.T) { testSuccess(3) testSuccess(4) - _, err := GetTeamByID(NonexistentID) + _, err := GetTeamByID(db.NonexistentID) assert.Error(t, err) } func TestUpdateTeam(t *testing.T) { // successful update - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - team := AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team) + team := db.AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team) team.LowerName = "newname" team.Name = "newName" team.Description = strings.Repeat("A long description!", 100) team.Authorize = AccessModeAdmin assert.NoError(t, UpdateTeam(team, true, false)) - team = AssertExistsAndLoadBean(t, &Team{Name: "newName"}).(*Team) + team = db.AssertExistsAndLoadBean(t, &Team{Name: "newName"}).(*Team) assert.True(t, strings.HasPrefix(team.Description, "A long description!")) - access := AssertExistsAndLoadBean(t, &Access{UserID: 4, RepoID: 3}).(*Access) + access := db.AssertExistsAndLoadBean(t, &Access{UserID: 4, RepoID: 3}).(*Access) assert.EqualValues(t, AccessModeAdmin, access.Mode) CheckConsistencyFor(t, &Team{ID: team.ID}) @@ -219,9 +220,9 @@ func TestUpdateTeam(t *testing.T) { func TestUpdateTeam2(t *testing.T) { // update to already-existing team - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - team := AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team) + team := db.AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team) team.LowerName = "owners" team.Name = "Owners" team.Description = strings.Repeat("A long description!", 100) @@ -232,24 +233,24 @@ func TestUpdateTeam2(t *testing.T) { } func TestDeleteTeam(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - team := AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team) + team := db.AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team) assert.NoError(t, DeleteTeam(team)) - AssertNotExistsBean(t, &Team{ID: team.ID}) - AssertNotExistsBean(t, &TeamRepo{TeamID: team.ID}) - AssertNotExistsBean(t, &TeamUser{TeamID: team.ID}) + db.AssertNotExistsBean(t, &Team{ID: team.ID}) + db.AssertNotExistsBean(t, &TeamRepo{TeamID: team.ID}) + db.AssertNotExistsBean(t, &TeamUser{TeamID: team.ID}) // check that team members don't have "leftover" access to repos - user := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) + user := db.AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) accessMode, err := AccessLevel(user, repo) assert.NoError(t, err) assert.True(t, accessMode < AccessModeWrite) } func TestIsTeamMember(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(orgID, teamID, userID int64, expected bool) { isMember, err := IsTeamMember(orgID, teamID, userID) assert.NoError(t, err) @@ -258,25 +259,25 @@ func TestIsTeamMember(t *testing.T) { test(3, 1, 2, true) test(3, 1, 4, false) - test(3, 1, NonexistentID, false) + test(3, 1, db.NonexistentID, false) test(3, 2, 2, true) test(3, 2, 4, true) - test(3, NonexistentID, NonexistentID, false) - test(NonexistentID, NonexistentID, NonexistentID, false) + test(3, db.NonexistentID, db.NonexistentID, false) + test(db.NonexistentID, db.NonexistentID, db.NonexistentID, false) } func TestGetTeamMembers(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(teamID int64) { - team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) + team := db.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) members, err := GetTeamMembers(teamID) assert.NoError(t, err) assert.Len(t, members, team.NumMembers) for _, member := range members { - AssertExistsAndLoadBean(t, &TeamUser{UID: member.ID, TeamID: teamID}) + db.AssertExistsAndLoadBean(t, &TeamUser{UID: member.ID, TeamID: teamID}) } } test(1) @@ -284,41 +285,41 @@ func TestGetTeamMembers(t *testing.T) { } func TestGetUserTeams(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(userID int64) { teams, _, err := SearchTeam(&SearchTeamOptions{UserID: userID}) assert.NoError(t, err) for _, team := range teams { - AssertExistsAndLoadBean(t, &TeamUser{TeamID: team.ID, UID: userID}) + db.AssertExistsAndLoadBean(t, &TeamUser{TeamID: team.ID, UID: userID}) } } test(2) test(5) - test(NonexistentID) + test(db.NonexistentID) } func TestGetUserOrgTeams(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(orgID, userID int64) { teams, err := GetUserOrgTeams(orgID, userID) assert.NoError(t, err) for _, team := range teams { assert.EqualValues(t, orgID, team.OrgID) - AssertExistsAndLoadBean(t, &TeamUser{TeamID: team.ID, UID: userID}) + db.AssertExistsAndLoadBean(t, &TeamUser{TeamID: team.ID, UID: userID}) } } test(3, 2) test(3, 4) - test(3, NonexistentID) + test(3, db.NonexistentID) } func TestAddTeamMember(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(teamID, userID int64) { - team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) + team := db.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) assert.NoError(t, AddTeamMember(team, userID)) - AssertExistsAndLoadBean(t, &TeamUser{UID: userID, TeamID: teamID}) + db.AssertExistsAndLoadBean(t, &TeamUser{UID: userID, TeamID: teamID}) CheckConsistencyFor(t, &Team{ID: teamID}, &User{ID: team.OrgID}) } test(1, 2) @@ -327,42 +328,42 @@ func TestAddTeamMember(t *testing.T) { } func TestRemoveTeamMember(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testSuccess := func(teamID, userID int64) { - team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) + team := db.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) assert.NoError(t, RemoveTeamMember(team, userID)) - AssertNotExistsBean(t, &TeamUser{UID: userID, TeamID: teamID}) + db.AssertNotExistsBean(t, &TeamUser{UID: userID, TeamID: teamID}) CheckConsistencyFor(t, &Team{ID: teamID}) } testSuccess(1, 4) testSuccess(2, 2) testSuccess(3, 2) - testSuccess(3, NonexistentID) + testSuccess(3, db.NonexistentID) - team := AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team) + team := db.AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team) err := RemoveTeamMember(team, 2) assert.True(t, IsErrLastOrgOwner(err)) } func TestHasTeamRepo(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(teamID, repoID int64, expected bool) { - team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) + team := db.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) assert.Equal(t, expected, HasTeamRepo(team.OrgID, teamID, repoID)) } test(1, 1, false) test(1, 3, true) test(1, 5, true) - test(1, NonexistentID, false) + test(1, db.NonexistentID, false) test(2, 3, true) test(2, 5, false) } func TestUsersInTeamsCount(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(teamIDs, userIDs []int64, expected int64) { count, err := UsersInTeamsCount(teamIDs, userIDs) diff --git a/models/org_test.go b/models/org_test.go index 7ba9d8ee88b8e..2df89b2afcac3 100644 --- a/models/org_test.go +++ b/models/org_test.go @@ -7,6 +7,7 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" @@ -14,7 +15,7 @@ import ( ) func TestUser_IsOwnedBy(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) for _, testCase := range []struct { OrgID int64 UserID int64 @@ -27,7 +28,7 @@ func TestUser_IsOwnedBy(t *testing.T) { {2, 2, false}, // user2 is not an organization {2, 3, false}, } { - org := AssertExistsAndLoadBean(t, &User{ID: testCase.OrgID}).(*User) + org := db.AssertExistsAndLoadBean(t, &User{ID: testCase.OrgID}).(*User) isOwner, err := org.IsOwnedBy(testCase.UserID) assert.NoError(t, err) assert.Equal(t, testCase.ExpectedOwner, isOwner) @@ -35,7 +36,7 @@ func TestUser_IsOwnedBy(t *testing.T) { } func TestUser_IsOrgMember(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) for _, testCase := range []struct { OrgID int64 UserID int64 @@ -48,7 +49,7 @@ func TestUser_IsOrgMember(t *testing.T) { {2, 2, false}, // user2 is not an organization {2, 3, false}, } { - org := AssertExistsAndLoadBean(t, &User{ID: testCase.OrgID}).(*User) + org := db.AssertExistsAndLoadBean(t, &User{ID: testCase.OrgID}).(*User) isMember, err := org.IsOrgMember(testCase.UserID) assert.NoError(t, err) assert.Equal(t, testCase.ExpectedMember, isMember) @@ -56,8 +57,8 @@ func TestUser_IsOrgMember(t *testing.T) { } func TestUser_GetTeam(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + org := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) team, err := org.GetTeam("team1") assert.NoError(t, err) assert.Equal(t, org.ID, team.OrgID) @@ -66,26 +67,26 @@ func TestUser_GetTeam(t *testing.T) { _, err = org.GetTeam("does not exist") assert.True(t, IsErrTeamNotExist(err)) - nonOrg := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + nonOrg := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) _, err = nonOrg.GetTeam("team") assert.True(t, IsErrTeamNotExist(err)) } func TestUser_GetOwnerTeam(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + org := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) team, err := org.GetOwnerTeam() assert.NoError(t, err) assert.Equal(t, org.ID, team.OrgID) - nonOrg := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + nonOrg := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) _, err = nonOrg.GetOwnerTeam() assert.True(t, IsErrTeamNotExist(err)) } func TestUser_GetTeams(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + org := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) assert.NoError(t, org.LoadTeams()) if assert.Len(t, org.Teams, 4) { assert.Equal(t, int64(1), org.Teams[0].ID) @@ -96,8 +97,8 @@ func TestUser_GetTeams(t *testing.T) { } func TestUser_GetMembers(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + org := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) assert.NoError(t, org.GetMembers()) if assert.Len(t, org.Members, 3) { assert.Equal(t, int64(2), org.Members[0].ID) @@ -107,67 +108,67 @@ func TestUser_GetMembers(t *testing.T) { } func TestUser_AddMember(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + org := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) // add a user that is not a member - AssertNotExistsBean(t, &OrgUser{UID: 5, OrgID: 3}) + db.AssertNotExistsBean(t, &OrgUser{UID: 5, OrgID: 3}) prevNumMembers := org.NumMembers assert.NoError(t, org.AddMember(5)) - AssertExistsAndLoadBean(t, &OrgUser{UID: 5, OrgID: 3}) - org = AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + db.AssertExistsAndLoadBean(t, &OrgUser{UID: 5, OrgID: 3}) + org = db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) assert.Equal(t, prevNumMembers+1, org.NumMembers) // add a user that is already a member - AssertExistsAndLoadBean(t, &OrgUser{UID: 4, OrgID: 3}) + db.AssertExistsAndLoadBean(t, &OrgUser{UID: 4, OrgID: 3}) prevNumMembers = org.NumMembers assert.NoError(t, org.AddMember(4)) - AssertExistsAndLoadBean(t, &OrgUser{UID: 4, OrgID: 3}) - org = AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + db.AssertExistsAndLoadBean(t, &OrgUser{UID: 4, OrgID: 3}) + org = db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) assert.Equal(t, prevNumMembers, org.NumMembers) CheckConsistencyFor(t, &User{}) } func TestUser_RemoveMember(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + org := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) // remove a user that is a member - AssertExistsAndLoadBean(t, &OrgUser{UID: 4, OrgID: 3}) + db.AssertExistsAndLoadBean(t, &OrgUser{UID: 4, OrgID: 3}) prevNumMembers := org.NumMembers assert.NoError(t, org.RemoveMember(4)) - AssertNotExistsBean(t, &OrgUser{UID: 4, OrgID: 3}) - org = AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + db.AssertNotExistsBean(t, &OrgUser{UID: 4, OrgID: 3}) + org = db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) assert.Equal(t, prevNumMembers-1, org.NumMembers) // remove a user that is not a member - AssertNotExistsBean(t, &OrgUser{UID: 5, OrgID: 3}) + db.AssertNotExistsBean(t, &OrgUser{UID: 5, OrgID: 3}) prevNumMembers = org.NumMembers assert.NoError(t, org.RemoveMember(5)) - AssertNotExistsBean(t, &OrgUser{UID: 5, OrgID: 3}) - org = AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + db.AssertNotExistsBean(t, &OrgUser{UID: 5, OrgID: 3}) + org = db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) assert.Equal(t, prevNumMembers, org.NumMembers) CheckConsistencyFor(t, &User{}, &Team{}) } func TestUser_RemoveOrgRepo(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) - repo := AssertExistsAndLoadBean(t, &Repository{OwnerID: org.ID}).(*Repository) + assert.NoError(t, db.PrepareTestDatabase()) + org := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + repo := db.AssertExistsAndLoadBean(t, &Repository{OwnerID: org.ID}).(*Repository) // remove a repo that does belong to org - AssertExistsAndLoadBean(t, &TeamRepo{RepoID: repo.ID, OrgID: org.ID}) + db.AssertExistsAndLoadBean(t, &TeamRepo{RepoID: repo.ID, OrgID: org.ID}) assert.NoError(t, org.RemoveOrgRepo(repo.ID)) - AssertNotExistsBean(t, &TeamRepo{RepoID: repo.ID, OrgID: org.ID}) - AssertExistsAndLoadBean(t, &Repository{ID: repo.ID}) // repo should still exist + db.AssertNotExistsBean(t, &TeamRepo{RepoID: repo.ID, OrgID: org.ID}) + db.AssertExistsAndLoadBean(t, &Repository{ID: repo.ID}) // repo should still exist // remove a repo that does not belong to org assert.NoError(t, org.RemoveOrgRepo(repo.ID)) - AssertNotExistsBean(t, &TeamRepo{RepoID: repo.ID, OrgID: org.ID}) + db.AssertNotExistsBean(t, &TeamRepo{RepoID: repo.ID, OrgID: org.ID}) - assert.NoError(t, org.RemoveOrgRepo(NonexistentID)) + assert.NoError(t, org.RemoveOrgRepo(db.NonexistentID)) CheckConsistencyFor(t, &User{ID: org.ID}, @@ -177,49 +178,49 @@ func TestUser_RemoveOrgRepo(t *testing.T) { func TestCreateOrganization(t *testing.T) { // successful creation of org - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + owner := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) const newOrgName = "neworg" org := &User{ Name: newOrgName, } - AssertNotExistsBean(t, &User{Name: newOrgName, Type: UserTypeOrganization}) + db.AssertNotExistsBean(t, &User{Name: newOrgName, Type: UserTypeOrganization}) assert.NoError(t, CreateOrganization(org, owner)) - org = AssertExistsAndLoadBean(t, + org = db.AssertExistsAndLoadBean(t, &User{Name: newOrgName, Type: UserTypeOrganization}).(*User) - ownerTeam := AssertExistsAndLoadBean(t, + ownerTeam := db.AssertExistsAndLoadBean(t, &Team{Name: ownerTeamName, OrgID: org.ID}).(*Team) - AssertExistsAndLoadBean(t, &TeamUser{UID: owner.ID, TeamID: ownerTeam.ID}) + db.AssertExistsAndLoadBean(t, &TeamUser{UID: owner.ID, TeamID: ownerTeam.ID}) CheckConsistencyFor(t, &User{}, &Team{}) } func TestCreateOrganization2(t *testing.T) { // unauthorized creation of org - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - owner := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) + owner := db.AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) const newOrgName = "neworg" org := &User{ Name: newOrgName, } - AssertNotExistsBean(t, &User{Name: newOrgName, Type: UserTypeOrganization}) + db.AssertNotExistsBean(t, &User{Name: newOrgName, Type: UserTypeOrganization}) err := CreateOrganization(org, owner) assert.Error(t, err) assert.True(t, IsErrUserNotAllowedCreateOrg(err)) - AssertNotExistsBean(t, &User{Name: newOrgName, Type: UserTypeOrganization}) + db.AssertNotExistsBean(t, &User{Name: newOrgName, Type: UserTypeOrganization}) CheckConsistencyFor(t, &User{}, &Team{}) } func TestCreateOrganization3(t *testing.T) { // create org with same name as existent org - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - org := &User{Name: "user3"} // should already exist - AssertExistsAndLoadBean(t, &User{Name: org.Name}) // sanity check + owner := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + org := &User{Name: "user3"} // should already exist + db.AssertExistsAndLoadBean(t, &User{Name: org.Name}) // sanity check err := CreateOrganization(org, owner) assert.Error(t, err) assert.True(t, IsErrUserAlreadyExist(err)) @@ -228,9 +229,9 @@ func TestCreateOrganization3(t *testing.T) { func TestCreateOrganization4(t *testing.T) { // create org with unusable name - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + owner := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) err := CreateOrganization(&User{Name: "assets"}, owner) assert.Error(t, err) assert.True(t, IsErrNameReserved(err)) @@ -238,7 +239,7 @@ func TestCreateOrganization4(t *testing.T) { } func TestGetOrgByName(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) org, err := GetOrgByName("user3") assert.NoError(t, err) @@ -253,32 +254,32 @@ func TestGetOrgByName(t *testing.T) { } func TestCountOrganizations(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - expected, err := x.Where("type=?", UserTypeOrganization).Count(&User{}) + assert.NoError(t, db.PrepareTestDatabase()) + expected, err := db.GetEngine(db.DefaultContext).Where("type=?", UserTypeOrganization).Count(&User{}) assert.NoError(t, err) assert.Equal(t, expected, CountOrganizations()) } func TestDeleteOrganization(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - org := AssertExistsAndLoadBean(t, &User{ID: 6}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + org := db.AssertExistsAndLoadBean(t, &User{ID: 6}).(*User) assert.NoError(t, DeleteOrganization(org)) - AssertNotExistsBean(t, &User{ID: 6}) - AssertNotExistsBean(t, &OrgUser{OrgID: 6}) - AssertNotExistsBean(t, &Team{OrgID: 6}) + db.AssertNotExistsBean(t, &User{ID: 6}) + db.AssertNotExistsBean(t, &OrgUser{OrgID: 6}) + db.AssertNotExistsBean(t, &Team{OrgID: 6}) - org = AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + org = db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) err := DeleteOrganization(org) assert.Error(t, err) assert.True(t, IsErrUserOwnRepos(err)) - user := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) + user := db.AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) assert.Error(t, DeleteOrganization(user)) CheckConsistencyFor(t, &User{}, &Team{}) } func TestIsOrganizationOwner(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(orgID, userID int64, expected bool) { isOwner, err := IsOrganizationOwner(orgID, userID) assert.NoError(t, err) @@ -288,11 +289,11 @@ func TestIsOrganizationOwner(t *testing.T) { test(3, 3, false) test(6, 5, true) test(6, 4, false) - test(NonexistentID, NonexistentID, false) + test(db.NonexistentID, db.NonexistentID, false) } func TestIsOrganizationMember(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(orgID, userID int64, expected bool) { isMember, err := IsOrganizationMember(orgID, userID) assert.NoError(t, err) @@ -303,11 +304,11 @@ func TestIsOrganizationMember(t *testing.T) { test(3, 4, true) test(6, 5, true) test(6, 4, false) - test(NonexistentID, NonexistentID, false) + test(db.NonexistentID, db.NonexistentID, false) } func TestIsPublicMembership(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(orgID, userID int64, expected bool) { isMember, err := IsPublicMembership(orgID, userID) assert.NoError(t, err) @@ -318,11 +319,11 @@ func TestIsPublicMembership(t *testing.T) { test(3, 4, false) test(6, 5, true) test(6, 4, false) - test(NonexistentID, NonexistentID, false) + test(db.NonexistentID, db.NonexistentID, false) } func TestGetOrgsByUserID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) orgs, err := GetOrgsByUserID(4, true) assert.NoError(t, err) @@ -336,7 +337,7 @@ func TestGetOrgsByUserID(t *testing.T) { } func TestGetOwnedOrgsByUserID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) orgs, err := GetOwnedOrgsByUserID(2) assert.NoError(t, err) @@ -350,7 +351,7 @@ func TestGetOwnedOrgsByUserID(t *testing.T) { } func TestGetOwnedOrgsByUserIDDesc(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) orgs, err := GetOwnedOrgsByUserIDDesc(5, "id") assert.NoError(t, err) @@ -365,7 +366,7 @@ func TestGetOwnedOrgsByUserIDDesc(t *testing.T) { } func TestGetOrgUsersByUserID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) orgUsers, err := GetOrgUsersByUserID(5, &SearchOrganizationsOptions{All: true}) assert.NoError(t, err) @@ -395,10 +396,10 @@ func TestGetOrgUsersByUserID(t *testing.T) { } func TestGetOrgUsersByOrgID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) orgUsers, err := GetOrgUsersByOrgID(&FindOrgMembersOpts{ - ListOptions: ListOptions{}, + ListOptions: db.ListOptions{}, OrgID: 3, PublicOnly: false, }) @@ -419,8 +420,8 @@ func TestGetOrgUsersByOrgID(t *testing.T) { } orgUsers, err = GetOrgUsersByOrgID(&FindOrgMembersOpts{ - ListOptions: ListOptions{}, - OrgID: NonexistentID, + ListOptions: db.ListOptions{}, + OrgID: db.NonexistentID, PublicOnly: false, }) assert.NoError(t, err) @@ -428,33 +429,33 @@ func TestGetOrgUsersByOrgID(t *testing.T) { } func TestChangeOrgUserStatus(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testSuccess := func(orgID, userID int64, public bool) { assert.NoError(t, ChangeOrgUserStatus(orgID, userID, public)) - orgUser := AssertExistsAndLoadBean(t, &OrgUser{OrgID: orgID, UID: userID}).(*OrgUser) + orgUser := db.AssertExistsAndLoadBean(t, &OrgUser{OrgID: orgID, UID: userID}).(*OrgUser) assert.Equal(t, public, orgUser.IsPublic) } testSuccess(3, 2, false) testSuccess(3, 2, false) testSuccess(3, 4, true) - assert.NoError(t, ChangeOrgUserStatus(NonexistentID, NonexistentID, true)) + assert.NoError(t, ChangeOrgUserStatus(db.NonexistentID, db.NonexistentID, true)) } func TestAddOrgUser(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testSuccess := func(orgID, userID int64, isPublic bool) { - org := AssertExistsAndLoadBean(t, &User{ID: orgID}).(*User) + org := db.AssertExistsAndLoadBean(t, &User{ID: orgID}).(*User) expectedNumMembers := org.NumMembers - if !BeanExists(t, &OrgUser{OrgID: orgID, UID: userID}) { + if !db.BeanExists(t, &OrgUser{OrgID: orgID, UID: userID}) { expectedNumMembers++ } assert.NoError(t, AddOrgUser(orgID, userID)) ou := &OrgUser{OrgID: orgID, UID: userID} - AssertExistsAndLoadBean(t, ou) + db.AssertExistsAndLoadBean(t, ou) assert.Equal(t, isPublic, ou.IsPublic) - org = AssertExistsAndLoadBean(t, &User{ID: orgID}).(*User) + org = db.AssertExistsAndLoadBean(t, &User{ID: orgID}).(*User) assert.EqualValues(t, expectedNumMembers, org.NumMembers) } @@ -470,16 +471,16 @@ func TestAddOrgUser(t *testing.T) { } func TestRemoveOrgUser(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testSuccess := func(orgID, userID int64) { - org := AssertExistsAndLoadBean(t, &User{ID: orgID}).(*User) + org := db.AssertExistsAndLoadBean(t, &User{ID: orgID}).(*User) expectedNumMembers := org.NumMembers - if BeanExists(t, &OrgUser{OrgID: orgID, UID: userID}) { + if db.BeanExists(t, &OrgUser{OrgID: orgID, UID: userID}) { expectedNumMembers-- } assert.NoError(t, RemoveOrgUser(orgID, userID)) - AssertNotExistsBean(t, &OrgUser{OrgID: orgID, UID: userID}) - org = AssertExistsAndLoadBean(t, &User{ID: orgID}).(*User) + db.AssertNotExistsBean(t, &OrgUser{OrgID: orgID, UID: userID}) + org = db.AssertExistsAndLoadBean(t, &User{ID: orgID}).(*User) assert.EqualValues(t, expectedNumMembers, org.NumMembers) } testSuccess(3, 4) @@ -488,13 +489,13 @@ func TestRemoveOrgUser(t *testing.T) { err := RemoveOrgUser(7, 5) assert.Error(t, err) assert.True(t, IsErrLastOrgOwner(err)) - AssertExistsAndLoadBean(t, &OrgUser{OrgID: 7, UID: 5}) + db.AssertExistsAndLoadBean(t, &OrgUser{OrgID: 7, UID: 5}) CheckConsistencyFor(t, &User{}, &Team{}) } func TestUser_GetUserTeamIDs(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + org := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) testSuccess := func(userID int64, expected []int64) { teamIDs, err := org.GetUserTeamIDs(userID) assert.NoError(t, err) @@ -502,12 +503,12 @@ func TestUser_GetUserTeamIDs(t *testing.T) { } testSuccess(2, []int64{1, 2}) testSuccess(4, []int64{2}) - testSuccess(NonexistentID, []int64{}) + testSuccess(db.NonexistentID, []int64{}) } func TestAccessibleReposEnv_CountRepos(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + org := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) testSuccess := func(userID, expectedCount int64) { env, err := org.AccessibleReposEnv(userID) assert.NoError(t, err) @@ -520,8 +521,8 @@ func TestAccessibleReposEnv_CountRepos(t *testing.T) { } func TestAccessibleReposEnv_RepoIDs(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + org := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) testSuccess := func(userID, _, pageSize int64, expectedRepoIDs []int64) { env, err := org.AccessibleReposEnv(userID) assert.NoError(t, err) @@ -534,8 +535,8 @@ func TestAccessibleReposEnv_RepoIDs(t *testing.T) { } func TestAccessibleReposEnv_Repos(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + org := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) testSuccess := func(userID int64, expectedRepoIDs []int64) { env, err := org.AccessibleReposEnv(userID) assert.NoError(t, err) @@ -543,7 +544,7 @@ func TestAccessibleReposEnv_Repos(t *testing.T) { assert.NoError(t, err) expectedRepos := make([]*Repository, len(expectedRepoIDs)) for i, repoID := range expectedRepoIDs { - expectedRepos[i] = AssertExistsAndLoadBean(t, + expectedRepos[i] = db.AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) } assert.Equal(t, expectedRepos, repos) @@ -553,8 +554,8 @@ func TestAccessibleReposEnv_Repos(t *testing.T) { } func TestAccessibleReposEnv_MirrorRepos(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + org := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) testSuccess := func(userID int64, expectedRepoIDs []int64) { env, err := org.AccessibleReposEnv(userID) assert.NoError(t, err) @@ -562,7 +563,7 @@ func TestAccessibleReposEnv_MirrorRepos(t *testing.T) { assert.NoError(t, err) expectedRepos := make([]*Repository, len(expectedRepoIDs)) for i, repoID := range expectedRepoIDs { - expectedRepos[i] = AssertExistsAndLoadBean(t, + expectedRepos[i] = db.AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) } assert.Equal(t, expectedRepos, repos) @@ -572,9 +573,9 @@ func TestAccessibleReposEnv_MirrorRepos(t *testing.T) { } func TestHasOrgVisibleTypePublic(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - user3 := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + owner := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + user3 := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) const newOrgName = "test-org-public" org := &User{ @@ -582,9 +583,9 @@ func TestHasOrgVisibleTypePublic(t *testing.T) { Visibility: structs.VisibleTypePublic, } - AssertNotExistsBean(t, &User{Name: org.Name, Type: UserTypeOrganization}) + db.AssertNotExistsBean(t, &User{Name: org.Name, Type: UserTypeOrganization}) assert.NoError(t, CreateOrganization(org, owner)) - org = AssertExistsAndLoadBean(t, + org = db.AssertExistsAndLoadBean(t, &User{Name: org.Name, Type: UserTypeOrganization}).(*User) test1 := HasOrgOrUserVisible(org, owner) test2 := HasOrgOrUserVisible(org, user3) @@ -595,9 +596,9 @@ func TestHasOrgVisibleTypePublic(t *testing.T) { } func TestHasOrgVisibleTypeLimited(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - user3 := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + owner := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + user3 := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) const newOrgName = "test-org-limited" org := &User{ @@ -605,9 +606,9 @@ func TestHasOrgVisibleTypeLimited(t *testing.T) { Visibility: structs.VisibleTypeLimited, } - AssertNotExistsBean(t, &User{Name: org.Name, Type: UserTypeOrganization}) + db.AssertNotExistsBean(t, &User{Name: org.Name, Type: UserTypeOrganization}) assert.NoError(t, CreateOrganization(org, owner)) - org = AssertExistsAndLoadBean(t, + org = db.AssertExistsAndLoadBean(t, &User{Name: org.Name, Type: UserTypeOrganization}).(*User) test1 := HasOrgOrUserVisible(org, owner) test2 := HasOrgOrUserVisible(org, user3) @@ -618,9 +619,9 @@ func TestHasOrgVisibleTypeLimited(t *testing.T) { } func TestHasOrgVisibleTypePrivate(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - user3 := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + owner := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + user3 := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) const newOrgName = "test-org-private" org := &User{ @@ -628,9 +629,9 @@ func TestHasOrgVisibleTypePrivate(t *testing.T) { Visibility: structs.VisibleTypePrivate, } - AssertNotExistsBean(t, &User{Name: org.Name, Type: UserTypeOrganization}) + db.AssertNotExistsBean(t, &User{Name: org.Name, Type: UserTypeOrganization}) assert.NoError(t, CreateOrganization(org, owner)) - org = AssertExistsAndLoadBean(t, + org = db.AssertExistsAndLoadBean(t, &User{Name: org.Name, Type: UserTypeOrganization}).(*User) test1 := HasOrgOrUserVisible(org, owner) test2 := HasOrgOrUserVisible(org, user3) @@ -641,7 +642,7 @@ func TestHasOrgVisibleTypePrivate(t *testing.T) { } func TestGetUsersWhoCanCreateOrgRepo(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) users, err := GetUsersWhoCanCreateOrgRepo(3) assert.NoError(t, err) diff --git a/models/project.go b/models/project.go index 7c976711be870..8aaff50e151ba 100644 --- a/models/project.go +++ b/models/project.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -55,6 +56,10 @@ type Project struct { ClosedDateUnix timeutil.TimeStamp } +func init() { + db.RegisterModel(new(Project)) +} + // GetProjectsConfig retrieves the types of configurations projects could have func GetProjectsConfig() []ProjectsConfig { return []ProjectsConfig{ @@ -85,10 +90,10 @@ type ProjectSearchOptions struct { // GetProjects returns a list of all projects that have been created in the repository func GetProjects(opts ProjectSearchOptions) ([]*Project, int64, error) { - return getProjects(x, opts) + return getProjects(db.GetEngine(db.DefaultContext), opts) } -func getProjects(e Engine, opts ProjectSearchOptions) ([]*Project, int64, error) { +func getProjects(e db.Engine, opts ProjectSearchOptions) ([]*Project, int64, error) { projects := make([]*Project, 0, setting.UI.IssuePagingNum) var cond builder.Cond = builder.Eq{"repo_id": opts.RepoID} @@ -138,7 +143,7 @@ func NewProject(p *Project) error { return errors.New("project type is not valid") } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { @@ -162,10 +167,10 @@ func NewProject(p *Project) error { // GetProjectByID returns the projects in a repository func GetProjectByID(id int64) (*Project, error) { - return getProjectByID(x, id) + return getProjectByID(db.GetEngine(db.DefaultContext), id) } -func getProjectByID(e Engine, id int64) (*Project, error) { +func getProjectByID(e db.Engine, id int64) (*Project, error) { p := new(Project) has, err := e.ID(id).Get(p) @@ -180,10 +185,10 @@ func getProjectByID(e Engine, id int64) (*Project, error) { // UpdateProject updates project properties func UpdateProject(p *Project) error { - return updateProject(x, p) + return updateProject(db.GetEngine(db.DefaultContext), p) } -func updateProject(e Engine, p *Project) error { +func updateProject(e db.Engine, p *Project) error { _, err := e.ID(p.ID).Cols( "title", "description", @@ -191,7 +196,7 @@ func updateProject(e Engine, p *Project) error { return err } -func updateRepositoryProjectCount(e Engine, repoID int64) error { +func updateRepositoryProjectCount(e db.Engine, repoID int64) error { if _, err := e.Exec(builder.Update( builder.Eq{ "`num_projects`": builder.Select("count(*)").From("`project`"). @@ -215,7 +220,7 @@ func updateRepositoryProjectCount(e Engine, repoID int64) error { // ChangeProjectStatusByRepoIDAndID toggles a project between opened and closed func ChangeProjectStatusByRepoIDAndID(repoID, projectID int64, isClosed bool) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -239,7 +244,7 @@ func ChangeProjectStatusByRepoIDAndID(repoID, projectID int64, isClosed bool) er // ChangeProjectStatus toggle a project between opened and closed func ChangeProjectStatus(p *Project, isClosed bool) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -252,7 +257,7 @@ func ChangeProjectStatus(p *Project, isClosed bool) error { return sess.Commit() } -func changeProjectStatus(e Engine, p *Project, isClosed bool) error { +func changeProjectStatus(e db.Engine, p *Project, isClosed bool) error { p.IsClosed = isClosed p.ClosedDateUnix = timeutil.TimeStampNow() count, err := e.ID(p.ID).Where("repo_id = ? AND is_closed = ?", p.RepoID, !isClosed).Cols("is_closed", "closed_date_unix").Update(p) @@ -268,7 +273,7 @@ func changeProjectStatus(e Engine, p *Project, isClosed bool) error { // DeleteProjectByID deletes a project from a repository. func DeleteProjectByID(id int64) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -281,7 +286,7 @@ func DeleteProjectByID(id int64) error { return sess.Commit() } -func deleteProjectByID(e Engine, id int64) error { +func deleteProjectByID(e db.Engine, id int64) error { p, err := getProjectByID(e, id) if err != nil { if IsErrProjectNotExist(err) { diff --git a/models/project_board.go b/models/project_board.go index 4b313ed8f0cb8..e6c9f0338632b 100644 --- a/models/project_board.go +++ b/models/project_board.go @@ -5,6 +5,10 @@ package models import ( + "fmt" + "regexp" + + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" @@ -31,12 +35,16 @@ const ( ProjectBoardTypeBugTriage ) +// BoardColorPattern is a regexp witch can validate BoardColor +var BoardColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$") + // ProjectBoard is used to represent boards on a project type ProjectBoard struct { ID int64 `xorm:"pk autoincr"` Title string - Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board - Sorting int8 `xorm:"NOT NULL DEFAULT 0"` + Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board + Sorting int8 `xorm:"NOT NULL DEFAULT 0"` + Color string `xorm:"VARCHAR(7)"` ProjectID int64 `xorm:"INDEX NOT NULL"` CreatorID int64 `xorm:"NOT NULL"` @@ -47,6 +55,10 @@ type ProjectBoard struct { Issues []*Issue `xorm:"-"` } +func init() { + db.RegisterModel(new(ProjectBoard)) +} + // IsProjectBoardTypeValid checks if the project board type is valid func IsProjectBoardTypeValid(p ProjectBoardType) bool { switch p { @@ -95,13 +107,17 @@ func createBoardsForProjectsType(sess *xorm.Session, project *Project) error { // NewProjectBoard adds a new project board to a given project func NewProjectBoard(board *ProjectBoard) error { - _, err := x.Insert(board) + if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) { + return fmt.Errorf("bad color code: %s", board.Color) + } + + _, err := db.GetEngine(db.DefaultContext).Insert(board) return err } // DeleteProjectBoardByID removes all issues references to the project board. func DeleteProjectBoardByID(boardID int64) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -114,7 +130,7 @@ func DeleteProjectBoardByID(boardID int64) error { return sess.Commit() } -func deleteProjectBoardByID(e Engine, boardID int64) error { +func deleteProjectBoardByID(e db.Engine, boardID int64) error { board, err := getProjectBoard(e, boardID) if err != nil { if IsErrProjectBoardNotExist(err) { @@ -134,17 +150,17 @@ func deleteProjectBoardByID(e Engine, boardID int64) error { return nil } -func deleteProjectBoardByProjectID(e Engine, projectID int64) error { +func deleteProjectBoardByProjectID(e db.Engine, projectID int64) error { _, err := e.Where("project_id=?", projectID).Delete(&ProjectBoard{}) return err } // GetProjectBoard fetches the current board of a project func GetProjectBoard(boardID int64) (*ProjectBoard, error) { - return getProjectBoard(x, boardID) + return getProjectBoard(db.GetEngine(db.DefaultContext), boardID) } -func getProjectBoard(e Engine, boardID int64) (*ProjectBoard, error) { +func getProjectBoard(e db.Engine, boardID int64) (*ProjectBoard, error) { board := new(ProjectBoard) has, err := e.ID(boardID).Get(board) @@ -159,10 +175,10 @@ func getProjectBoard(e Engine, boardID int64) (*ProjectBoard, error) { // UpdateProjectBoard updates a project board func UpdateProjectBoard(board *ProjectBoard) error { - return updateProjectBoard(x, board) + return updateProjectBoard(db.GetEngine(db.DefaultContext), board) } -func updateProjectBoard(e Engine, board *ProjectBoard) error { +func updateProjectBoard(e db.Engine, board *ProjectBoard) error { var fieldToUpdate []string if board.Sorting != 0 { @@ -173,6 +189,11 @@ func updateProjectBoard(e Engine, board *ProjectBoard) error { fieldToUpdate = append(fieldToUpdate, "title") } + if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) { + return fmt.Errorf("bad color code: %s", board.Color) + } + fieldToUpdate = append(fieldToUpdate, "color") + _, err := e.ID(board.ID).Cols(fieldToUpdate...).Update(board) return err @@ -181,10 +202,10 @@ func updateProjectBoard(e Engine, board *ProjectBoard) error { // GetProjectBoards fetches all boards related to a project // if no default board set, first board is a temporary "Uncategorized" board func GetProjectBoards(projectID int64) (ProjectBoardList, error) { - return getProjectBoards(x, projectID) + return getProjectBoards(db.GetEngine(db.DefaultContext), projectID) } -func getProjectBoards(e Engine, projectID int64) ([]*ProjectBoard, error) { +func getProjectBoards(e db.Engine, projectID int64) ([]*ProjectBoard, error) { boards := make([]*ProjectBoard, 0, 5) if err := e.Where("project_id=? AND `default`=?", projectID, false).OrderBy("Sorting").Find(&boards); err != nil { @@ -200,7 +221,7 @@ func getProjectBoards(e Engine, projectID int64) ([]*ProjectBoard, error) { } // getDefaultBoard return default board and create a dummy if none exist -func getDefaultBoard(e Engine, projectID int64) (*ProjectBoard, error) { +func getDefaultBoard(e db.Engine, projectID int64) (*ProjectBoard, error) { var board ProjectBoard exist, err := e.Where("project_id=? AND `default`=?", projectID, true).Get(&board) if err != nil { @@ -221,9 +242,7 @@ func getDefaultBoard(e Engine, projectID int64) (*ProjectBoard, error) { // SetDefaultBoard represents a board for issues not assigned to one // if boardID is 0 unset default func SetDefaultBoard(projectID, boardID int64) error { - sess := x - - _, err := sess.Where(builder.Eq{ + _, err := db.GetEngine(db.DefaultContext).Where(builder.Eq{ "project_id": projectID, "`default`": true, }).Cols("`default`").Update(&ProjectBoard{Default: false}) @@ -232,7 +251,7 @@ func SetDefaultBoard(projectID, boardID int64) error { } if boardID > 0 { - _, err = sess.ID(boardID).Where(builder.Eq{"project_id": projectID}). + _, err = db.GetEngine(db.DefaultContext).ID(boardID).Where(builder.Eq{"project_id": projectID}). Cols("`default`").Update(&ProjectBoard{Default: true}) } @@ -290,7 +309,7 @@ func (bs ProjectBoardList) LoadIssues() (IssueList, error) { // UpdateProjectBoardSorting update project board sorting func UpdateProjectBoardSorting(bs ProjectBoardList) error { for i := range bs { - _, err := x.ID(bs[i].ID).Cols( + _, err := db.GetEngine(db.DefaultContext).ID(bs[i].ID).Cols( "sorting", ).Update(bs[i]) if err != nil { diff --git a/models/project_issue.go b/models/project_issue.go index e35307158d32b..a3179507dc9ad 100644 --- a/models/project_issue.go +++ b/models/project_issue.go @@ -7,6 +7,8 @@ package models import ( "fmt" + "code.gitea.io/gitea/models/db" + "xorm.io/xorm" ) @@ -20,7 +22,11 @@ type ProjectIssue struct { ProjectBoardID int64 `xorm:"INDEX"` } -func deleteProjectIssuesByProjectID(e Engine, projectID int64) error { +func init() { + db.RegisterModel(new(ProjectIssue)) +} + +func deleteProjectIssuesByProjectID(e db.Engine, projectID int64) error { _, err := e.Where("project_id=?", projectID).Delete(&ProjectIssue{}) return err } @@ -33,10 +39,10 @@ func deleteProjectIssuesByProjectID(e Engine, projectID int64) error { // LoadProject load the project the issue was assigned to func (i *Issue) LoadProject() (err error) { - return i.loadProject(x) + return i.loadProject(db.GetEngine(db.DefaultContext)) } -func (i *Issue) loadProject(e Engine) (err error) { +func (i *Issue) loadProject(e db.Engine) (err error) { if i.Project == nil { var p Project if _, err = e.Table("project"). @@ -52,10 +58,10 @@ func (i *Issue) loadProject(e Engine) (err error) { // ProjectID return project id if issue was assigned to one func (i *Issue) ProjectID() int64 { - return i.projectID(x) + return i.projectID(db.GetEngine(db.DefaultContext)) } -func (i *Issue) projectID(e Engine) int64 { +func (i *Issue) projectID(e db.Engine) int64 { var ip ProjectIssue has, err := e.Where("issue_id=?", i.ID).Get(&ip) if err != nil || !has { @@ -66,10 +72,10 @@ func (i *Issue) projectID(e Engine) int64 { // ProjectBoardID return project board id if issue was assigned to one func (i *Issue) ProjectBoardID() int64 { - return i.projectBoardID(x) + return i.projectBoardID(db.GetEngine(db.DefaultContext)) } -func (i *Issue) projectBoardID(e Engine) int64 { +func (i *Issue) projectBoardID(e db.Engine) int64 { var ip ProjectIssue has, err := e.Where("issue_id=?", i.ID).Get(&ip) if err != nil || !has { @@ -87,7 +93,7 @@ func (i *Issue) projectBoardID(e Engine) int64 { // NumIssues return counter of all issues assigned to a project func (p *Project) NumIssues() int { - c, err := x.Table("project_issue"). + c, err := db.GetEngine(db.DefaultContext).Table("project_issue"). Where("project_id=?", p.ID). GroupBy("issue_id"). Cols("issue_id"). @@ -100,7 +106,7 @@ func (p *Project) NumIssues() int { // NumClosedIssues return counter of closed issues assigned to a project func (p *Project) NumClosedIssues() int { - c, err := x.Table("project_issue"). + c, err := db.GetEngine(db.DefaultContext).Table("project_issue"). Join("INNER", "issue", "project_issue.issue_id=issue.id"). Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, true). Cols("issue_id"). @@ -113,7 +119,7 @@ func (p *Project) NumClosedIssues() int { // NumOpenIssues return counter of open issues assigned to a project func (p *Project) NumOpenIssues() int { - c, err := x.Table("project_issue"). + c, err := db.GetEngine(db.DefaultContext).Table("project_issue"). Join("INNER", "issue", "project_issue.issue_id=issue.id"). Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false).Count("issue.id") if err != nil { @@ -124,7 +130,7 @@ func (p *Project) NumOpenIssues() int { // ChangeProjectAssign changes the project associated with an issue func ChangeProjectAssign(issue *Issue, doer *User, newProjectID int64) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -177,7 +183,7 @@ func addUpdateIssueProject(e *xorm.Session, issue *Issue, doer *User, newProject // MoveIssueAcrossProjectBoards move a card from one board to another func MoveIssueAcrossProjectBoards(issue *Issue, board *ProjectBoard) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -201,7 +207,7 @@ func MoveIssueAcrossProjectBoards(issue *Issue, board *ProjectBoard) error { return sess.Commit() } -func (pb *ProjectBoard) removeIssues(e Engine) error { +func (pb *ProjectBoard) removeIssues(e db.Engine) error { _, err := e.Exec("UPDATE `project_issue` SET project_board_id = 0 WHERE project_board_id = ? ", pb.ID) return err } diff --git a/models/project_test.go b/models/project_test.go index 23ad56eb3207a..8c630d8bedd5f 100644 --- a/models/project_test.go +++ b/models/project_test.go @@ -7,6 +7,7 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" "github.com/stretchr/testify/assert" @@ -31,7 +32,7 @@ func TestIsProjectTypeValid(t *testing.T) { } func TestGetProjects(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) projects, _, err := GetProjects(ProjectSearchOptions{RepoID: 1}) assert.NoError(t, err) @@ -47,7 +48,7 @@ func TestGetProjects(t *testing.T) { } func TestProject(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) project := &Project{ Type: ProjectTypeRepository, diff --git a/models/protected_tag.go b/models/protected_tag.go index 88f20dd29a865..93318300e8845 100644 --- a/models/protected_tag.go +++ b/models/protected_tag.go @@ -8,6 +8,7 @@ import ( "regexp" "strings" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/timeutil" @@ -28,21 +29,25 @@ type ProtectedTag struct { UpdatedUnix timeutil.TimeStamp `xorm:"updated"` } +func init() { + db.RegisterModel(new(ProtectedTag)) +} + // InsertProtectedTag inserts a protected tag to database func InsertProtectedTag(pt *ProtectedTag) error { - _, err := x.Insert(pt) + _, err := db.GetEngine(db.DefaultContext).Insert(pt) return err } // UpdateProtectedTag updates the protected tag func UpdateProtectedTag(pt *ProtectedTag) error { - _, err := x.ID(pt.ID).AllCols().Update(pt) + _, err := db.GetEngine(db.DefaultContext).ID(pt.ID).AllCols().Update(pt) return err } // DeleteProtectedTag deletes a protected tag by ID func DeleteProtectedTag(pt *ProtectedTag) error { - _, err := x.ID(pt.ID).Delete(&ProtectedTag{}) + _, err := db.GetEngine(db.DefaultContext).ID(pt.ID).Delete(&ProtectedTag{}) return err } @@ -81,13 +86,13 @@ func (pt *ProtectedTag) IsUserAllowed(userID int64) (bool, error) { // GetProtectedTags gets all protected tags of the repository func (repo *Repository) GetProtectedTags() ([]*ProtectedTag, error) { tags := make([]*ProtectedTag, 0) - return tags, x.Find(&tags, &ProtectedTag{RepoID: repo.ID}) + return tags, db.GetEngine(db.DefaultContext).Find(&tags, &ProtectedTag{RepoID: repo.ID}) } // GetProtectedTagByID gets the protected tag with the specific id func GetProtectedTagByID(id int64) (*ProtectedTag, error) { tag := new(ProtectedTag) - has, err := x.ID(id).Get(tag) + has, err := db.GetEngine(db.DefaultContext).ID(id).Get(tag) if err != nil { return nil, err } diff --git a/models/protected_tag_test.go b/models/protected_tag_test.go index 3dc895c69fe11..fd29f7e64b2c9 100644 --- a/models/protected_tag_test.go +++ b/models/protected_tag_test.go @@ -7,11 +7,12 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestIsUserAllowed(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) pt := &ProtectedTag{} allowed, err := pt.IsUserAllowed(1) diff --git a/models/pull.go b/models/pull.go index ea8f2899932f2..38f2c1b8ce414 100644 --- a/models/pull.go +++ b/models/pull.go @@ -10,6 +10,7 @@ import ( "io" "strings" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" @@ -84,6 +85,10 @@ type PullRequest struct { Flow PullRequestFlow `xorm:"NOT NULL DEFAULT 0"` } +func init() { + db.RegisterModel(new(PullRequest)) +} + // MustHeadUserName returns the HeadRepo's username if failed return blank func (pr *PullRequest) MustHeadUserName() string { if err := pr.LoadHeadRepo(); err != nil { @@ -101,7 +106,7 @@ func (pr *PullRequest) MustHeadUserName() string { } // Note: don't try to get Issue because will end up recursive querying. -func (pr *PullRequest) loadAttributes(e Engine) (err error) { +func (pr *PullRequest) loadAttributes(e db.Engine) (err error) { if pr.HasMerged && pr.Merger == nil { pr.Merger, err = getUserByID(e, pr.MergerID) if IsErrUserNotExist(err) { @@ -117,10 +122,10 @@ func (pr *PullRequest) loadAttributes(e Engine) (err error) { // LoadAttributes loads pull request attributes from database func (pr *PullRequest) LoadAttributes() error { - return pr.loadAttributes(x) + return pr.loadAttributes(db.GetEngine(db.DefaultContext)) } -func (pr *PullRequest) loadHeadRepo(e Engine) (err error) { +func (pr *PullRequest) loadHeadRepo(e db.Engine) (err error) { if !pr.isHeadRepoLoaded && pr.HeadRepo == nil && pr.HeadRepoID > 0 { if pr.HeadRepoID == pr.BaseRepoID { if pr.BaseRepo != nil { @@ -143,15 +148,15 @@ func (pr *PullRequest) loadHeadRepo(e Engine) (err error) { // LoadHeadRepo loads the head repository func (pr *PullRequest) LoadHeadRepo() error { - return pr.loadHeadRepo(x) + return pr.loadHeadRepo(db.GetEngine(db.DefaultContext)) } // LoadBaseRepo loads the target repository func (pr *PullRequest) LoadBaseRepo() error { - return pr.loadBaseRepo(x) + return pr.loadBaseRepo(db.GetEngine(db.DefaultContext)) } -func (pr *PullRequest) loadBaseRepo(e Engine) (err error) { +func (pr *PullRequest) loadBaseRepo(e db.Engine) (err error) { if pr.BaseRepo != nil { return nil } @@ -175,10 +180,10 @@ func (pr *PullRequest) loadBaseRepo(e Engine) (err error) { // LoadIssue loads issue information from database func (pr *PullRequest) LoadIssue() (err error) { - return pr.loadIssue(x) + return pr.loadIssue(db.GetEngine(db.DefaultContext)) } -func (pr *PullRequest) loadIssue(e Engine) (err error) { +func (pr *PullRequest) loadIssue(e db.Engine) (err error) { if pr.Issue != nil { return nil } @@ -192,10 +197,10 @@ func (pr *PullRequest) loadIssue(e Engine) (err error) { // LoadProtectedBranch loads the protected branch of the base branch func (pr *PullRequest) LoadProtectedBranch() (err error) { - return pr.loadProtectedBranch(x) + return pr.loadProtectedBranch(db.GetEngine(db.DefaultContext)) } -func (pr *PullRequest) loadProtectedBranch(e Engine) (err error) { +func (pr *PullRequest) loadProtectedBranch(e db.Engine) (err error) { if pr.ProtectedBranch == nil { if pr.BaseRepo == nil { if pr.BaseRepoID == 0 { @@ -252,10 +257,10 @@ type ReviewCount struct { // GetApprovalCounts returns the approval counts by type // FIXME: Only returns official counts due to double counting of non-official counts func (pr *PullRequest) GetApprovalCounts() ([]*ReviewCount, error) { - return pr.getApprovalCounts(x) + return pr.getApprovalCounts(db.GetEngine(db.DefaultContext)) } -func (pr *PullRequest) getApprovalCounts(e Engine) ([]*ReviewCount, error) { +func (pr *PullRequest) getApprovalCounts(e db.Engine) ([]*ReviewCount, error) { rCounts := make([]*ReviewCount, 0, 6) sess := e.Where("issue_id = ?", pr.IssueID) return rCounts, sess.Select("issue_id, type, count(id) as `count`").Where("official = ? AND dismissed = ?", true, false).GroupBy("issue_id, type").Table("review").Find(&rCounts) @@ -279,7 +284,7 @@ func (pr *PullRequest) getReviewedByLines(writer io.Writer) error { return nil } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -388,7 +393,7 @@ func (pr *PullRequest) SetMerged() (bool, error) { pr.HasMerged = true - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return false, err @@ -443,14 +448,14 @@ func (pr *PullRequest) SetMerged() (bool, error) { // NewPullRequest creates new pull request with labels for repository. func NewPullRequest(repo *Repository, issue *Issue, labelIDs []int64, uuids []string, pr *PullRequest) (err error) { - idx, err := GetNextResourceIndex("issue_index", repo.ID) + idx, err := db.GetNextResourceIndex("issue_index", repo.ID) if err != nil { - return fmt.Errorf("generate issue index failed: %v", err) + return fmt.Errorf("generate pull request index failed: %v", err) } issue.Index = idx - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -487,7 +492,7 @@ func NewPullRequest(repo *Repository, issue *Issue, labelIDs []int64, uuids []st // by given head/base and repo/branch. func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string, flow PullRequestFlow) (*PullRequest, error) { pr := new(PullRequest) - has, err := x. + has, err := db.GetEngine(db.DefaultContext). Where("head_repo_id=? AND head_branch=? AND base_repo_id=? AND base_branch=? AND has_merged=? AND flow = ? AND issue.is_closed=?", headRepoID, headBranch, baseRepoID, baseBranch, false, flow, false). Join("INNER", "issue", "issue.id=pull_request.issue_id"). @@ -505,7 +510,7 @@ func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch // by given head information (repo and branch). func GetLatestPullRequestByHeadInfo(repoID int64, branch string) (*PullRequest, error) { pr := new(PullRequest) - has, err := x. + has, err := db.GetEngine(db.DefaultContext). Where("head_repo_id = ? AND head_branch = ? AND flow = ?", repoID, branch, PullRequestFlowGithub). OrderBy("id DESC"). Get(pr) @@ -517,12 +522,15 @@ func GetLatestPullRequestByHeadInfo(repoID int64, branch string) (*PullRequest, // GetPullRequestByIndex returns a pull request by the given index func GetPullRequestByIndex(repoID, index int64) (*PullRequest, error) { + if index < 1 { + return nil, ErrPullRequestNotExist{} + } pr := &PullRequest{ BaseRepoID: repoID, Index: index, } - has, err := x.Get(pr) + has, err := db.GetEngine(db.DefaultContext).Get(pr) if err != nil { return nil, err } else if !has { @@ -539,7 +547,7 @@ func GetPullRequestByIndex(repoID, index int64) (*PullRequest, error) { return pr, nil } -func getPullRequestByID(e Engine, id int64) (*PullRequest, error) { +func getPullRequestByID(e db.Engine, id int64) (*PullRequest, error) { pr := new(PullRequest) has, err := e.ID(id).Get(pr) if err != nil { @@ -552,13 +560,13 @@ func getPullRequestByID(e Engine, id int64) (*PullRequest, error) { // GetPullRequestByID returns a pull request by given ID. func GetPullRequestByID(id int64) (*PullRequest, error) { - return getPullRequestByID(x, id) + return getPullRequestByID(db.GetEngine(db.DefaultContext), id) } // GetPullRequestByIssueIDWithNoAttributes returns pull request with no attributes loaded by given issue ID. func GetPullRequestByIssueIDWithNoAttributes(issueID int64) (*PullRequest, error) { var pr PullRequest - has, err := x.Where("issue_id = ?", issueID).Get(&pr) + has, err := db.GetEngine(db.DefaultContext).Where("issue_id = ?", issueID).Get(&pr) if err != nil { return nil, err } @@ -568,7 +576,7 @@ func GetPullRequestByIssueIDWithNoAttributes(issueID int64) (*PullRequest, error return &pr, nil } -func getPullRequestByIssueID(e Engine, issueID int64) (*PullRequest, error) { +func getPullRequestByIssueID(e db.Engine, issueID int64) (*PullRequest, error) { pr := &PullRequest{ IssueID: issueID, } @@ -586,7 +594,7 @@ func getPullRequestByIssueID(e Engine, issueID int64) (*PullRequest, error) { func GetAllUnmergedAgitPullRequestByPoster(uid int64) ([]*PullRequest, error) { pulls := make([]*PullRequest, 0, 10) - err := x. + err := db.GetEngine(db.DefaultContext). Where("has_merged=? AND flow = ? AND issue.is_closed=? AND issue.poster_id=?", false, PullRequestFlowAGit, false, uid). Join("INNER", "issue", "issue.id=pull_request.issue_id"). @@ -597,24 +605,24 @@ func GetAllUnmergedAgitPullRequestByPoster(uid int64) ([]*PullRequest, error) { // GetPullRequestByIssueID returns pull request by given issue ID. func GetPullRequestByIssueID(issueID int64) (*PullRequest, error) { - return getPullRequestByIssueID(x, issueID) + return getPullRequestByIssueID(db.GetEngine(db.DefaultContext), issueID) } // Update updates all fields of pull request. func (pr *PullRequest) Update() error { - _, err := x.ID(pr.ID).AllCols().Update(pr) + _, err := db.GetEngine(db.DefaultContext).ID(pr.ID).AllCols().Update(pr) return err } // UpdateCols updates specific fields of pull request. func (pr *PullRequest) UpdateCols(cols ...string) error { - _, err := x.ID(pr.ID).Cols(cols...).Update(pr) + _, err := db.GetEngine(db.DefaultContext).ID(pr.ID).Cols(cols...).Update(pr) return err } // UpdateColsIfNotMerged updates specific fields of a pull request if it has not been merged func (pr *PullRequest) UpdateColsIfNotMerged(cols ...string) error { - _, err := x.Where("id = ? AND has_merged = ?", pr.ID, false).Cols(cols...).Update(pr) + _, err := db.GetEngine(db.DefaultContext).Where("id = ? AND has_merged = ?", pr.ID, false).Cols(cols...).Update(pr) return err } @@ -660,10 +668,10 @@ func (pr *PullRequest) GetWorkInProgressPrefix() string { // UpdateCommitDivergence update Divergence of a pull request func (pr *PullRequest) UpdateCommitDivergence(ahead, behind int) error { - return pr.updateCommitDivergence(x, ahead, behind) + return pr.updateCommitDivergence(db.GetEngine(db.DefaultContext), ahead, behind) } -func (pr *PullRequest) updateCommitDivergence(e Engine, ahead, behind int) error { +func (pr *PullRequest) updateCommitDivergence(e db.Engine, ahead, behind int) error { if pr.ID == 0 { return fmt.Errorf("pull ID is 0") } diff --git a/models/pull_list.go b/models/pull_list.go index 2f685e19f5eaa..57ef210213950 100644 --- a/models/pull_list.go +++ b/models/pull_list.go @@ -7,6 +7,7 @@ package models import ( "fmt" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" @@ -16,7 +17,7 @@ import ( // PullRequestsOptions holds the options for PRs type PullRequestsOptions struct { - ListOptions + db.ListOptions State string SortType string Labels []string @@ -24,7 +25,7 @@ type PullRequestsOptions struct { } func listPullRequestStatement(baseRepoID int64, opts *PullRequestsOptions) (*xorm.Session, error) { - sess := x.Where("pull_request.base_repo_id=?", baseRepoID) + sess := db.GetEngine(db.DefaultContext).Where("pull_request.base_repo_id=?", baseRepoID) sess.Join("INNER", "issue", "pull_request.issue_id = issue.id") switch opts.State { @@ -50,7 +51,7 @@ func listPullRequestStatement(baseRepoID int64, opts *PullRequestsOptions) (*xor // by given head information (repo and branch). func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequest, error) { prs := make([]*PullRequest, 0, 2) - return prs, x. + return prs, db.GetEngine(db.DefaultContext). 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"). @@ -61,7 +62,7 @@ func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequ // by given base information (repo and branch). func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequest, error) { prs := make([]*PullRequest, 0, 2) - return prs, x. + return prs, db.GetEngine(db.DefaultContext). Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?", repoID, branch, false, false). Join("INNER", "issue", "issue.id=pull_request.issue_id"). @@ -71,7 +72,7 @@ func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequ // GetPullRequestIDsByCheckStatus returns all pull requests according the special checking status. func GetPullRequestIDsByCheckStatus(status PullRequestStatus) ([]int64, error) { prs := make([]int64, 0, 10) - return prs, x.Table("pull_request"). + return prs, db.GetEngine(db.DefaultContext).Table("pull_request"). Where("status=?", status). Cols("pull_request.id"). Find(&prs) @@ -100,7 +101,7 @@ func PullRequests(baseRepoID int64, opts *PullRequestsOptions) ([]*PullRequest, log.Error("listPullRequestStatement: %v", err) return nil, maxResults, err } - findSession = opts.setSessionPagination(findSession) + findSession = db.SetSessionPagination(findSession, opts) prs := make([]*PullRequest, 0, opts.PageSize) return prs, maxResults, findSession.Find(&prs) } @@ -108,7 +109,7 @@ func PullRequests(baseRepoID int64, opts *PullRequestsOptions) ([]*PullRequest, // PullRequestList defines a list of pull requests type PullRequestList []*PullRequest -func (prs PullRequestList) loadAttributes(e Engine) error { +func (prs PullRequestList) loadAttributes(e db.Engine) error { if len(prs) == 0 { return nil } @@ -143,10 +144,10 @@ func (prs PullRequestList) getIssueIDs() []int64 { // LoadAttributes load all the prs attributes func (prs PullRequestList) LoadAttributes() error { - return prs.loadAttributes(x) + return prs.loadAttributes(db.GetEngine(db.DefaultContext)) } -func (prs PullRequestList) invalidateCodeComments(e Engine, doer *User, repo *git.Repository, branch string) error { +func (prs PullRequestList) invalidateCodeComments(e db.Engine, doer *User, repo *git.Repository, branch string) error { if len(prs) == 0 { return nil } @@ -168,5 +169,5 @@ func (prs PullRequestList) invalidateCodeComments(e Engine, doer *User, repo *gi // InvalidateCodeComments will lookup the prs for code comments which got invalidated by change func (prs PullRequestList) InvalidateCodeComments(doer *User, repo *git.Repository, branch string) error { - return prs.invalidateCodeComments(x, doer, repo, branch) + return prs.invalidateCodeComments(db.GetEngine(db.DefaultContext), doer, repo, branch) } diff --git a/models/pull_sign.go b/models/pull_sign.go index e7cf4ab666ee8..028a3e5c3b659 100644 --- a/models/pull_sign.go +++ b/models/pull_sign.go @@ -5,6 +5,8 @@ package models import ( + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -35,7 +37,7 @@ Loop: case always: break Loop case pubkey: - keys, err := ListGPGKeys(u.ID, ListOptions{}) + keys, err := ListGPGKeys(u.ID, db.ListOptions{}) if err != nil { return false, "", nil, err } @@ -43,8 +45,8 @@ Loop: return false, "", nil, &ErrWontSign{pubkey} } case twofa: - twofaModel, err := GetTwoFactorByUID(u.ID) - if err != nil && !IsErrTwoFactorNotEnrolled(err) { + twofaModel, err := login.GetTwoFactorByUID(u.ID) + if err != nil && !login.IsErrTwoFactorNotEnrolled(err) { return false, "", nil, err } if twofaModel == nil { diff --git a/models/pull_test.go b/models/pull_test.go index 07216da324344..173977aafeebf 100644 --- a/models/pull_test.go +++ b/models/pull_test.go @@ -7,20 +7,21 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestPullRequest_LoadAttributes(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest) + assert.NoError(t, db.PrepareTestDatabase()) + pr := db.AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest) assert.NoError(t, pr.LoadAttributes()) assert.NotNil(t, pr.Merger) assert.Equal(t, pr.MergerID, pr.Merger.ID) } func TestPullRequest_LoadIssue(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest) + assert.NoError(t, db.PrepareTestDatabase()) + pr := db.AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest) assert.NoError(t, pr.LoadIssue()) assert.NotNil(t, pr.Issue) assert.Equal(t, int64(2), pr.Issue.ID) @@ -30,8 +31,8 @@ func TestPullRequest_LoadIssue(t *testing.T) { } func TestPullRequest_LoadBaseRepo(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest) + assert.NoError(t, db.PrepareTestDatabase()) + pr := db.AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest) assert.NoError(t, pr.LoadBaseRepo()) assert.NotNil(t, pr.BaseRepo) assert.Equal(t, pr.BaseRepoID, pr.BaseRepo.ID) @@ -41,8 +42,8 @@ func TestPullRequest_LoadBaseRepo(t *testing.T) { } func TestPullRequest_LoadHeadRepo(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest) + assert.NoError(t, db.PrepareTestDatabase()) + pr := db.AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest) assert.NoError(t, pr.LoadHeadRepo()) assert.NotNil(t, pr.HeadRepo) assert.Equal(t, pr.HeadRepoID, pr.HeadRepo.ID) @@ -53,9 +54,9 @@ func TestPullRequest_LoadHeadRepo(t *testing.T) { // TODO TestNewPullRequest func TestPullRequestsNewest(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) prs, count, err := PullRequests(1, &PullRequestsOptions{ - ListOptions: ListOptions{ + ListOptions: db.ListOptions{ Page: 1, }, State: "open", @@ -72,9 +73,9 @@ func TestPullRequestsNewest(t *testing.T) { } func TestPullRequestsOldest(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) prs, count, err := PullRequests(1, &PullRequestsOptions{ - ListOptions: ListOptions{ + ListOptions: db.ListOptions{ Page: 1, }, State: "open", @@ -91,7 +92,7 @@ func TestPullRequestsOldest(t *testing.T) { } func TestGetUnmergedPullRequest(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) pr, err := GetUnmergedPullRequest(1, 1, "branch2", "master", PullRequestFlowGithub) assert.NoError(t, err) assert.Equal(t, int64(2), pr.ID) @@ -102,7 +103,7 @@ func TestGetUnmergedPullRequest(t *testing.T) { } func TestGetUnmergedPullRequestsByHeadInfo(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) prs, err := GetUnmergedPullRequestsByHeadInfo(1, "branch2") assert.NoError(t, err) assert.Len(t, prs, 1) @@ -113,7 +114,7 @@ func TestGetUnmergedPullRequestsByHeadInfo(t *testing.T) { } func TestGetUnmergedPullRequestsByBaseInfo(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) prs, err := GetUnmergedPullRequestsByBaseInfo(1, "master") assert.NoError(t, err) assert.Len(t, prs, 1) @@ -124,7 +125,7 @@ func TestGetUnmergedPullRequestsByBaseInfo(t *testing.T) { } func TestGetPullRequestByIndex(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) pr, err := GetPullRequestByIndex(1, 2) assert.NoError(t, err) assert.Equal(t, int64(1), pr.BaseRepoID) @@ -133,10 +134,14 @@ func TestGetPullRequestByIndex(t *testing.T) { _, err = GetPullRequestByIndex(9223372036854775807, 9223372036854775807) assert.Error(t, err) assert.True(t, IsErrPullRequestNotExist(err)) + + _, err = GetPullRequestByIndex(1, 0) + assert.Error(t, err) + assert.True(t, IsErrPullRequestNotExist(err)) } func TestGetPullRequestByID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) pr, err := GetPullRequestByID(1) assert.NoError(t, err) assert.Equal(t, int64(1), pr.ID) @@ -148,7 +153,7 @@ func TestGetPullRequestByID(t *testing.T) { } func TestGetPullRequestByIssueID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) pr, err := GetPullRequestByIssueID(2) assert.NoError(t, err) assert.Equal(t, int64(2), pr.IssueID) @@ -159,20 +164,20 @@ func TestGetPullRequestByIssueID(t *testing.T) { } func TestPullRequest_Update(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest) + assert.NoError(t, db.PrepareTestDatabase()) + pr := db.AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest) pr.BaseBranch = "baseBranch" pr.HeadBranch = "headBranch" pr.Update() - pr = AssertExistsAndLoadBean(t, &PullRequest{ID: pr.ID}).(*PullRequest) + pr = db.AssertExistsAndLoadBean(t, &PullRequest{ID: pr.ID}).(*PullRequest) assert.Equal(t, "baseBranch", pr.BaseBranch) assert.Equal(t, "headBranch", pr.HeadBranch) CheckConsistencyFor(t, pr) } func TestPullRequest_UpdateCols(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) pr := &PullRequest{ ID: 1, BaseBranch: "baseBranch", @@ -180,18 +185,18 @@ func TestPullRequest_UpdateCols(t *testing.T) { } assert.NoError(t, pr.UpdateCols("head_branch")) - pr = AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest) + pr = db.AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest) assert.Equal(t, "master", pr.BaseBranch) assert.Equal(t, "headBranch", pr.HeadBranch) CheckConsistencyFor(t, pr) } func TestPullRequestList_LoadAttributes(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) prs := []*PullRequest{ - AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest), - AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest), + db.AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest), + db.AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest), } assert.NoError(t, PullRequestList(prs).LoadAttributes()) for _, pr := range prs { @@ -205,9 +210,9 @@ func TestPullRequestList_LoadAttributes(t *testing.T) { // TODO TestAddTestPullRequestTask func TestPullRequest_IsWorkInProgress(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest) + pr := db.AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest) pr.LoadIssue() assert.False(t, pr.IsWorkInProgress()) @@ -220,9 +225,9 @@ func TestPullRequest_IsWorkInProgress(t *testing.T) { } func TestPullRequest_GetWorkInProgressPrefixWorkInProgress(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest) + pr := db.AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest) pr.LoadIssue() assert.Empty(t, pr.GetWorkInProgressPrefix()) @@ -236,8 +241,8 @@ func TestPullRequest_GetWorkInProgressPrefixWorkInProgress(t *testing.T) { } func TestPullRequest_GetDefaultMergeMessage_InternalTracker(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest) + assert.NoError(t, db.PrepareTestDatabase()) + pr := db.AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest) assert.Equal(t, "Merge pull request 'issue3' (#3) from branch2 into master", pr.GetDefaultMergeMessage()) @@ -247,7 +252,7 @@ func TestPullRequest_GetDefaultMergeMessage_InternalTracker(t *testing.T) { } func TestPullRequest_GetDefaultMergeMessage_ExternalTracker(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) externalTracker := RepoUnit{ Type: UnitTypeExternalTracker, @@ -259,7 +264,7 @@ func TestPullRequest_GetDefaultMergeMessage_ExternalTracker(t *testing.T) { baseRepo.Owner = &User{Name: "testOwner"} baseRepo.Units = []*RepoUnit{&externalTracker} - pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 2, BaseRepo: baseRepo}).(*PullRequest) + pr := db.AssertExistsAndLoadBean(t, &PullRequest{ID: 2, BaseRepo: baseRepo}).(*PullRequest) assert.Equal(t, "Merge pull request 'issue3' (!3) from branch2 into master", pr.GetDefaultMergeMessage()) diff --git a/models/release.go b/models/release.go index 1ce88a8210c9d..4624791b8ff41 100644 --- a/models/release.go +++ b/models/release.go @@ -6,11 +6,13 @@ package models import ( + "context" "errors" "fmt" "sort" "strings" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" @@ -44,7 +46,11 @@ type Release struct { CreatedUnix timeutil.TimeStamp `xorm:"INDEX"` } -func (r *Release) loadAttributes(e Engine) error { +func init() { + db.RegisterModel(new(Release)) +} + +func (r *Release) loadAttributes(e db.Engine) error { var err error if r.Repo == nil { r.Repo, err = GetRepositoryByID(r.RepoID) @@ -67,7 +73,7 @@ func (r *Release) loadAttributes(e Engine) error { // LoadAttributes load repo and publisher attributes for a release func (r *Release) LoadAttributes() error { - return r.loadAttributes(x) + return r.loadAttributes(db.GetEngine(db.DefaultContext)) } // APIURL the api url for a release. release must have attributes loaded @@ -97,31 +103,31 @@ func IsReleaseExist(repoID int64, tagName string) (bool, error) { return false, nil } - return x.Get(&Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)}) + return db.GetEngine(db.DefaultContext).Get(&Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)}) } // InsertRelease inserts a release func InsertRelease(rel *Release) error { - _, err := x.Insert(rel) + _, err := db.GetEngine(db.DefaultContext).Insert(rel) return err } // InsertReleasesContext insert releases -func InsertReleasesContext(ctx DBContext, rels []*Release) error { - _, err := ctx.e.Insert(rels) +func InsertReleasesContext(ctx context.Context, rels []*Release) error { + _, err := db.GetEngine(ctx).Insert(rels) return err } // UpdateRelease updates all columns of a release -func UpdateRelease(ctx DBContext, rel *Release) error { - _, err := ctx.e.ID(rel.ID).AllCols().Update(rel) +func UpdateRelease(ctx context.Context, rel *Release) error { + _, err := db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel) return err } // AddReleaseAttachments adds a release attachments -func AddReleaseAttachments(ctx DBContext, releaseID int64, attachmentUUIDs []string) (err error) { +func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs []string) (err error) { // Check attachments - attachments, err := getAttachmentsByUUIDs(ctx.e, attachmentUUIDs) + attachments, err := getAttachmentsByUUIDs(db.GetEngine(ctx), attachmentUUIDs) if err != nil { return fmt.Errorf("GetAttachmentsByUUIDs [uuids: %v]: %v", attachmentUUIDs, err) } @@ -132,7 +138,7 @@ func AddReleaseAttachments(ctx DBContext, releaseID int64, attachmentUUIDs []str } attachments[i].ReleaseID = releaseID // No assign value could be 0, so ignore AllCols(). - if _, err = ctx.e.ID(attachments[i].ID).Update(attachments[i]); err != nil { + if _, err = db.GetEngine(ctx).ID(attachments[i].ID).Update(attachments[i]); err != nil { return fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err) } } @@ -150,14 +156,14 @@ func GetRelease(repoID int64, tagName string) (*Release, error) { } rel := &Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)} - _, err = x.Get(rel) + _, err = db.GetEngine(db.DefaultContext).Get(rel) return rel, err } // GetReleaseByID returns release with given ID. func GetReleaseByID(id int64) (*Release, error) { rel := new(Release) - has, err := x. + has, err := db.GetEngine(db.DefaultContext). ID(id). Get(rel) if err != nil { @@ -171,7 +177,7 @@ func GetReleaseByID(id int64) (*Release, error) { // FindReleasesOptions describes the conditions to Find releases type FindReleasesOptions struct { - ListOptions + db.ListOptions IncludeDrafts bool IncludeTags bool IsPreRelease util.OptionalBool @@ -203,12 +209,12 @@ func (opts *FindReleasesOptions) toConds(repoID int64) builder.Cond { // GetReleasesByRepoID returns a list of releases of repository. func GetReleasesByRepoID(repoID int64, opts FindReleasesOptions) ([]*Release, error) { - sess := x. + sess := db.GetEngine(db.DefaultContext). Desc("created_unix", "id"). Where(opts.toConds(repoID)) if opts.PageSize != 0 { - sess = opts.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, &opts.ListOptions) } rels := make([]*Release, 0, opts.PageSize) @@ -217,7 +223,7 @@ func GetReleasesByRepoID(repoID int64, opts FindReleasesOptions) ([]*Release, er // CountReleasesByRepoID returns a number of releases matching FindReleaseOptions and RepoID. func CountReleasesByRepoID(repoID int64, opts FindReleasesOptions) (int64, error) { - return x.Where(opts.toConds(repoID)).Count(new(Release)) + return db.GetEngine(db.DefaultContext).Where(opts.toConds(repoID)).Count(new(Release)) } // GetLatestReleaseByRepoID returns the latest release for a repository @@ -229,7 +235,7 @@ func GetLatestReleaseByRepoID(repoID int64) (*Release, error) { And(builder.Eq{"is_tag": false}) rel := new(Release) - has, err := x. + has, err := db.GetEngine(db.DefaultContext). Desc("created_unix", "id"). Where(cond). Get(rel) @@ -243,8 +249,8 @@ func GetLatestReleaseByRepoID(repoID int64) (*Release, error) { } // GetReleasesByRepoIDAndNames returns a list of releases of repository according repoID and tagNames. -func GetReleasesByRepoIDAndNames(ctx DBContext, repoID int64, tagNames []string) (rels []*Release, err error) { - err = ctx.e. +func GetReleasesByRepoIDAndNames(ctx context.Context, repoID int64, tagNames []string) (rels []*Release, err error) { + err = db.GetEngine(ctx). In("tag_name", tagNames). Desc("created_unix"). Find(&rels, Release{RepoID: repoID}) @@ -253,7 +259,7 @@ func GetReleasesByRepoIDAndNames(ctx DBContext, repoID int64, tagNames []string) // GetReleaseCountByRepoID returns the count of releases of repository func GetReleaseCountByRepoID(repoID int64, opts FindReleasesOptions) (int64, error) { - return x.Where(opts.toConds(repoID)).Count(&Release{}) + return db.GetEngine(db.DefaultContext).Where(opts.toConds(repoID)).Count(&Release{}) } type releaseMetaSearch struct { @@ -276,10 +282,10 @@ func (s releaseMetaSearch) Less(i, j int) bool { // GetReleaseAttachments retrieves the attachments for releases func GetReleaseAttachments(rels ...*Release) (err error) { - return getReleaseAttachments(x, rels...) + return getReleaseAttachments(db.GetEngine(db.DefaultContext), rels...) } -func getReleaseAttachments(e Engine, rels ...*Release) (err error) { +func getReleaseAttachments(e db.Engine, rels ...*Release) (err error) { if len(rels) == 0 { return } @@ -347,13 +353,13 @@ func SortReleases(rels []*Release) { // DeleteReleaseByID deletes a release from database by given ID. func DeleteReleaseByID(id int64) error { - _, err := x.ID(id).Delete(new(Release)) + _, err := db.GetEngine(db.DefaultContext).ID(id).Delete(new(Release)) return err } // UpdateReleasesMigrationsByType updates all migrated repositories' releases from gitServiceType to replace originalAuthorID to posterID func UpdateReleasesMigrationsByType(gitServiceType structs.GitServiceType, originalAuthorID string, posterID int64) error { - _, err := x.Table("release"). + _, err := db.GetEngine(db.DefaultContext).Table("release"). Where("repo_id IN (SELECT id FROM repository WHERE original_service_type = ?)", gitServiceType). And("original_author_id = ?", originalAuthorID). Update(map[string]interface{}{ diff --git a/models/repo.go b/models/repo.go index 23287067e2a40..efd78c6042d69 100644 --- a/models/repo.go +++ b/models/repo.go @@ -10,11 +10,7 @@ import ( "errors" "fmt" "html/template" - "unicode/utf8" - - // Needed for jpeg support - _ "image/jpeg" - "io/ioutil" + _ "image/jpeg" // Needed for jpeg support "net" "net/url" "os" @@ -24,7 +20,9 @@ import ( "strconv" "strings" "time" + "unicode/utf8" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" @@ -251,6 +249,10 @@ type Repository struct { UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } +func init() { + db.RegisterModel(new(Repository)) +} + // SanitizedOriginalURL returns a sanitized OriginalURL func (repo *Repository) SanitizedOriginalURL() string { if repo.OriginalURL == "" { @@ -300,7 +302,7 @@ func (repo *Repository) AfterLoad() { // It creates a fake object that contains error details // when error occurs. func (repo *Repository) MustOwner() *User { - return repo.mustOwner(x) + return repo.mustOwner(db.GetEngine(db.DefaultContext)) } // FullName returns the repository full name @@ -340,7 +342,7 @@ func (repo *Repository) GetCommitsCountCacheKey(contextName string, isRef bool) return fmt.Sprintf("commits-count-%d-%s-%s", repo.ID, prefix, contextName) } -func (repo *Repository) getUnits(e Engine) (err error) { +func (repo *Repository) getUnits(e db.Engine) (err error) { if repo.Units != nil { return nil } @@ -352,10 +354,10 @@ func (repo *Repository) getUnits(e Engine) (err error) { // CheckUnitUser check whether user could visit the unit of this repository func (repo *Repository) CheckUnitUser(user *User, unitType UnitType) bool { - return repo.checkUnitUser(x, user, unitType) + return repo.checkUnitUser(db.GetEngine(db.DefaultContext), user, unitType) } -func (repo *Repository) checkUnitUser(e Engine, user *User, unitType UnitType) bool { +func (repo *Repository) checkUnitUser(e db.Engine, user *User, unitType UnitType) bool { if user.IsAdmin { return true } @@ -370,7 +372,7 @@ func (repo *Repository) checkUnitUser(e Engine, user *User, unitType UnitType) b // UnitEnabled if this repository has the given unit enabled func (repo *Repository) UnitEnabled(tp UnitType) bool { - if err := repo.getUnits(x); err != nil { + if err := repo.getUnits(db.GetEngine(db.DefaultContext)); err != nil { log.Warn("Error loading repository (ID: %d) units: %s", repo.ID, err.Error()) } for _, unit := range repo.Units { @@ -432,10 +434,10 @@ func (repo *Repository) MustGetUnit(tp UnitType) *RepoUnit { // GetUnit returns a RepoUnit object func (repo *Repository) GetUnit(tp UnitType) (*RepoUnit, error) { - return repo.getUnit(x, tp) + return repo.getUnit(db.GetEngine(db.DefaultContext), tp) } -func (repo *Repository) getUnit(e Engine, tp UnitType) (*RepoUnit, error) { +func (repo *Repository) getUnit(e db.Engine, tp UnitType) (*RepoUnit, error) { if err := repo.getUnits(e); err != nil { return nil, err } @@ -447,7 +449,7 @@ func (repo *Repository) getUnit(e Engine, tp UnitType) (*RepoUnit, error) { return nil, ErrUnitTypeNotExist{tp} } -func (repo *Repository) getOwner(e Engine) (err error) { +func (repo *Repository) getOwner(e db.Engine) (err error) { if repo.Owner != nil { return nil } @@ -458,10 +460,10 @@ func (repo *Repository) getOwner(e Engine) (err error) { // GetOwner returns the repository owner func (repo *Repository) GetOwner() error { - return repo.getOwner(x) + return repo.getOwner(db.GetEngine(db.DefaultContext)) } -func (repo *Repository) mustOwner(e Engine) *User { +func (repo *Repository) mustOwner(e db.Engine) *User { if err := repo.getOwner(e); err != nil { return &User{ Name: "error", @@ -496,7 +498,7 @@ func (repo *Repository) ComposeMetas() map[string]string { repo.MustOwner() if repo.Owner.IsOrganization() { teams := make([]string, 0, 5) - _ = x.Table("team_repo"). + _ = db.GetEngine(db.DefaultContext).Table("team_repo"). Join("INNER", "team", "team.id = team_repo.team_id"). Where("team_repo.repo_id = ?", repo.ID). Select("team.lower_name"). @@ -524,7 +526,7 @@ func (repo *Repository) ComposeDocumentMetas() map[string]string { return repo.DocumentRenderingMetas } -func (repo *Repository) getAssignees(e Engine) (_ []*User, err error) { +func (repo *Repository) getAssignees(e db.Engine) (_ []*User, err error) { if err = repo.getOwner(e); err != nil { return nil, err } @@ -559,10 +561,10 @@ func (repo *Repository) getAssignees(e Engine) (_ []*User, err error) { // GetAssignees returns all users that have write access and can be assigned to issues // of the repository, func (repo *Repository) GetAssignees() (_ []*User, err error) { - return repo.getAssignees(x) + return repo.getAssignees(db.GetEngine(db.DefaultContext)) } -func (repo *Repository) getReviewers(e Engine, doerID, posterID int64) ([]*User, error) { +func (repo *Repository) getReviewers(e db.Engine, doerID, posterID int64) ([]*User, error) { // Get the owner of the repository - this often already pre-cached and if so saves complexity for the following queries if err := repo.getOwner(e); err != nil { return nil, err @@ -611,7 +613,7 @@ func (repo *Repository) getReviewers(e Engine, doerID, posterID int64) ([]*User, // all repo watchers and all organization members. // TODO: may be we should have a busy choice for users to block review request to them. func (repo *Repository) GetReviewers(doerID, posterID int64) ([]*User, error) { - return repo.getReviewers(x, doerID, posterID) + return repo.getReviewers(db.GetEngine(db.DefaultContext), doerID, posterID) } // GetReviewerTeams get all teams can be requested to review @@ -657,10 +659,10 @@ func (repo *Repository) LoadPushMirrors() (err error) { // returns an error on failure (NOTE: no error is returned for // non-fork repositories, and BaseRepo will be left untouched) func (repo *Repository) GetBaseRepo() (err error) { - return repo.getBaseRepo(x) + return repo.getBaseRepo(db.GetEngine(db.DefaultContext)) } -func (repo *Repository) getBaseRepo(e Engine) (err error) { +func (repo *Repository) getBaseRepo(e db.Engine) (err error) { if !repo.IsFork { return nil } @@ -678,10 +680,10 @@ func (repo *Repository) IsGenerated() bool { // returns an error on failure (NOTE: no error is returned for // non-generated repositories, and TemplateRepo will be left untouched) func (repo *Repository) GetTemplateRepo() (err error) { - return repo.getTemplateRepo(x) + return repo.getTemplateRepo(db.GetEngine(db.DefaultContext)) } -func (repo *Repository) getTemplateRepo(e Engine) (err error) { +func (repo *Repository) getTemplateRepo(e db.Engine) (err error) { if !repo.IsGenerated() { return nil } @@ -722,7 +724,7 @@ func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) strin // UpdateDefaultBranch updates the default branch func (repo *Repository) UpdateDefaultBranch() error { - _, err := x.ID(repo.ID).Cols("default_branch").Update(repo) + _, err := db.GetEngine(db.DefaultContext).ID(repo.ID).Cols("default_branch").Update(repo) return err } @@ -731,7 +733,7 @@ func (repo *Repository) IsOwnedBy(userID int64) bool { return repo.OwnerID == userID } -func (repo *Repository) updateSize(e Engine) error { +func (repo *Repository) updateSize(e db.Engine) error { size, err := util.GetDirectorySize(repo.RepoPath()) if err != nil { return fmt.Errorf("updateSize: %v", err) @@ -748,8 +750,8 @@ func (repo *Repository) updateSize(e Engine) error { } // UpdateSize updates the repository size, calculating it using util.GetDirectorySize -func (repo *Repository) UpdateSize(ctx DBContext) error { - return repo.updateSize(ctx.e) +func (repo *Repository) UpdateSize(ctx context.Context) error { + return repo.updateSize(db.GetEngine(ctx)) } // CanUserFork returns true if specified user can fork repository. @@ -810,12 +812,12 @@ func (repo *Repository) CanEnableEditor() bool { // GetReaders returns all users that have explicit read access or higher to the repository. func (repo *Repository) GetReaders() (_ []*User, err error) { - return repo.getUsersWithAccessMode(x, AccessModeRead) + return repo.getUsersWithAccessMode(db.GetEngine(db.DefaultContext), AccessModeRead) } // GetWriters returns all users that have write access to the repository. func (repo *Repository) GetWriters() (_ []*User, err error) { - return repo.getUsersWithAccessMode(x, AccessModeWrite) + return repo.getUsersWithAccessMode(db.GetEngine(db.DefaultContext), AccessModeWrite) } // IsReader returns true if user has explicit read access or higher to the repository. @@ -823,11 +825,11 @@ func (repo *Repository) IsReader(userID int64) (bool, error) { if repo.OwnerID == userID { return true, nil } - return x.Where("repo_id = ? AND user_id = ? AND mode >= ?", repo.ID, userID, AccessModeRead).Get(&Access{}) + return db.GetEngine(db.DefaultContext).Where("repo_id = ? AND user_id = ? AND mode >= ?", repo.ID, userID, AccessModeRead).Get(&Access{}) } // getUsersWithAccessMode returns users that have at least given access mode to the repository. -func (repo *Repository) getUsersWithAccessMode(e Engine, mode AccessMode) (_ []*User, err error) { +func (repo *Repository) getUsersWithAccessMode(e db.Engine, mode AccessMode) (_ []*User, err error) { if err = repo.getOwner(e); err != nil { return nil, err } @@ -872,10 +874,10 @@ func (repo *Repository) DescriptionHTML() template.HTML { // ReadBy sets repo to be visited by given user. func (repo *Repository) ReadBy(userID int64) error { - return setRepoNotificationStatusReadIfUnread(x, userID, repo.ID) + return setRepoNotificationStatusReadIfUnread(db.GetEngine(db.DefaultContext), userID, repo.ID) } -func isRepositoryExist(e Engine, u *User, repoName string) (bool, error) { +func isRepositoryExist(e db.Engine, u *User, repoName string) (bool, error) { has, err := e.Get(&Repository{ OwnerID: u.ID, LowerName: strings.ToLower(repoName), @@ -889,7 +891,7 @@ func isRepositoryExist(e Engine, u *User, repoName string) (bool, error) { // IsRepositoryExist returns true if the repository with given name under user has already existed. func IsRepositoryExist(u *User, repoName string) (bool, error) { - return isRepositoryExist(x, u, repoName) + return isRepositoryExist(db.GetEngine(db.DefaultContext), u, repoName) } // CloneLink represents different types of clone URLs of repository. @@ -951,7 +953,7 @@ func CheckCreateRepository(doer, u *User, name string, overwriteOrAdopt bool) er return err } - has, err := isRepositoryExist(x, u, name) + has, err := isRepositoryExist(db.GetEngine(db.DefaultContext), u, name) if err != nil { return fmt.Errorf("IsRepositoryExist: %v", err) } else if has { @@ -1008,7 +1010,7 @@ func GetRepoInitFile(tp, name string) ([]byte, error) { log.Error("Unable to check if %s is a file. Error: %v", customPath, err) } if isFile { - return ioutil.ReadFile(customPath) + return os.ReadFile(customPath) } switch tp { @@ -1040,12 +1042,12 @@ func IsUsableRepoName(name string) error { } // CreateRepository creates a repository for the user/organization. -func CreateRepository(ctx DBContext, doer, u *User, repo *Repository, overwriteOrAdopt bool) (err error) { +func CreateRepository(ctx context.Context, doer, u *User, repo *Repository, overwriteOrAdopt bool) (err error) { if err = IsUsableRepoName(repo.Name); err != nil { return err } - has, err := isRepositoryExist(ctx.e, u, repo.Name) + has, err := isRepositoryExist(db.GetEngine(ctx), u, repo.Name) if err != nil { return fmt.Errorf("IsRepositoryExist: %v", err) } else if has { @@ -1066,10 +1068,10 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository, overwriteO } } - if _, err = ctx.e.Insert(repo); err != nil { + if _, err = db.GetEngine(ctx).Insert(repo); err != nil { return err } - if err = deleteRepoRedirect(ctx.e, u.ID, repo.Name); err != nil { + if err = deleteRepoRedirect(db.GetEngine(ctx), u.ID, repo.Name); err != nil { return err } @@ -1100,46 +1102,46 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository, overwriteO } } - if _, err = ctx.e.Insert(&units); err != nil { + if _, err = db.GetEngine(ctx).Insert(&units); err != nil { return err } // Remember visibility preference. u.LastRepoVisibility = repo.IsPrivate - if err = updateUserCols(ctx.e, u, "last_repo_visibility"); err != nil { + if err = updateUserCols(db.GetEngine(ctx), u, "last_repo_visibility"); err != nil { return fmt.Errorf("updateUser: %v", err) } - if _, err = ctx.e.Incr("num_repos").ID(u.ID).Update(new(User)); err != nil { + if _, err = db.GetEngine(ctx).Incr("num_repos").ID(u.ID).Update(new(User)); err != nil { return fmt.Errorf("increment user total_repos: %v", err) } u.NumRepos++ // Give access to all members in teams with access to all repositories. if u.IsOrganization() { - if err := u.loadTeams(ctx.e); err != nil { + if err := u.loadTeams(db.GetEngine(ctx)); err != nil { return fmt.Errorf("loadTeams: %v", err) } for _, t := range u.Teams { if t.IncludesAllRepositories { - if err := t.addRepository(ctx.e, repo); err != nil { + if err := t.addRepository(db.GetEngine(ctx), repo); err != nil { return fmt.Errorf("addRepository: %v", err) } } } - if isAdmin, err := isUserRepoAdmin(ctx.e, repo, doer); err != nil { + if isAdmin, err := isUserRepoAdmin(db.GetEngine(ctx), repo, doer); err != nil { return fmt.Errorf("isUserRepoAdmin: %v", err) } else if !isAdmin { // Make creator repo admin if it wan't assigned automatically - if err = repo.addCollaborator(ctx.e, doer); err != nil { + if err = repo.addCollaborator(db.GetEngine(ctx), doer); err != nil { return fmt.Errorf("AddCollaborator: %v", err) } - if err = repo.changeCollaborationAccessMode(ctx.e, doer.ID, AccessModeAdmin); err != nil { + if err = repo.changeCollaborationAccessMode(db.GetEngine(ctx), doer.ID, AccessModeAdmin); err != nil { return fmt.Errorf("ChangeCollaborationAccessMode: %v", err) } } - } else if err = repo.recalculateAccesses(ctx.e); err != nil { + } else if err = repo.recalculateAccesses(db.GetEngine(ctx)); err != nil { // Organization automatically called this in addRepository method. return fmt.Errorf("recalculateAccesses: %v", err) } @@ -1155,12 +1157,12 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository, overwriteO } if setting.Service.AutoWatchNewRepos { - if err = watchRepo(ctx.e, doer.ID, repo.ID, true); err != nil { + if err = watchRepo(db.GetEngine(ctx), doer.ID, repo.ID, true); err != nil { return fmt.Errorf("watchRepo: %v", err) } } - if err = copyDefaultWebhooksToRepo(ctx.e, repo.ID); err != nil { + if err = copyDefaultWebhooksToRepo(db.GetEngine(ctx), repo.ID); err != nil { return fmt.Errorf("copyDefaultWebhooksToRepo: %v", err) } @@ -1168,7 +1170,7 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository, overwriteO } func countRepositories(userID int64, private bool) int64 { - sess := x.Where("id > 0") + sess := db.GetEngine(db.DefaultContext).Where("id > 0") if userID > 0 { sess.And("owner_id = ?", userID) @@ -1204,8 +1206,14 @@ func RepoPath(userName, repoName string) string { } // IncrementRepoForkNum increment repository fork number -func IncrementRepoForkNum(ctx DBContext, repoID int64) error { - _, err := ctx.e.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", repoID) +func IncrementRepoForkNum(ctx context.Context, repoID int64) error { + _, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", repoID) + return err +} + +// DecrementRepoForkNum decrement repository fork number +func DecrementRepoForkNum(ctx context.Context, repoID int64) error { + _, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repoID) return err } @@ -1245,7 +1253,7 @@ func ChangeRepositoryName(doer *User, repo *Repository, newRepoName string) (err } } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return fmt.Errorf("sess.Begin: %v", err) @@ -1258,7 +1266,7 @@ func ChangeRepositoryName(doer *User, repo *Repository, newRepoName string) (err return sess.Commit() } -func getRepositoriesByForkID(e Engine, forkID int64) ([]*Repository, error) { +func getRepositoriesByForkID(e db.Engine, forkID int64) ([]*Repository, error) { repos := make([]*Repository, 0, 10) return repos, e. Where("fork_id=?", forkID). @@ -1267,10 +1275,10 @@ func getRepositoriesByForkID(e Engine, forkID int64) ([]*Repository, error) { // GetRepositoriesByForkID returns all repositories with given fork ID. func GetRepositoriesByForkID(forkID int64) ([]*Repository, error) { - return getRepositoriesByForkID(x, forkID) + return getRepositoriesByForkID(db.GetEngine(db.DefaultContext), forkID) } -func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err error) { +func updateRepository(e db.Engine, repo *Repository, visibilityChanged bool) (err error) { repo.LowerName = strings.ToLower(repo.Name) if utf8.RuneCountInString(repo.Description) > 255 { @@ -1345,13 +1353,13 @@ func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err e } // UpdateRepositoryCtx updates a repository with db context -func UpdateRepositoryCtx(ctx DBContext, repo *Repository, visibilityChanged bool) error { - return updateRepository(ctx.e, repo, visibilityChanged) +func UpdateRepositoryCtx(ctx context.Context, repo *Repository, visibilityChanged bool) error { + return updateRepository(db.GetEngine(ctx), repo, visibilityChanged) } // UpdateRepository updates a repository func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -1369,7 +1377,7 @@ func UpdateRepositoryOwnerNames(ownerID int64, ownerName string) error { if ownerID == 0 { return nil } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -1386,13 +1394,13 @@ func UpdateRepositoryOwnerNames(ownerID int64, ownerName string) error { // UpdateRepositoryUpdatedTime updates a repository's updated time func UpdateRepositoryUpdatedTime(repoID int64, updateTime time.Time) error { - _, err := x.Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", updateTime.Unix(), repoID) + _, err := db.GetEngine(db.DefaultContext).Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", updateTime.Unix(), repoID) return err } // UpdateRepositoryUnits updates a repository's units func UpdateRepositoryUnits(repo *Repository, units []RepoUnit, deleteUnitTypes []UnitType) (err error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -1419,7 +1427,7 @@ func UpdateRepositoryUnits(repo *Repository, units []RepoUnit, deleteUnitTypes [ // DeleteRepository deletes a repository for a user or organization. // make sure if you call this func to close open sessions (sqlite will otherwise get a deadlock) func DeleteRepository(doer *User, uid, repoID int64) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -1527,7 +1535,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error { } // Delete issue index - if err := deleteResouceIndex(sess, "issue_index", repoID); err != nil { + if err := db.DeleteResouceIndex(sess, "issue_index", repoID); err != nil { return err } @@ -1635,21 +1643,21 @@ func DeleteRepository(doer *User, uid, repoID int64) error { // Remove repository files. repoPath := repo.RepoPath() - removeAllWithNotice(x, "Delete repository files", repoPath) + removeAllWithNotice(db.GetEngine(db.DefaultContext), "Delete repository files", repoPath) // Remove wiki files if repo.HasWiki() { - removeAllWithNotice(x, "Delete repository wiki", repo.WikiPath()) + removeAllWithNotice(db.GetEngine(db.DefaultContext), "Delete repository wiki", repo.WikiPath()) } // Remove archives for i := range archivePaths { - removeStorageWithNotice(x, storage.RepoArchives, "Delete repo archive file", archivePaths[i]) + removeStorageWithNotice(db.GetEngine(db.DefaultContext), storage.RepoArchives, "Delete repo archive file", archivePaths[i]) } // Remove lfs objects for i := range lfsPaths { - removeStorageWithNotice(x, storage.LFS, "Delete orphaned LFS file", lfsPaths[i]) + removeStorageWithNotice(db.GetEngine(db.DefaultContext), storage.LFS, "Delete orphaned LFS file", lfsPaths[i]) } // Remove issue attachment files. @@ -1678,10 +1686,10 @@ func DeleteRepository(doer *User, uid, repoID int64) error { // GetRepositoryByOwnerAndName returns the repository by given ownername and reponame. func GetRepositoryByOwnerAndName(ownerName, repoName string) (*Repository, error) { - return getRepositoryByOwnerAndName(x, ownerName, repoName) + return getRepositoryByOwnerAndName(db.GetEngine(db.DefaultContext), ownerName, repoName) } -func getRepositoryByOwnerAndName(e Engine, ownerName, repoName string) (*Repository, error) { +func getRepositoryByOwnerAndName(e db.Engine, ownerName, repoName string) (*Repository, error) { var repo Repository has, err := e.Table("repository").Select("repository.*"). Join("INNER", "`user`", "`user`.id = repository.owner_id"). @@ -1702,7 +1710,7 @@ func GetRepositoryByName(ownerID int64, name string) (*Repository, error) { OwnerID: ownerID, LowerName: strings.ToLower(name), } - has, err := x.Get(repo) + has, err := db.GetEngine(db.DefaultContext).Get(repo) if err != nil { return nil, err } else if !has { @@ -1711,7 +1719,7 @@ func GetRepositoryByName(ownerID int64, name string) (*Repository, error) { return repo, err } -func getRepositoryByID(e Engine, id int64) (*Repository, error) { +func getRepositoryByID(e db.Engine, id int64) (*Repository, error) { repo := new(Repository) has, err := e.ID(id).Get(repo) if err != nil { @@ -1724,18 +1732,18 @@ func getRepositoryByID(e Engine, id int64) (*Repository, error) { // GetRepositoryByID returns the repository by given id if exists. func GetRepositoryByID(id int64) (*Repository, error) { - return getRepositoryByID(x, id) + return getRepositoryByID(db.GetEngine(db.DefaultContext), id) } // GetRepositoryByIDCtx returns the repository by given id if exists. -func GetRepositoryByIDCtx(ctx DBContext, id int64) (*Repository, error) { - return getRepositoryByID(ctx.e, id) +func GetRepositoryByIDCtx(ctx context.Context, id int64) (*Repository, error) { + return getRepositoryByID(db.GetEngine(ctx), id) } // GetRepositoriesMapByIDs returns the repositories by given id slice. func GetRepositoriesMapByIDs(ids []int64) (map[int64]*Repository, error) { repos := make(map[int64]*Repository, len(ids)) - return repos, x.In("id", ids).Find(&repos) + return repos, db.GetEngine(db.DefaultContext).In("id", ids).Find(&repos) } // GetUserRepositories returns a list of repositories of given user. @@ -1754,7 +1762,7 @@ func GetUserRepositories(opts *SearchRepoOptions) ([]*Repository, int64, error) cond = cond.And(builder.In("lower_name", opts.LowerNames)) } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() count, err := sess.Where(cond).Count(new(Repository)) @@ -1764,43 +1772,43 @@ func GetUserRepositories(opts *SearchRepoOptions) ([]*Repository, int64, error) sess.Where(cond).OrderBy(opts.OrderBy.String()) repos := make([]*Repository, 0, opts.PageSize) - return repos, count, opts.setSessionPagination(sess).Find(&repos) + return repos, count, db.SetSessionPagination(sess, opts).Find(&repos) } // GetUserMirrorRepositories returns a list of mirror repositories of given user. func GetUserMirrorRepositories(userID int64) ([]*Repository, error) { repos := make([]*Repository, 0, 10) - return repos, x. + return repos, db.GetEngine(db.DefaultContext). Where("owner_id = ?", userID). And("is_mirror = ?", true). Find(&repos) } -func getRepositoryCount(e Engine, u *User) (int64, error) { +func getRepositoryCount(e db.Engine, u *User) (int64, error) { return e.Count(&Repository{OwnerID: u.ID}) } -func getPublicRepositoryCount(e Engine, u *User) (int64, error) { +func getPublicRepositoryCount(e db.Engine, u *User) (int64, error) { return e.Where("is_private = ?", false).Count(&Repository{OwnerID: u.ID}) } -func getPrivateRepositoryCount(e Engine, u *User) (int64, error) { +func getPrivateRepositoryCount(e db.Engine, u *User) (int64, error) { return e.Where("is_private = ?", true).Count(&Repository{OwnerID: u.ID}) } // GetRepositoryCount returns the total number of repositories of user. func GetRepositoryCount(u *User) (int64, error) { - return getRepositoryCount(x, u) + return getRepositoryCount(db.GetEngine(db.DefaultContext), u) } // GetPublicRepositoryCount returns the total number of public repositories of user. func GetPublicRepositoryCount(u *User) (int64, error) { - return getPublicRepositoryCount(x, u) + return getPublicRepositoryCount(db.GetEngine(db.DefaultContext), u) } // GetPrivateRepositoryCount returns the total number of private repositories of user. func GetPrivateRepositoryCount(u *User) (int64, error) { - return getPrivateRepositoryCount(x, u) + return getPrivateRepositoryCount(db.GetEngine(db.DefaultContext), u) } // DeleteOldRepositoryArchives deletes old repository archives. @@ -1809,7 +1817,7 @@ func DeleteOldRepositoryArchives(ctx context.Context, olderThan time.Duration) e for { var archivers []RepoArchiver - err := x.Where("created_unix < ?", time.Now().Add(-olderThan).Unix()). + err := db.GetEngine(db.DefaultContext).Where("created_unix < ?", time.Now().Add(-olderThan).Unix()). Asc("created_unix"). Limit(100). Find(&archivers) @@ -1839,7 +1847,7 @@ func deleteOldRepoArchiver(ctx context.Context, archiver *RepoArchiver) error { if err != nil { return err } - _, err = x.ID(archiver.ID).Delete(delRepoArchiver) + _, err = db.GetEngine(db.DefaultContext).ID(archiver.ID).Delete(delRepoArchiver) if err != nil { return err } @@ -1855,7 +1863,7 @@ type repoChecker struct { } func repoStatsCheck(ctx context.Context, checker *repoChecker) { - results, err := x.Query(checker.querySQL) + results, err := db.GetEngine(db.DefaultContext).Query(checker.querySQL) if err != nil { log.Error("Select %s: %v", checker.desc, err) return @@ -1869,7 +1877,7 @@ func repoStatsCheck(ctx context.Context, checker *repoChecker) { default: } log.Trace("Updating %s: %d", checker.desc, id) - _, err = x.Exec(checker.correctSQL, id, id) + _, err = db.GetEngine(db.DefaultContext).Exec(checker.correctSQL, id, id) if err != nil { log.Error("Update %s[%d]: %v", checker.desc, id, err) } @@ -1924,7 +1932,7 @@ func CheckRepoStats(ctx context.Context) error { // ***** START: Repository.NumClosedIssues ***** desc := "repository count 'num_closed_issues'" - results, err := x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, false) + results, err := db.GetEngine(db.DefaultContext).Query("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, false) if err != nil { log.Error("Select %s: %v", desc, err) } else { @@ -1937,7 +1945,7 @@ func CheckRepoStats(ctx context.Context) error { default: } log.Trace("Updating %s: %d", desc, id) - _, err = x.Exec("UPDATE `repository` SET num_closed_issues=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, false, id) + _, err = db.GetEngine(db.DefaultContext).Exec("UPDATE `repository` SET num_closed_issues=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, false, id) if err != nil { log.Error("Update %s[%d]: %v", desc, id, err) } @@ -1947,7 +1955,7 @@ func CheckRepoStats(ctx context.Context) error { // ***** START: Repository.NumClosedPulls ***** desc = "repository count 'num_closed_pulls'" - results, err = x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_pulls!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, true) + results, err = db.GetEngine(db.DefaultContext).Query("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_pulls!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, true) if err != nil { log.Error("Select %s: %v", desc, err) } else { @@ -1960,7 +1968,7 @@ func CheckRepoStats(ctx context.Context) error { default: } log.Trace("Updating %s: %d", desc, id) - _, err = x.Exec("UPDATE `repository` SET num_closed_pulls=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, true, id) + _, err = db.GetEngine(db.DefaultContext).Exec("UPDATE `repository` SET num_closed_pulls=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, true, id) if err != nil { log.Error("Update %s[%d]: %v", desc, id, err) } @@ -1970,7 +1978,7 @@ func CheckRepoStats(ctx context.Context) error { // FIXME: use checker when stop supporting old fork repo format. // ***** START: Repository.NumForks ***** - results, err = x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)") + results, err = db.GetEngine(db.DefaultContext).Query("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)") if err != nil { log.Error("Select repository count 'num_forks': %v", err) } else { @@ -1990,7 +1998,7 @@ func CheckRepoStats(ctx context.Context) error { continue } - rawResult, err := x.Query("SELECT COUNT(*) FROM `repository` WHERE fork_id=?", repo.ID) + rawResult, err := db.GetEngine(db.DefaultContext).Query("SELECT COUNT(*) FROM `repository` WHERE fork_id=?", repo.ID) if err != nil { log.Error("Select count of forks[%d]: %v", repo.ID, err) continue @@ -2010,7 +2018,7 @@ func CheckRepoStats(ctx context.Context) error { // SetArchiveRepoState sets if a repo is archived func (repo *Repository) SetArchiveRepoState(isArchived bool) (err error) { repo.IsArchived = isArchived - _, err = x.Where("id = ?", repo.ID).Cols("is_archived").NoAutoTime().Update(repo) + _, err = db.GetEngine(db.DefaultContext).Where("id = ?", repo.ID).Cols("is_archived").NoAutoTime().Update(repo) return } @@ -2024,23 +2032,23 @@ func (repo *Repository) SetArchiveRepoState(isArchived bool) (err error) { // HasForkedRepo checks if given user has already forked a repository with given ID. func HasForkedRepo(ownerID, repoID int64) (*Repository, bool) { repo := new(Repository) - has, _ := x. + has, _ := db.GetEngine(db.DefaultContext). Where("owner_id=? AND fork_id=?", ownerID, repoID). Get(repo) return repo, has } // CopyLFS copies LFS data from one repo to another -func CopyLFS(ctx DBContext, newRepo, oldRepo *Repository) error { +func CopyLFS(ctx context.Context, newRepo, oldRepo *Repository) error { var lfsObjects []*LFSMetaObject - if err := ctx.e.Where("repository_id=?", oldRepo.ID).Find(&lfsObjects); err != nil { + if err := db.GetEngine(ctx).Where("repository_id=?", oldRepo.ID).Find(&lfsObjects); err != nil { return err } for _, v := range lfsObjects { v.ID = 0 v.RepositoryID = newRepo.ID - if _, err := ctx.e.Insert(v); err != nil { + if _, err := db.GetEngine(ctx).Insert(v); err != nil { return err } } @@ -2049,13 +2057,13 @@ func CopyLFS(ctx DBContext, newRepo, oldRepo *Repository) error { } // GetForks returns all the forks of the repository -func (repo *Repository) GetForks(listOptions ListOptions) ([]*Repository, error) { +func (repo *Repository) GetForks(listOptions db.ListOptions) ([]*Repository, error) { if listOptions.Page == 0 { forks := make([]*Repository, 0, repo.NumForks) - return forks, x.Find(&forks, &Repository{ForkID: repo.ID}) + return forks, db.GetEngine(db.DefaultContext).Find(&forks, &Repository{ForkID: repo.ID}) } - sess := listOptions.getPaginatedSession() + sess := db.GetPaginatedSession(&listOptions) forks := make([]*Repository, 0, listOptions.PageSize) return forks, sess.Find(&forks, &Repository{ForkID: repo.ID}) } @@ -2063,7 +2071,7 @@ func (repo *Repository) GetForks(listOptions ListOptions) ([]*Repository, error) // GetUserFork return user forked repository from this repository, if not forked return nil func (repo *Repository) GetUserFork(userID int64) (*Repository, error) { var forkedRepo Repository - has, err := x.Where("fork_id = ?", repo.ID).And("owner_id = ?", userID).Get(&forkedRepo) + has, err := db.GetEngine(db.DefaultContext).Where("fork_id = ?", repo.ID).And("owner_id = ?", userID).Get(&forkedRepo) if err != nil { return nil, err } @@ -2099,14 +2107,14 @@ func (repo *Repository) GetTreePathLock(treePath string) (*LFSLock, error) { return nil, nil } -func updateRepositoryCols(e Engine, repo *Repository, cols ...string) error { +func updateRepositoryCols(e db.Engine, repo *Repository, cols ...string) error { _, err := e.ID(repo.ID).Cols(cols...).Update(repo) return err } // UpdateRepositoryCols updates repository's columns func UpdateRepositoryCols(repo *Repository, cols ...string) error { - return updateRepositoryCols(x, repo, cols...) + return updateRepositoryCols(db.GetEngine(db.DefaultContext), repo, cols...) } // GetTrustModel will get the TrustModel for the repo or the default trust model @@ -2124,7 +2132,7 @@ func (repo *Repository) GetTrustModel() TrustModelType { // DoctorUserStarNum recalculate Stars number for all user func DoctorUserStarNum() (err error) { const batchSize = 100 - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() for start := 0; ; start += batchSize { @@ -2162,7 +2170,7 @@ func IterateRepository(f func(repo *Repository) error) error { batchSize := setting.Database.IterateBufferSize for { repos := make([]*Repository, 0, batchSize) - if err := x.Limit(batchSize, start).Find(&repos); err != nil { + if err := db.GetEngine(db.DefaultContext).Limit(batchSize, start).Find(&repos); err != nil { return err } if len(repos) == 0 { diff --git a/models/repo_activity.go b/models/repo_activity.go index 8ee1011c2a6ad..5986da7e77ae9 100644 --- a/models/repo_activity.go +++ b/models/repo_activity.go @@ -9,6 +9,7 @@ import ( "sort" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "xorm.io/xorm" @@ -245,7 +246,7 @@ func (stats *ActivityStats) FillPullRequests(repoID int64, fromTime time.Time) e } func pullRequestsForActivityStatement(repoID int64, fromTime time.Time, merged bool) *xorm.Session { - sess := x.Where("pull_request.base_repo_id=?", repoID). + sess := db.GetEngine(db.DefaultContext).Where("pull_request.base_repo_id=?", repoID). Join("INNER", "issue", "pull_request.issue_id = issue.id") if merged { @@ -313,7 +314,7 @@ func (stats *ActivityStats) FillUnresolvedIssues(repoID int64, fromTime time.Tim } func issuesForActivityStatement(repoID int64, fromTime time.Time, closed, unresolved bool) *xorm.Session { - sess := x.Where("issue.repo_id = ?", repoID). + sess := db.GetEngine(db.DefaultContext).Where("issue.repo_id = ?", repoID). And("issue.is_closed = ?", closed) if !unresolved { @@ -355,7 +356,7 @@ func (stats *ActivityStats) FillReleases(repoID int64, fromTime time.Time) error } func releasesForActivityStatement(repoID int64, fromTime time.Time) *xorm.Session { - return x.Where("release.repo_id = ?", repoID). + return db.GetEngine(db.DefaultContext).Where("release.repo_id = ?", repoID). And("release.is_draft = ?", false). And("release.created_unix >= ?", fromTime.Unix()) } diff --git a/models/repo_archiver.go b/models/repo_archiver.go index 677f0d3e805df..647a3b47be34a 100644 --- a/models/repo_archiver.go +++ b/models/repo_archiver.go @@ -5,8 +5,10 @@ package models import ( + "context" "fmt" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/timeutil" ) @@ -31,6 +33,10 @@ type RepoArchiver struct { CreatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL created"` } +func init() { + db.RegisterModel(new(RepoArchiver)) +} + // LoadRepo loads repository func (archiver *RepoArchiver) LoadRepo() (*Repository, error) { if archiver.Repo != nil { @@ -38,7 +44,7 @@ func (archiver *RepoArchiver) LoadRepo() (*Repository, error) { } var repo Repository - has, err := x.ID(archiver.RepoID).Get(&repo) + has, err := db.GetEngine(db.DefaultContext).ID(archiver.RepoID).Get(&repo) if err != nil { return nil, err } @@ -56,9 +62,9 @@ func (archiver *RepoArchiver) RelativePath() (string, error) { } // GetRepoArchiver get an archiver -func GetRepoArchiver(ctx DBContext, repoID int64, tp git.ArchiveType, commitID string) (*RepoArchiver, error) { +func GetRepoArchiver(ctx context.Context, repoID int64, tp git.ArchiveType, commitID string) (*RepoArchiver, error) { var archiver RepoArchiver - has, err := ctx.e.Where("repo_id=?", repoID).And("`type`=?", tp).And("commit_id=?", commitID).Get(&archiver) + has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).And("`type`=?", tp).And("commit_id=?", commitID).Get(&archiver) if err != nil { return nil, err } @@ -69,19 +75,19 @@ func GetRepoArchiver(ctx DBContext, repoID int64, tp git.ArchiveType, commitID s } // AddRepoArchiver adds an archiver -func AddRepoArchiver(ctx DBContext, archiver *RepoArchiver) error { - _, err := ctx.e.Insert(archiver) +func AddRepoArchiver(ctx context.Context, archiver *RepoArchiver) error { + _, err := db.GetEngine(ctx).Insert(archiver) return err } // UpdateRepoArchiverStatus updates archiver's status -func UpdateRepoArchiverStatus(ctx DBContext, archiver *RepoArchiver) error { - _, err := ctx.e.ID(archiver.ID).Cols("status").Update(archiver) +func UpdateRepoArchiverStatus(ctx context.Context, archiver *RepoArchiver) error { + _, err := db.GetEngine(ctx).ID(archiver.ID).Cols("status").Update(archiver) return err } // DeleteAllRepoArchives deletes all repo archives records func DeleteAllRepoArchives() error { - _, err := x.Where("1=1").Delete(new(RepoArchiver)) + _, err := db.GetEngine(db.DefaultContext).Where("1=1").Delete(new(RepoArchiver)) return err } diff --git a/models/repo_avatar.go b/models/repo_avatar.go index 6f8f55f9e3ece..bb5f083dd5e2c 100644 --- a/models/repo_avatar.go +++ b/models/repo_avatar.go @@ -13,6 +13,7 @@ import ( "strconv" "strings" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/avatar" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -25,7 +26,7 @@ func (repo *Repository) CustomAvatarRelativePath() string { } // generateRandomAvatar generates a random avatar for repository. -func (repo *Repository) generateRandomAvatar(e Engine) error { +func (repo *Repository) generateRandomAvatar(e db.Engine) error { idToString := fmt.Sprintf("%d", repo.ID) seed := idToString @@ -56,7 +57,7 @@ func (repo *Repository) generateRandomAvatar(e Engine) error { // RemoveRandomAvatars removes the randomly generated avatars that were created for repositories func RemoveRandomAvatars(ctx context.Context) error { - return x. + return db.GetEngine(db.DefaultContext). Where("id > 0").BufferSize(setting.Database.IterateBufferSize). Iterate(new(Repository), func(idx int, bean interface{}) error { @@ -76,10 +77,10 @@ func RemoveRandomAvatars(ctx context.Context) error { // RelAvatarLink returns a relative link to the repository's avatar. func (repo *Repository) RelAvatarLink() string { - return repo.relAvatarLink(x) + return repo.relAvatarLink(db.GetEngine(db.DefaultContext)) } -func (repo *Repository) relAvatarLink(e Engine) string { +func (repo *Repository) relAvatarLink(e db.Engine) string { // If no avatar - path is empty avatarPath := repo.CustomAvatarRelativePath() if len(avatarPath) == 0 { @@ -100,11 +101,11 @@ func (repo *Repository) relAvatarLink(e Engine) string { // AvatarLink returns a link to the repository's avatar. func (repo *Repository) AvatarLink() string { - return repo.avatarLink(x) + return repo.avatarLink(db.GetEngine(db.DefaultContext)) } // avatarLink returns user avatar absolute link. -func (repo *Repository) avatarLink(e Engine) string { +func (repo *Repository) avatarLink(e db.Engine) string { link := repo.relAvatarLink(e) // link may be empty! if len(link) > 0 { @@ -128,7 +129,7 @@ func (repo *Repository) UploadAvatar(data []byte) error { return nil } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -171,7 +172,7 @@ func (repo *Repository) DeleteAvatar() error { avatarPath := repo.CustomAvatarRelativePath() log.Trace("DeleteAvatar[%d]: %s", repo.ID, avatarPath) - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err diff --git a/models/repo_collaboration.go b/models/repo_collaboration.go index a8b715bbcfe63..bc9d0100ef2e8 100644 --- a/models/repo_collaboration.go +++ b/models/repo_collaboration.go @@ -8,6 +8,8 @@ package models import ( "fmt" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" "xorm.io/builder" @@ -23,7 +25,11 @@ type Collaboration struct { UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } -func (repo *Repository) addCollaborator(e Engine, u *User) error { +func init() { + db.RegisterModel(new(Collaboration)) +} + +func (repo *Repository) addCollaborator(e db.Engine, u *User) error { collaboration := &Collaboration{ RepoID: repo.ID, UserID: u.ID, @@ -46,7 +52,7 @@ func (repo *Repository) addCollaborator(e Engine, u *User) error { // AddCollaborator adds new collaboration to a repository with default access mode. func (repo *Repository) AddCollaborator(u *User) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -59,13 +65,13 @@ func (repo *Repository) AddCollaborator(u *User) error { return sess.Commit() } -func (repo *Repository) getCollaborations(e Engine, listOptions ListOptions) ([]*Collaboration, error) { +func (repo *Repository) getCollaborations(e db.Engine, listOptions db.ListOptions) ([]*Collaboration, error) { if listOptions.Page == 0 { collaborations := make([]*Collaboration, 0, 8) return collaborations, e.Find(&collaborations, &Collaboration{RepoID: repo.ID}) } - e = listOptions.setEnginePagination(e) + e = db.SetEnginePagination(e, &listOptions) collaborations := make([]*Collaboration, 0, listOptions.PageSize) return collaborations, e.Find(&collaborations, &Collaboration{RepoID: repo.ID}) @@ -77,37 +83,42 @@ type Collaborator struct { Collaboration *Collaboration } -func (repo *Repository) getCollaborators(e Engine, listOptions ListOptions) ([]*Collaborator, error) { +func (repo *Repository) getCollaborators(e db.Engine, listOptions db.ListOptions) ([]*Collaborator, error) { collaborations, err := repo.getCollaborations(e, listOptions) if err != nil { return nil, fmt.Errorf("getCollaborations: %v", err) } - collaborators := make([]*Collaborator, len(collaborations)) - for i, c := range collaborations { + collaborators := make([]*Collaborator, 0, len(collaborations)) + for _, c := range collaborations { user, err := getUserByID(e, c.UserID) if err != nil { - return nil, err + if IsErrUserNotExist(err) { + log.Warn("Inconsistent DB: User: %d is listed as collaborator of %-v but does not exist", c.UserID, repo) + user = NewGhostUser() + } else { + return nil, err + } } - collaborators[i] = &Collaborator{ + collaborators = append(collaborators, &Collaborator{ User: user, Collaboration: c, - } + }) } return collaborators, nil } // GetCollaborators returns the collaborators for a repository -func (repo *Repository) GetCollaborators(listOptions ListOptions) ([]*Collaborator, error) { - return repo.getCollaborators(x, listOptions) +func (repo *Repository) GetCollaborators(listOptions db.ListOptions) ([]*Collaborator, error) { + return repo.getCollaborators(db.GetEngine(db.DefaultContext), listOptions) } // CountCollaborators returns total number of collaborators for a repository func (repo *Repository) CountCollaborators() (int64, error) { - return x.Where("repo_id = ? ", repo.ID).Count(&Collaboration{}) + return db.GetEngine(db.DefaultContext).Where("repo_id = ? ", repo.ID).Count(&Collaboration{}) } -func (repo *Repository) getCollaboration(e Engine, uid int64) (*Collaboration, error) { +func (repo *Repository) getCollaboration(e db.Engine, uid int64) (*Collaboration, error) { collaboration := &Collaboration{ RepoID: repo.ID, UserID: uid, @@ -119,16 +130,16 @@ func (repo *Repository) getCollaboration(e Engine, uid int64) (*Collaboration, e return collaboration, err } -func (repo *Repository) isCollaborator(e Engine, userID int64) (bool, error) { +func (repo *Repository) isCollaborator(e db.Engine, userID int64) (bool, error) { return e.Get(&Collaboration{RepoID: repo.ID, UserID: userID}) } // IsCollaborator check if a user is a collaborator of a repository func (repo *Repository) IsCollaborator(userID int64) (bool, error) { - return repo.isCollaborator(x, userID) + return repo.isCollaborator(db.GetEngine(db.DefaultContext), userID) } -func (repo *Repository) changeCollaborationAccessMode(e Engine, uid int64, mode AccessMode) error { +func (repo *Repository) changeCollaborationAccessMode(e db.Engine, uid int64, mode AccessMode) error { // Discard invalid input if mode <= AccessModeNone || mode > AccessModeOwner { return nil @@ -164,7 +175,7 @@ func (repo *Repository) changeCollaborationAccessMode(e Engine, uid int64, mode // ChangeCollaborationAccessMode sets new access mode for the collaboration. func (repo *Repository) ChangeCollaborationAccessMode(uid int64, mode AccessMode) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -184,7 +195,7 @@ func (repo *Repository) DeleteCollaboration(uid int64) (err error) { UserID: uid, } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -212,7 +223,7 @@ func (repo *Repository) DeleteCollaboration(uid int64) (err error) { return sess.Commit() } -func (repo *Repository) reconsiderIssueAssignees(e Engine, uid int64) error { +func (repo *Repository) reconsiderIssueAssignees(e db.Engine, uid int64) error { user, err := getUserByID(e, uid) if err != nil { return err @@ -230,7 +241,7 @@ func (repo *Repository) reconsiderIssueAssignees(e Engine, uid int64) error { return nil } -func (repo *Repository) reconsiderWatches(e Engine, uid int64) error { +func (repo *Repository) reconsiderWatches(e db.Engine, uid int64) error { if has, err := hasAccess(e, uid, repo); err != nil || has { return err } @@ -243,7 +254,7 @@ func (repo *Repository) reconsiderWatches(e Engine, uid int64) error { return removeIssueWatchersByRepoID(e, uid, repo.ID) } -func (repo *Repository) getRepoTeams(e Engine) (teams []*Team, err error) { +func (repo *Repository) getRepoTeams(e db.Engine) (teams []*Team, err error) { return teams, e. Join("INNER", "team_repo", "team_repo.team_id = team.id"). Where("team.org_id = ?", repo.OwnerID). @@ -254,7 +265,7 @@ func (repo *Repository) getRepoTeams(e Engine) (teams []*Team, err error) { // GetRepoTeams gets the list of teams that has access to the repository func (repo *Repository) GetRepoTeams() ([]*Team, error) { - return repo.getRepoTeams(x) + return repo.getRepoTeams(db.GetEngine(db.DefaultContext)) } // IsOwnerMemberCollaborator checks if a provided user is the owner, a collaborator or a member of a team in a repository @@ -262,7 +273,7 @@ func (repo *Repository) IsOwnerMemberCollaborator(userID int64) (bool, error) { if repo.OwnerID == userID { return true, nil } - teamMember, err := x.Join("INNER", "team_repo", "team_repo.team_id = team_user.team_id"). + teamMember, err := db.GetEngine(db.DefaultContext).Join("INNER", "team_repo", "team_repo.team_id = team_user.team_id"). Join("INNER", "team_unit", "team_unit.team_id = team_user.team_id"). Where("team_repo.repo_id = ?", repo.ID). And("team_unit.`type` = ?", UnitTypeCode). @@ -274,5 +285,5 @@ func (repo *Repository) IsOwnerMemberCollaborator(userID int64) (bool, error) { return true, nil } - return x.Get(&Collaboration{RepoID: repo.ID, UserID: userID}) + return db.GetEngine(db.DefaultContext).Get(&Collaboration{RepoID: repo.ID, UserID: userID}) } diff --git a/models/repo_collaboration_test.go b/models/repo_collaboration_test.go index 7bae27bce0d4a..326fb4dbf7a87 100644 --- a/models/repo_collaboration_test.go +++ b/models/repo_collaboration_test.go @@ -7,16 +7,17 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestRepository_AddCollaborator(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testSuccess := func(repoID, userID int64) { - repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) assert.NoError(t, repo.GetOwner()) - user := AssertExistsAndLoadBean(t, &User{ID: userID}).(*User) + user := db.AssertExistsAndLoadBean(t, &User{ID: userID}).(*User) assert.NoError(t, repo.AddCollaborator(user)) CheckConsistencyFor(t, &Repository{ID: repoID}, &User{ID: userID}) } @@ -26,12 +27,12 @@ func TestRepository_AddCollaborator(t *testing.T) { } func TestRepository_GetCollaborators(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(repoID int64) { - repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) - collaborators, err := repo.GetCollaborators(ListOptions{}) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) + collaborators, err := repo.GetCollaborators(db.ListOptions{}) assert.NoError(t, err) - expectedLen, err := x.Count(&Collaboration{RepoID: repoID}) + expectedLen, err := db.GetEngine(db.DefaultContext).Count(&Collaboration{RepoID: repoID}) assert.NoError(t, err) assert.Len(t, collaborators, int(expectedLen)) for _, collaborator := range collaborators { @@ -46,49 +47,49 @@ func TestRepository_GetCollaborators(t *testing.T) { } func TestRepository_IsCollaborator(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) test := func(repoID, userID int64, expected bool) { - repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) actual, err := repo.IsCollaborator(userID) assert.NoError(t, err) assert.Equal(t, expected, actual) } test(3, 2, true) - test(3, NonexistentID, false) + test(3, db.NonexistentID, false) test(4, 2, false) test(4, 4, true) } func TestRepository_ChangeCollaborationAccessMode(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 4}).(*Repository) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 4}).(*Repository) assert.NoError(t, repo.ChangeCollaborationAccessMode(4, AccessModeAdmin)) - collaboration := AssertExistsAndLoadBean(t, &Collaboration{RepoID: repo.ID, UserID: 4}).(*Collaboration) + collaboration := db.AssertExistsAndLoadBean(t, &Collaboration{RepoID: repo.ID, UserID: 4}).(*Collaboration) assert.EqualValues(t, AccessModeAdmin, collaboration.Mode) - access := AssertExistsAndLoadBean(t, &Access{UserID: 4, RepoID: repo.ID}).(*Access) + access := db.AssertExistsAndLoadBean(t, &Access{UserID: 4, RepoID: repo.ID}).(*Access) assert.EqualValues(t, AccessModeAdmin, access.Mode) assert.NoError(t, repo.ChangeCollaborationAccessMode(4, AccessModeAdmin)) - assert.NoError(t, repo.ChangeCollaborationAccessMode(NonexistentID, AccessModeAdmin)) + assert.NoError(t, repo.ChangeCollaborationAccessMode(db.NonexistentID, AccessModeAdmin)) CheckConsistencyFor(t, &Repository{ID: repo.ID}) } func TestRepository_DeleteCollaboration(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 4}).(*Repository) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 4}).(*Repository) assert.NoError(t, repo.GetOwner()) assert.NoError(t, repo.DeleteCollaboration(4)) - AssertNotExistsBean(t, &Collaboration{RepoID: repo.ID, UserID: 4}) + db.AssertNotExistsBean(t, &Collaboration{RepoID: repo.ID, UserID: 4}) assert.NoError(t, repo.DeleteCollaboration(4)) - AssertNotExistsBean(t, &Collaboration{RepoID: repo.ID, UserID: 4}) + db.AssertNotExistsBean(t, &Collaboration{RepoID: repo.ID, UserID: 4}) CheckConsistencyFor(t, &Repository{ID: repo.ID}) } diff --git a/models/repo_generate.go b/models/repo_generate.go index 66682903f1e97..650da711a3472 100644 --- a/models/repo_generate.go +++ b/models/repo_generate.go @@ -7,9 +7,11 @@ package models import ( "bufio" "bytes" + "context" "strconv" "strings" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/storage" @@ -67,9 +69,9 @@ func (gt GiteaTemplate) Globs() []glob.Glob { } // GenerateTopics generates topics from a template repository -func GenerateTopics(ctx DBContext, templateRepo, generateRepo *Repository) error { +func GenerateTopics(ctx context.Context, templateRepo, generateRepo *Repository) error { for _, topic := range templateRepo.Topics { - if _, err := addTopicByNameToRepo(ctx.e, generateRepo.ID, topic); err != nil { + if _, err := addTopicByNameToRepo(db.GetEngine(ctx), generateRepo.ID, topic); err != nil { return err } } @@ -77,7 +79,7 @@ func GenerateTopics(ctx DBContext, templateRepo, generateRepo *Repository) error } // GenerateGitHooks generates git hooks from a template repository -func GenerateGitHooks(ctx DBContext, templateRepo, generateRepo *Repository) error { +func GenerateGitHooks(ctx context.Context, templateRepo, generateRepo *Repository) error { generateGitRepo, err := git.OpenRepository(generateRepo.RepoPath()) if err != nil { return err @@ -110,7 +112,7 @@ func GenerateGitHooks(ctx DBContext, templateRepo, generateRepo *Repository) err } // GenerateWebhooks generates webhooks from a template repository -func GenerateWebhooks(ctx DBContext, templateRepo, generateRepo *Repository) error { +func GenerateWebhooks(ctx context.Context, templateRepo, generateRepo *Repository) error { templateWebhooks, err := ListWebhooksByOpts(&ListWebhookOptions{RepoID: templateRepo.ID}) if err != nil { return err @@ -130,7 +132,7 @@ func GenerateWebhooks(ctx DBContext, templateRepo, generateRepo *Repository) err Events: templateWebhook.Events, Meta: templateWebhook.Meta, } - if err := createWebhook(ctx.e, generateWebhook); err != nil { + if err := createWebhook(db.GetEngine(ctx), generateWebhook); err != nil { return err } } @@ -138,18 +140,18 @@ func GenerateWebhooks(ctx DBContext, templateRepo, generateRepo *Repository) err } // GenerateAvatar generates the avatar from a template repository -func GenerateAvatar(ctx DBContext, templateRepo, generateRepo *Repository) error { +func GenerateAvatar(ctx context.Context, templateRepo, generateRepo *Repository) error { generateRepo.Avatar = strings.Replace(templateRepo.Avatar, strconv.FormatInt(templateRepo.ID, 10), strconv.FormatInt(generateRepo.ID, 10), 1) if _, err := storage.Copy(storage.RepoAvatars, generateRepo.CustomAvatarRelativePath(), storage.RepoAvatars, templateRepo.CustomAvatarRelativePath()); err != nil { return err } - return updateRepositoryCols(ctx.e, generateRepo, "avatar") + return updateRepositoryCols(db.GetEngine(ctx), generateRepo, "avatar") } // GenerateIssueLabels generates issue labels from a template repository -func GenerateIssueLabels(ctx DBContext, templateRepo, generateRepo *Repository) error { - templateLabels, err := getLabelsByRepoID(ctx.e, templateRepo.ID, "", ListOptions{}) +func GenerateIssueLabels(ctx context.Context, templateRepo, generateRepo *Repository) error { + templateLabels, err := getLabelsByRepoID(db.GetEngine(ctx), templateRepo.ID, "", db.ListOptions{}) if err != nil { return err } @@ -161,7 +163,7 @@ func GenerateIssueLabels(ctx DBContext, templateRepo, generateRepo *Repository) Description: templateLabel.Description, Color: templateLabel.Color, } - if err := newLabel(ctx.e, generateLabel); err != nil { + if err := newLabel(db.GetEngine(ctx), generateLabel); err != nil { return err } } diff --git a/models/repo_indexer.go b/models/repo_indexer.go index de3358f55a0ce..7029b0922b07f 100644 --- a/models/repo_indexer.go +++ b/models/repo_indexer.go @@ -7,6 +7,7 @@ package models import ( "fmt" + "code.gitea.io/gitea/models/db" "xorm.io/builder" ) @@ -29,6 +30,10 @@ type RepoIndexerStatus struct { IndexerType RepoIndexerType `xorm:"INDEX(s) NOT NULL DEFAULT 0"` } +func init() { + db.RegisterModel(new(RepoIndexerStatus)) +} + // GetUnindexedRepos returns repos which do not have an indexer status func GetUnindexedRepos(indexerType RepoIndexerType, maxRepoID int64, page, pageSize int) ([]int64, error) { ids := make([]int64, 0, 50) @@ -37,7 +42,7 @@ func GetUnindexedRepos(indexerType RepoIndexerType, maxRepoID int64, page, pageS }).And(builder.Eq{ "repository.is_empty": false, }) - sess := x.Table("repository").Join("LEFT OUTER", "repo_indexer_status", "repository.id = repo_indexer_status.repo_id AND repo_indexer_status.indexer_type = ?", indexerType) + sess := db.GetEngine(db.DefaultContext).Table("repository").Join("LEFT OUTER", "repo_indexer_status", "repository.id = repo_indexer_status.repo_id AND repo_indexer_status.indexer_type = ?", indexerType) if maxRepoID > 0 { cond = builder.And(cond, builder.Lte{ "repository.id": maxRepoID, @@ -57,7 +62,7 @@ func GetUnindexedRepos(indexerType RepoIndexerType, maxRepoID int64, page, pageS } // getIndexerStatus loads repo codes indxer status -func (repo *Repository) getIndexerStatus(e Engine, indexerType RepoIndexerType) (*RepoIndexerStatus, error) { +func (repo *Repository) getIndexerStatus(e db.Engine, indexerType RepoIndexerType) (*RepoIndexerStatus, error) { switch indexerType { case RepoIndexerTypeCode: if repo.CodeIndexerStatus != nil { @@ -86,11 +91,11 @@ func (repo *Repository) getIndexerStatus(e Engine, indexerType RepoIndexerType) // GetIndexerStatus loads repo codes indxer status func (repo *Repository) GetIndexerStatus(indexerType RepoIndexerType) (*RepoIndexerStatus, error) { - return repo.getIndexerStatus(x, indexerType) + return repo.getIndexerStatus(db.GetEngine(db.DefaultContext), indexerType) } // updateIndexerStatus updates indexer status -func (repo *Repository) updateIndexerStatus(e Engine, indexerType RepoIndexerType, sha string) error { +func (repo *Repository) updateIndexerStatus(e db.Engine, indexerType RepoIndexerType, sha string) error { status, err := repo.getIndexerStatus(e, indexerType) if err != nil { return fmt.Errorf("UpdateIndexerStatus: Unable to getIndexerStatus for repo: %s Error: %v", repo.FullName(), err) @@ -115,5 +120,5 @@ func (repo *Repository) updateIndexerStatus(e Engine, indexerType RepoIndexerTyp // UpdateIndexerStatus updates indexer status func (repo *Repository) UpdateIndexerStatus(indexerType RepoIndexerType, sha string) error { - return repo.updateIndexerStatus(x, indexerType, sha) + return repo.updateIndexerStatus(db.GetEngine(db.DefaultContext), indexerType, sha) } diff --git a/models/repo_language_stats.go b/models/repo_language_stats.go index 8760726aeb197..2f126aa3d2ac0 100644 --- a/models/repo_language_stats.go +++ b/models/repo_language_stats.go @@ -8,6 +8,7 @@ import ( "math" "strings" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" "github.com/go-enry/go-enry/v2" @@ -26,6 +27,10 @@ type LanguageStat struct { CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"` } +func init() { + db.RegisterModel(new(LanguageStat)) +} + // LanguageStatList defines a list of language statistics type LanguageStatList []*LanguageStat @@ -60,7 +65,7 @@ func (stats LanguageStatList) getLanguagePercentages() map[string]float32 { return langPerc } -func (repo *Repository) getLanguageStats(e Engine) (LanguageStatList, error) { +func (repo *Repository) getLanguageStats(e db.Engine) (LanguageStatList, error) { stats := make(LanguageStatList, 0, 6) if err := e.Where("`repo_id` = ?", repo.ID).Desc("`size`").Find(&stats); err != nil { return nil, err @@ -70,12 +75,12 @@ func (repo *Repository) getLanguageStats(e Engine) (LanguageStatList, error) { // GetLanguageStats returns the language statistics for a repository func (repo *Repository) GetLanguageStats() (LanguageStatList, error) { - return repo.getLanguageStats(x) + return repo.getLanguageStats(db.GetEngine(db.DefaultContext)) } // GetTopLanguageStats returns the top language statistics for a repository func (repo *Repository) GetTopLanguageStats(limit int) (LanguageStatList, error) { - stats, err := repo.getLanguageStats(x) + stats, err := repo.getLanguageStats(db.GetEngine(db.DefaultContext)) if err != nil { return nil, err } @@ -107,7 +112,7 @@ func (repo *Repository) GetTopLanguageStats(limit int) (LanguageStatList, error) // UpdateLanguageStats updates the language statistics for repository func (repo *Repository) UpdateLanguageStats(commitID string, stats map[string]int64) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) if err := sess.Begin(); err != nil { return err } @@ -178,7 +183,7 @@ func (repo *Repository) UpdateLanguageStats(commitID string, stats map[string]in // CopyLanguageStat Copy originalRepo language stat information to destRepo (use for forked repo) func CopyLanguageStat(originalRepo, destRepo *Repository) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err diff --git a/models/repo_list.go b/models/repo_list.go index 772bd20be3871..6804a997c845c 100644 --- a/models/repo_list.go +++ b/models/repo_list.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -42,7 +43,7 @@ func RepositoryListOfMap(repoMap map[int64]*Repository) RepositoryList { return RepositoryList(valuesRepository(repoMap)) } -func (repos RepositoryList) loadAttributes(e Engine) error { +func (repos RepositoryList) loadAttributes(e db.Engine) error { if len(repos) == 0 { return nil } @@ -89,13 +90,13 @@ func (repos RepositoryList) loadAttributes(e Engine) error { // LoadAttributes loads the attributes for the given RepositoryList func (repos RepositoryList) LoadAttributes() error { - return repos.loadAttributes(x) + return repos.loadAttributes(db.GetEngine(db.DefaultContext)) } // MirrorRepositoryList contains the mirror repositories type MirrorRepositoryList []*Repository -func (repos MirrorRepositoryList) loadAttributes(e Engine) error { +func (repos MirrorRepositoryList) loadAttributes(e db.Engine) error { if len(repos) == 0 { return nil } @@ -129,12 +130,12 @@ func (repos MirrorRepositoryList) loadAttributes(e Engine) error { // LoadAttributes loads the attributes for the given MirrorRepositoryList func (repos MirrorRepositoryList) LoadAttributes() error { - return repos.loadAttributes(x) + return repos.loadAttributes(db.GetEngine(db.DefaultContext)) } // SearchRepoOptions holds the search options type SearchRepoOptions struct { - ListOptions + db.ListOptions Actor *User Keyword string OwnerID int64 @@ -409,7 +410,7 @@ func searchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond) (*x opts.OrderBy = SearchOrderBy(fmt.Sprintf("CASE WHEN owner_id = %d THEN 0 ELSE owner_id END, %s", opts.PriorityOwnerID, opts.OrderBy)) } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) var count int64 if opts.PageSize > 0 { @@ -519,7 +520,7 @@ func AccessibleRepoIDsQuery(user *User) *builder.Builder { // FindUserAccessibleRepoIDs find all accessible repositories' ID by user's id func FindUserAccessibleRepoIDs(user *User) ([]int64, error) { repoIDs := make([]int64, 0, 10) - if err := x. + if err := db.GetEngine(db.DefaultContext). Table("repository"). Cols("id"). Where(accessibleRepositoryCondition(user)). diff --git a/models/repo_list_test.go b/models/repo_list_test.go index d1fefc3e32c34..3c30cad564d67 100644 --- a/models/repo_list_test.go +++ b/models/repo_list_test.go @@ -7,17 +7,18 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) func TestSearchRepository(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // test search public repository on explore page repos, count, err := SearchRepositoryByName(&SearchRepoOptions{ - ListOptions: ListOptions{ + ListOptions: db.ListOptions{ Page: 1, PageSize: 10, }, @@ -32,7 +33,7 @@ func TestSearchRepository(t *testing.T) { assert.Equal(t, int64(1), count) repos, count, err = SearchRepositoryByName(&SearchRepoOptions{ - ListOptions: ListOptions{ + ListOptions: db.ListOptions{ Page: 1, PageSize: 10, }, @@ -46,7 +47,7 @@ func TestSearchRepository(t *testing.T) { // test search private repository on explore page repos, count, err = SearchRepositoryByName(&SearchRepoOptions{ - ListOptions: ListOptions{ + ListOptions: db.ListOptions{ Page: 1, PageSize: 10, }, @@ -62,7 +63,7 @@ func TestSearchRepository(t *testing.T) { assert.Equal(t, int64(1), count) repos, count, err = SearchRepositoryByName(&SearchRepoOptions{ - ListOptions: ListOptions{ + ListOptions: db.ListOptions{ Page: 1, PageSize: 10, }, @@ -76,7 +77,7 @@ func TestSearchRepository(t *testing.T) { assert.Len(t, repos, 3) // Test non existing owner - repos, count, err = SearchRepositoryByName(&SearchRepoOptions{OwnerID: NonexistentID}) + repos, count, err = SearchRepositoryByName(&SearchRepoOptions{OwnerID: db.NonexistentID}) assert.NoError(t, err) assert.Empty(t, repos) @@ -84,7 +85,7 @@ func TestSearchRepository(t *testing.T) { // Test search within description repos, count, err = SearchRepository(&SearchRepoOptions{ - ListOptions: ListOptions{ + ListOptions: db.ListOptions{ Page: 1, PageSize: 10, }, @@ -101,7 +102,7 @@ func TestSearchRepository(t *testing.T) { // Test NOT search within description repos, count, err = SearchRepository(&SearchRepoOptions{ - ListOptions: ListOptions{ + ListOptions: db.ListOptions{ Page: 1, PageSize: 10, }, @@ -121,142 +122,142 @@ func TestSearchRepository(t *testing.T) { }{ { name: "PublicRepositoriesByName", - opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: ListOptions{PageSize: 10}, Collaborate: util.OptionalBoolFalse}, + opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, Collaborate: util.OptionalBoolFalse}, count: 7, }, { name: "PublicAndPrivateRepositoriesByName", - opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: ListOptions{Page: 1, PageSize: 10}, Private: true, Collaborate: util.OptionalBoolFalse}, + opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, Collaborate: util.OptionalBoolFalse}, count: 14, }, { name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitFirstPage", - opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: ListOptions{Page: 1, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, + opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, count: 14, }, { name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitSecondPage", - opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: ListOptions{Page: 2, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, + opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 2, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, count: 14, }, { name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitThirdPage", - opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, + opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, count: 14, }, { name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitFourthPage", - opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, + opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, count: 14, }, { name: "PublicRepositoriesOfUser", - opts: &SearchRepoOptions{ListOptions: ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Collaborate: util.OptionalBoolFalse}, + opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Collaborate: util.OptionalBoolFalse}, count: 2, }, { name: "PublicRepositoriesOfUser2", - opts: &SearchRepoOptions{ListOptions: ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Collaborate: util.OptionalBoolFalse}, + opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Collaborate: util.OptionalBoolFalse}, count: 0, }, { name: "PublicRepositoriesOfUser3", - opts: &SearchRepoOptions{ListOptions: ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Collaborate: util.OptionalBoolFalse}, + opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Collaborate: util.OptionalBoolFalse}, count: 2, }, { name: "PublicAndPrivateRepositoriesOfUser", - opts: &SearchRepoOptions{ListOptions: ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, Collaborate: util.OptionalBoolFalse}, + opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, Collaborate: util.OptionalBoolFalse}, count: 4, }, { name: "PublicAndPrivateRepositoriesOfUser2", - opts: &SearchRepoOptions{ListOptions: ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, Collaborate: util.OptionalBoolFalse}, + opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, Collaborate: util.OptionalBoolFalse}, count: 0, }, { name: "PublicAndPrivateRepositoriesOfUser3", - opts: &SearchRepoOptions{ListOptions: ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true, Collaborate: util.OptionalBoolFalse}, + opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true, Collaborate: util.OptionalBoolFalse}, count: 4, }, { name: "PublicRepositoriesOfUserIncludingCollaborative", - opts: &SearchRepoOptions{ListOptions: ListOptions{Page: 1, PageSize: 10}, OwnerID: 15}, + opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15}, count: 5, }, { name: "PublicRepositoriesOfUser2IncludingCollaborative", - opts: &SearchRepoOptions{ListOptions: ListOptions{Page: 1, PageSize: 10}, OwnerID: 18}, + opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18}, count: 1, }, { name: "PublicRepositoriesOfUser3IncludingCollaborative", - opts: &SearchRepoOptions{ListOptions: ListOptions{Page: 1, PageSize: 10}, OwnerID: 20}, + opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20}, count: 3, }, { name: "PublicAndPrivateRepositoriesOfUserIncludingCollaborative", - opts: &SearchRepoOptions{ListOptions: ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true}, + opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true}, count: 9, }, { name: "PublicAndPrivateRepositoriesOfUser2IncludingCollaborative", - opts: &SearchRepoOptions{ListOptions: ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true}, + opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true}, count: 4, }, { name: "PublicAndPrivateRepositoriesOfUser3IncludingCollaborative", - opts: &SearchRepoOptions{ListOptions: ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true}, + opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true}, count: 7, }, { name: "PublicRepositoriesOfOrganization", - opts: &SearchRepoOptions{ListOptions: ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Collaborate: util.OptionalBoolFalse}, + opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Collaborate: util.OptionalBoolFalse}, count: 1, }, { name: "PublicAndPrivateRepositoriesOfOrganization", - opts: &SearchRepoOptions{ListOptions: ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Private: true, Collaborate: util.OptionalBoolFalse}, + opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Private: true, Collaborate: util.OptionalBoolFalse}, count: 2, }, { name: "AllPublic/PublicRepositoriesByName", - opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: ListOptions{PageSize: 10}, AllPublic: true, Collaborate: util.OptionalBoolFalse}, + opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, AllPublic: true, Collaborate: util.OptionalBoolFalse}, count: 7, }, { name: "AllPublic/PublicAndPrivateRepositoriesByName", - opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: ListOptions{Page: 1, PageSize: 10}, Private: true, AllPublic: true, Collaborate: util.OptionalBoolFalse}, + opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, AllPublic: true, Collaborate: util.OptionalBoolFalse}, count: 14, }, { name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative", - opts: &SearchRepoOptions{ListOptions: ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse}, + opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse}, count: 28, }, { name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative", - opts: &SearchRepoOptions{ListOptions: ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: util.OptionalBoolFalse}, + opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: util.OptionalBoolFalse}, count: 33, }, { name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName", - opts: &SearchRepoOptions{Keyword: "test", ListOptions: ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true}, + opts: &SearchRepoOptions{Keyword: "test", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true}, count: 15, }, { name: "AllPublic/PublicAndPrivateRepositoriesOfUser2IncludingCollaborativeByName", - opts: &SearchRepoOptions{Keyword: "test", ListOptions: ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, AllPublic: true}, + opts: &SearchRepoOptions{Keyword: "test", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, AllPublic: true}, count: 13, }, { name: "AllPublic/PublicRepositoriesOfOrganization", - opts: &SearchRepoOptions{ListOptions: ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse, Template: util.OptionalBoolFalse}, + opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse, Template: util.OptionalBoolFalse}, count: 28, }, { name: "AllTemplates", - opts: &SearchRepoOptions{ListOptions: ListOptions{Page: 1, PageSize: 10}, Template: util.OptionalBoolTrue}, + opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Template: util.OptionalBoolTrue}, count: 2, }, } @@ -323,7 +324,7 @@ func TestSearchRepository(t *testing.T) { } func TestSearchRepositoryByTopicName(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testCases := []struct { name string diff --git a/models/repo_mirror.go b/models/repo_mirror.go index cd1f74cb24691..35685b3220962 100644 --- a/models/repo_mirror.go +++ b/models/repo_mirror.go @@ -8,6 +8,7 @@ package models import ( "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" @@ -37,6 +38,10 @@ type Mirror struct { Address string `xorm:"-"` } +func init() { + db.RegisterModel(new(Mirror)) +} + // BeforeInsert will be invoked by XORM before inserting a record func (m *Mirror) BeforeInsert() { if m != nil { @@ -77,7 +82,7 @@ func (m *Mirror) ScheduleNextUpdate() { } } -func getMirrorByRepoID(e Engine, repoID int64) (*Mirror, error) { +func getMirrorByRepoID(e db.Engine, repoID int64) (*Mirror, error) { m := &Mirror{RepoID: repoID} has, err := e.Get(m) if err != nil { @@ -90,28 +95,28 @@ func getMirrorByRepoID(e Engine, repoID int64) (*Mirror, error) { // GetMirrorByRepoID returns mirror information of a repository. func GetMirrorByRepoID(repoID int64) (*Mirror, error) { - return getMirrorByRepoID(x, repoID) + return getMirrorByRepoID(db.GetEngine(db.DefaultContext), repoID) } -func updateMirror(e Engine, m *Mirror) error { +func updateMirror(e db.Engine, m *Mirror) error { _, err := e.ID(m.ID).AllCols().Update(m) return err } // UpdateMirror updates the mirror func UpdateMirror(m *Mirror) error { - return updateMirror(x, m) + return updateMirror(db.GetEngine(db.DefaultContext), m) } // DeleteMirrorByRepoID deletes a mirror by repoID func DeleteMirrorByRepoID(repoID int64) error { - _, err := x.Delete(&Mirror{RepoID: repoID}) + _, err := db.GetEngine(db.DefaultContext).Delete(&Mirror{RepoID: repoID}) return err } // MirrorsIterate iterates all mirror repositories. func MirrorsIterate(f func(idx int, bean interface{}) error) error { - return x. + return db.GetEngine(db.DefaultContext). Where("next_update_unix<=?", time.Now().Unix()). And("next_update_unix!=0"). Iterate(new(Mirror), f) @@ -119,6 +124,6 @@ func MirrorsIterate(f func(idx int, bean interface{}) error) error { // InsertMirror inserts a mirror to database func InsertMirror(mirror *Mirror) error { - _, err := x.Insert(mirror) + _, err := db.GetEngine(db.DefaultContext).Insert(mirror) return err } diff --git a/models/repo_permission.go b/models/repo_permission.go index f5138fc54ce49..5ec933aa0fce8 100644 --- a/models/repo_permission.go +++ b/models/repo_permission.go @@ -7,6 +7,7 @@ package models import ( "fmt" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" ) @@ -139,10 +140,10 @@ func (p *Permission) ColorFormat(s fmt.State) { // GetUserRepoPermission returns the user permissions to the repository func GetUserRepoPermission(repo *Repository, user *User) (Permission, error) { - return getUserRepoPermission(x, repo, user) + return getUserRepoPermission(db.GetEngine(db.DefaultContext), repo, user) } -func getUserRepoPermission(e Engine, repo *Repository, user *User) (perm Permission, err error) { +func getUserRepoPermission(e db.Engine, repo *Repository, user *User) (perm Permission, err error) { if log.IsTrace() { defer func() { if user == nil { @@ -277,7 +278,7 @@ func IsUserRealRepoAdmin(repo *Repository, user *User) (bool, error) { return true, nil } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := repo.getOwner(sess); err != nil { @@ -294,10 +295,10 @@ func IsUserRealRepoAdmin(repo *Repository, user *User) (bool, error) { // IsUserRepoAdmin return true if user has admin right of a repo func IsUserRepoAdmin(repo *Repository, user *User) (bool, error) { - return isUserRepoAdmin(x, repo, user) + return isUserRepoAdmin(db.GetEngine(db.DefaultContext), repo, user) } -func isUserRepoAdmin(e Engine, repo *Repository, user *User) (bool, error) { +func isUserRepoAdmin(e db.Engine, repo *Repository, user *User) (bool, error) { if user == nil || repo == nil { return false, nil } @@ -329,16 +330,16 @@ func isUserRepoAdmin(e Engine, repo *Repository, user *User) (bool, error) { // AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the // user does not have access. func AccessLevel(user *User, repo *Repository) (AccessMode, error) { - return accessLevelUnit(x, user, repo, UnitTypeCode) + return accessLevelUnit(db.GetEngine(db.DefaultContext), user, repo, UnitTypeCode) } // AccessLevelUnit returns the Access a user has to a repository's. Will return NoneAccess if the // user does not have access. func AccessLevelUnit(user *User, repo *Repository, unitType UnitType) (AccessMode, error) { - return accessLevelUnit(x, user, repo, unitType) + return accessLevelUnit(db.GetEngine(db.DefaultContext), user, repo, unitType) } -func accessLevelUnit(e Engine, user *User, repo *Repository, unitType UnitType) (AccessMode, error) { +func accessLevelUnit(e db.Engine, user *User, repo *Repository, unitType UnitType) (AccessMode, error) { perm, err := getUserRepoPermission(e, repo, user) if err != nil { return AccessModeNone, err @@ -346,24 +347,24 @@ func accessLevelUnit(e Engine, user *User, repo *Repository, unitType UnitType) return perm.UnitAccessMode(unitType), nil } -func hasAccessUnit(e Engine, user *User, repo *Repository, unitType UnitType, testMode AccessMode) (bool, error) { +func hasAccessUnit(e db.Engine, user *User, repo *Repository, unitType UnitType, testMode AccessMode) (bool, error) { mode, err := accessLevelUnit(e, user, repo, unitType) return testMode <= mode, err } // HasAccessUnit returns true if user has testMode to the unit of the repository func HasAccessUnit(user *User, repo *Repository, unitType UnitType, testMode AccessMode) (bool, error) { - return hasAccessUnit(x, user, repo, unitType, testMode) + return hasAccessUnit(db.GetEngine(db.DefaultContext), user, repo, unitType, testMode) } // CanBeAssigned return true if user can be assigned to issue or pull requests in repo // Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface. // FIXME: user could send PullRequest also could be assigned??? func CanBeAssigned(user *User, repo *Repository, isPull bool) (bool, error) { - return canBeAssigned(x, user, repo, isPull) + return canBeAssigned(db.GetEngine(db.DefaultContext), user, repo, isPull) } -func canBeAssigned(e Engine, user *User, repo *Repository, _ bool) (bool, error) { +func canBeAssigned(e db.Engine, user *User, repo *Repository, _ bool) (bool, error) { if user.IsOrganization() { return false, fmt.Errorf("Organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID) } @@ -374,7 +375,7 @@ func canBeAssigned(e Engine, user *User, repo *Repository, _ bool) (bool, error) return perm.CanAccessAny(AccessModeWrite, UnitTypeCode, UnitTypeIssues, UnitTypePullRequests), nil } -func hasAccess(e Engine, userID int64, repo *Repository) (bool, error) { +func hasAccess(e db.Engine, userID int64, repo *Repository) (bool, error) { var user *User var err error if userID > 0 { @@ -392,7 +393,7 @@ func hasAccess(e Engine, userID int64, repo *Repository) (bool, error) { // HasAccess returns true if user has access to repo func HasAccess(userID int64, repo *Repository) (bool, error) { - return hasAccess(x, userID, repo) + return hasAccess(db.GetEngine(db.DefaultContext), userID, repo) } // FilterOutRepoIdsWithoutUnitAccess filter out repos where user has no access to repositories diff --git a/models/repo_permission_test.go b/models/repo_permission_test.go index 0f350e62aad98..1fbf1b9f8fa31 100644 --- a/models/repo_permission_test.go +++ b/models/repo_permission_test.go @@ -7,18 +7,19 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestRepoPermissionPublicNonOrgRepo(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // public non-organization repo - repo := AssertExistsAndLoadBean(t, &Repository{ID: 4}).(*Repository) - assert.NoError(t, repo.getUnits(x)) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 4}).(*Repository) + assert.NoError(t, repo.getUnits(db.GetEngine(db.DefaultContext))) // plain user - user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + user := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) perm, err := GetUserRepoPermission(repo, user) assert.NoError(t, err) for _, unit := range repo.Units { @@ -36,7 +37,7 @@ func TestRepoPermissionPublicNonOrgRepo(t *testing.T) { } // collaborator - collaborator := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) + collaborator := db.AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) perm, err = GetUserRepoPermission(repo, collaborator) assert.NoError(t, err) for _, unit := range repo.Units { @@ -45,7 +46,7 @@ func TestRepoPermissionPublicNonOrgRepo(t *testing.T) { } // owner - owner := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) + owner := db.AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) perm, err = GetUserRepoPermission(repo, owner) assert.NoError(t, err) for _, unit := range repo.Units { @@ -54,7 +55,7 @@ func TestRepoPermissionPublicNonOrgRepo(t *testing.T) { } // admin - admin := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + admin := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) perm, err = GetUserRepoPermission(repo, admin) assert.NoError(t, err) for _, unit := range repo.Units { @@ -64,14 +65,14 @@ func TestRepoPermissionPublicNonOrgRepo(t *testing.T) { } func TestRepoPermissionPrivateNonOrgRepo(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // private non-organization repo - repo := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) - assert.NoError(t, repo.getUnits(x)) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) + assert.NoError(t, repo.getUnits(db.GetEngine(db.DefaultContext))) // plain user - user := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) + user := db.AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) perm, err := GetUserRepoPermission(repo, user) assert.NoError(t, err) for _, unit := range repo.Units { @@ -97,7 +98,7 @@ func TestRepoPermissionPrivateNonOrgRepo(t *testing.T) { } // owner - owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + owner := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) perm, err = GetUserRepoPermission(repo, owner) assert.NoError(t, err) for _, unit := range repo.Units { @@ -106,7 +107,7 @@ func TestRepoPermissionPrivateNonOrgRepo(t *testing.T) { } // admin - admin := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + admin := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) perm, err = GetUserRepoPermission(repo, admin) assert.NoError(t, err) for _, unit := range repo.Units { @@ -116,14 +117,14 @@ func TestRepoPermissionPrivateNonOrgRepo(t *testing.T) { } func TestRepoPermissionPublicOrgRepo(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // public organization repo - repo := AssertExistsAndLoadBean(t, &Repository{ID: 32}).(*Repository) - assert.NoError(t, repo.getUnits(x)) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 32}).(*Repository) + assert.NoError(t, repo.getUnits(db.GetEngine(db.DefaultContext))) // plain user - user := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) + user := db.AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) perm, err := GetUserRepoPermission(repo, user) assert.NoError(t, err) for _, unit := range repo.Units { @@ -149,7 +150,7 @@ func TestRepoPermissionPublicOrgRepo(t *testing.T) { } // org member team owner - owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + owner := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) perm, err = GetUserRepoPermission(repo, owner) assert.NoError(t, err) for _, unit := range repo.Units { @@ -158,7 +159,7 @@ func TestRepoPermissionPublicOrgRepo(t *testing.T) { } // org member team tester - member := AssertExistsAndLoadBean(t, &User{ID: 15}).(*User) + member := db.AssertExistsAndLoadBean(t, &User{ID: 15}).(*User) perm, err = GetUserRepoPermission(repo, member) assert.NoError(t, err) for _, unit := range repo.Units { @@ -168,7 +169,7 @@ func TestRepoPermissionPublicOrgRepo(t *testing.T) { assert.False(t, perm.CanWrite(UnitTypeCode)) // admin - admin := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + admin := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) perm, err = GetUserRepoPermission(repo, admin) assert.NoError(t, err) for _, unit := range repo.Units { @@ -178,14 +179,14 @@ func TestRepoPermissionPublicOrgRepo(t *testing.T) { } func TestRepoPermissionPrivateOrgRepo(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // private organization repo - repo := AssertExistsAndLoadBean(t, &Repository{ID: 24}).(*Repository) - assert.NoError(t, repo.getUnits(x)) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 24}).(*Repository) + assert.NoError(t, repo.getUnits(db.GetEngine(db.DefaultContext))) // plain user - user := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) + user := db.AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) perm, err := GetUserRepoPermission(repo, user) assert.NoError(t, err) for _, unit := range repo.Units { @@ -211,7 +212,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) { } // org member team owner - owner := AssertExistsAndLoadBean(t, &User{ID: 15}).(*User) + owner := db.AssertExistsAndLoadBean(t, &User{ID: 15}).(*User) perm, err = GetUserRepoPermission(repo, owner) assert.NoError(t, err) for _, unit := range repo.Units { @@ -220,7 +221,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) { } // update team information and then check permission - team := AssertExistsAndLoadBean(t, &Team{ID: 5}).(*Team) + team := db.AssertExistsAndLoadBean(t, &Team{ID: 5}).(*Team) err = UpdateTeamUnits(team, nil) assert.NoError(t, err) perm, err = GetUserRepoPermission(repo, owner) @@ -231,7 +232,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) { } // org member team tester - tester := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + tester := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) perm, err = GetUserRepoPermission(repo, tester) assert.NoError(t, err) assert.True(t, perm.CanWrite(UnitTypeIssues)) @@ -239,7 +240,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) { assert.False(t, perm.CanRead(UnitTypeCode)) // org member team reviewer - reviewer := AssertExistsAndLoadBean(t, &User{ID: 20}).(*User) + reviewer := db.AssertExistsAndLoadBean(t, &User{ID: 20}).(*User) perm, err = GetUserRepoPermission(repo, reviewer) assert.NoError(t, err) assert.False(t, perm.CanRead(UnitTypeIssues)) @@ -247,7 +248,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) { assert.True(t, perm.CanRead(UnitTypeCode)) // admin - admin := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + admin := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) perm, err = GetUserRepoPermission(repo, admin) assert.NoError(t, err) for _, unit := range repo.Units { diff --git a/models/repo_pushmirror.go b/models/repo_pushmirror.go index 439972ffa6cc5..c6207bae6df69 100644 --- a/models/repo_pushmirror.go +++ b/models/repo_pushmirror.go @@ -8,6 +8,7 @@ import ( "errors" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" @@ -32,6 +33,10 @@ type PushMirror struct { LastError string `xorm:"text"` } +func init() { + db.RegisterModel(new(PushMirror)) +} + // AfterLoad is invoked from XORM after setting the values of all fields of this object. func (m *PushMirror) AfterLoad(session *xorm.Session) { if m == nil { @@ -57,32 +62,32 @@ func (m *PushMirror) GetRemoteName() string { // InsertPushMirror inserts a push-mirror to database func InsertPushMirror(m *PushMirror) error { - _, err := x.Insert(m) + _, err := db.GetEngine(db.DefaultContext).Insert(m) return err } // UpdatePushMirror updates the push-mirror func UpdatePushMirror(m *PushMirror) error { - _, err := x.ID(m.ID).AllCols().Update(m) + _, err := db.GetEngine(db.DefaultContext).ID(m.ID).AllCols().Update(m) return err } // DeletePushMirrorByID deletes a push-mirrors by ID func DeletePushMirrorByID(ID int64) error { - _, err := x.ID(ID).Delete(&PushMirror{}) + _, err := db.GetEngine(db.DefaultContext).ID(ID).Delete(&PushMirror{}) return err } // DeletePushMirrorsByRepoID deletes all push-mirrors by repoID func DeletePushMirrorsByRepoID(repoID int64) error { - _, err := x.Delete(&PushMirror{RepoID: repoID}) + _, err := db.GetEngine(db.DefaultContext).Delete(&PushMirror{RepoID: repoID}) return err } // GetPushMirrorByID returns push-mirror information. func GetPushMirrorByID(ID int64) (*PushMirror, error) { m := &PushMirror{} - has, err := x.ID(ID).Get(m) + has, err := db.GetEngine(db.DefaultContext).ID(ID).Get(m) if err != nil { return nil, err } else if !has { @@ -94,12 +99,12 @@ func GetPushMirrorByID(ID int64) (*PushMirror, error) { // GetPushMirrorsByRepoID returns push-mirror information of a repository. func GetPushMirrorsByRepoID(repoID int64) ([]*PushMirror, error) { mirrors := make([]*PushMirror, 0, 10) - return mirrors, x.Where("repo_id=?", repoID).Find(&mirrors) + return mirrors, db.GetEngine(db.DefaultContext).Where("repo_id=?", repoID).Find(&mirrors) } // PushMirrorsIterate iterates all push-mirror repositories. func PushMirrorsIterate(f func(idx int, bean interface{}) error) error { - return x. + return db.GetEngine(db.DefaultContext). Where("last_update + (`interval` / ?) <= ?", time.Second, time.Now().Unix()). And("`interval` != 0"). Iterate(new(PushMirror), f) diff --git a/models/repo_pushmirror_test.go b/models/repo_pushmirror_test.go index 66c499b1c359d..65ef918141637 100644 --- a/models/repo_pushmirror_test.go +++ b/models/repo_pushmirror_test.go @@ -8,13 +8,14 @@ import ( "testing" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" "github.com/stretchr/testify/assert" ) func TestPushMirrorsIterate(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) now := timeutil.TimeStampNow() diff --git a/models/repo_redirect.go b/models/repo_redirect.go index afbfeb499e11a..18422f9d18e20 100644 --- a/models/repo_redirect.go +++ b/models/repo_redirect.go @@ -6,6 +6,8 @@ package models import ( "strings" + + "code.gitea.io/gitea/models/db" ) // RepoRedirect represents that a repo name should be redirected to another @@ -16,11 +18,15 @@ type RepoRedirect struct { RedirectRepoID int64 // repoID to redirect to } +func init() { + db.RegisterModel(new(RepoRedirect)) +} + // LookupRepoRedirect look up if a repository has a redirect name func LookupRepoRedirect(ownerID int64, repoName string) (int64, error) { repoName = strings.ToLower(repoName) redirect := &RepoRedirect{OwnerID: ownerID, LowerName: repoName} - if has, err := x.Get(redirect); err != nil { + if has, err := db.GetEngine(db.DefaultContext).Get(redirect); err != nil { return 0, err } else if !has { return 0, ErrRepoRedirectNotExist{OwnerID: ownerID, RepoName: repoName} @@ -29,7 +35,7 @@ func LookupRepoRedirect(ownerID int64, repoName string) (int64, error) { } // newRepoRedirect create a new repo redirect -func newRepoRedirect(e Engine, ownerID, repoID int64, oldRepoName, newRepoName string) error { +func newRepoRedirect(e db.Engine, ownerID, repoID int64, oldRepoName, newRepoName string) error { oldRepoName = strings.ToLower(oldRepoName) newRepoName = strings.ToLower(newRepoName) @@ -49,7 +55,7 @@ func newRepoRedirect(e Engine, ownerID, repoID int64, oldRepoName, newRepoName s // deleteRepoRedirect delete any redirect from the specified repo name to // anything else -func deleteRepoRedirect(e Engine, ownerID int64, repoName string) error { +func deleteRepoRedirect(e db.Engine, ownerID int64, repoName string) error { repoName = strings.ToLower(repoName) _, err := e.Delete(&RepoRedirect{OwnerID: ownerID, LowerName: repoName}) return err diff --git a/models/repo_redirect_test.go b/models/repo_redirect_test.go index 4c3184a0fd479..9400422752cb9 100644 --- a/models/repo_redirect_test.go +++ b/models/repo_redirect_test.go @@ -7,33 +7,34 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestLookupRepoRedirect(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) repoID, err := LookupRepoRedirect(2, "oldrepo1") assert.NoError(t, err) assert.EqualValues(t, 1, repoID) - _, err = LookupRepoRedirect(NonexistentID, "doesnotexist") + _, err = LookupRepoRedirect(db.NonexistentID, "doesnotexist") assert.True(t, IsErrRepoRedirectNotExist(err)) } func TestNewRepoRedirect(t *testing.T) { // redirect to a completely new name - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - assert.NoError(t, newRepoRedirect(x, repo.OwnerID, repo.ID, repo.Name, "newreponame")) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + assert.NoError(t, newRepoRedirect(db.GetEngine(db.DefaultContext), repo.OwnerID, repo.ID, repo.Name, "newreponame")) - AssertExistsAndLoadBean(t, &RepoRedirect{ + db.AssertExistsAndLoadBean(t, &RepoRedirect{ OwnerID: repo.OwnerID, LowerName: repo.LowerName, RedirectRepoID: repo.ID, }) - AssertExistsAndLoadBean(t, &RepoRedirect{ + db.AssertExistsAndLoadBean(t, &RepoRedirect{ OwnerID: repo.OwnerID, LowerName: "oldrepo1", RedirectRepoID: repo.ID, @@ -42,17 +43,17 @@ func TestNewRepoRedirect(t *testing.T) { func TestNewRepoRedirect2(t *testing.T) { // redirect to previously used name - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - assert.NoError(t, newRepoRedirect(x, repo.OwnerID, repo.ID, repo.Name, "oldrepo1")) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + assert.NoError(t, newRepoRedirect(db.GetEngine(db.DefaultContext), repo.OwnerID, repo.ID, repo.Name, "oldrepo1")) - AssertExistsAndLoadBean(t, &RepoRedirect{ + db.AssertExistsAndLoadBean(t, &RepoRedirect{ OwnerID: repo.OwnerID, LowerName: repo.LowerName, RedirectRepoID: repo.ID, }) - AssertNotExistsBean(t, &RepoRedirect{ + db.AssertNotExistsBean(t, &RepoRedirect{ OwnerID: repo.OwnerID, LowerName: "oldrepo1", RedirectRepoID: repo.ID, @@ -61,12 +62,12 @@ func TestNewRepoRedirect2(t *testing.T) { func TestNewRepoRedirect3(t *testing.T) { // redirect for a previously-unredirected repo - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) - assert.NoError(t, newRepoRedirect(x, repo.OwnerID, repo.ID, repo.Name, "newreponame")) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) + assert.NoError(t, newRepoRedirect(db.GetEngine(db.DefaultContext), repo.OwnerID, repo.ID, repo.Name, "newreponame")) - AssertExistsAndLoadBean(t, &RepoRedirect{ + db.AssertExistsAndLoadBean(t, &RepoRedirect{ OwnerID: repo.OwnerID, LowerName: repo.LowerName, RedirectRepoID: repo.ID, diff --git a/models/repo_sign.go b/models/repo_sign.go index be9309ed4eac1..f7a303b0c1242 100644 --- a/models/repo_sign.go +++ b/models/repo_sign.go @@ -7,6 +7,8 @@ package models import ( "strings" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" @@ -120,7 +122,7 @@ Loop: case always: break Loop case pubkey: - keys, err := ListGPGKeys(u.ID, ListOptions{}) + keys, err := ListGPGKeys(u.ID, db.ListOptions{}) if err != nil { return false, "", nil, err } @@ -128,8 +130,8 @@ Loop: return false, "", nil, &ErrWontSign{pubkey} } case twofa: - twofaModel, err := GetTwoFactorByUID(u.ID) - if err != nil && !IsErrTwoFactorNotEnrolled(err) { + twofaModel, err := login.GetTwoFactorByUID(u.ID) + if err != nil && !login.IsErrTwoFactorNotEnrolled(err) { return false, "", nil, err } if twofaModel == nil { @@ -156,7 +158,7 @@ Loop: case always: break Loop case pubkey: - keys, err := ListGPGKeys(u.ID, ListOptions{}) + keys, err := ListGPGKeys(u.ID, db.ListOptions{}) if err != nil { return false, "", nil, err } @@ -164,8 +166,8 @@ Loop: return false, "", nil, &ErrWontSign{pubkey} } case twofa: - twofaModel, err := GetTwoFactorByUID(u.ID) - if err != nil && !IsErrTwoFactorNotEnrolled(err) { + twofaModel, err := login.GetTwoFactorByUID(u.ID) + if err != nil && !login.IsErrTwoFactorNotEnrolled(err) { return false, "", nil, err } if twofaModel == nil { @@ -209,7 +211,7 @@ Loop: case always: break Loop case pubkey: - keys, err := ListGPGKeys(u.ID, ListOptions{}) + keys, err := ListGPGKeys(u.ID, db.ListOptions{}) if err != nil { return false, "", nil, err } @@ -217,8 +219,8 @@ Loop: return false, "", nil, &ErrWontSign{pubkey} } case twofa: - twofaModel, err := GetTwoFactorByUID(u.ID) - if err != nil && !IsErrTwoFactorNotEnrolled(err) { + twofaModel, err := login.GetTwoFactorByUID(u.ID) + if err != nil && !login.IsErrTwoFactorNotEnrolled(err) { return false, "", nil, err } if twofaModel == nil { diff --git a/models/repo_test.go b/models/repo_test.go index 28eb9baa17db8..8073a9cd2f2ad 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -12,13 +12,14 @@ import ( "image/png" "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/markup" "github.com/stretchr/testify/assert" ) func TestMetas(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) repo := &Repository{Name: "testRepo"} repo.Owner = &User{Name: "testOwner"} @@ -66,7 +67,7 @@ func TestMetas(t *testing.T) { } func TestGetRepositoryCount(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) count, err1 := GetRepositoryCount(&User{ID: int64(10)}) privateCount, err2 := GetPrivateRepositoryCount(&User{ID: int64(10)}) @@ -79,7 +80,7 @@ func TestGetRepositoryCount(t *testing.T) { } func TestGetPublicRepositoryCount(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) count, err := GetPublicRepositoryCount(&User{ID: int64(10)}) assert.NoError(t, err) @@ -87,7 +88,7 @@ func TestGetPublicRepositoryCount(t *testing.T) { } func TestGetPrivateRepositoryCount(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) count, err := GetPrivateRepositoryCount(&User{ID: int64(10)}) assert.NoError(t, err) @@ -95,7 +96,7 @@ func TestGetPrivateRepositoryCount(t *testing.T) { } func TestUpdateRepositoryVisibilityChanged(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // Get sample repo and change visibility repo, err := GetRepositoryByID(9) @@ -108,14 +109,14 @@ func TestUpdateRepositoryVisibilityChanged(t *testing.T) { // Check visibility of action has become private act := Action{} - _, err = x.ID(3).Get(&act) + _, err = db.GetEngine(db.DefaultContext).ID(3).Get(&act) assert.NoError(t, err) assert.True(t, act.IsPrivate) } func TestGetUserFork(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // User13 has repo 11 forked from repo10 repo, err := GetRepositoryByID(10) @@ -134,8 +135,8 @@ func TestGetUserFork(t *testing.T) { } func TestRepoAPIURL(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository) + assert.NoError(t, db.PrepareTestDatabase()) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository) assert.Equal(t, "https://try.gitea.io/api/v1/repos/user12/repo10", repo.APIURL()) } @@ -146,8 +147,8 @@ func TestUploadAvatar(t *testing.T) { var buff bytes.Buffer png.Encode(&buff, myImage) - assert.NoError(t, PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository) + assert.NoError(t, db.PrepareTestDatabase()) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository) err := repo.UploadAvatar(buff.Bytes()) assert.NoError(t, err) @@ -160,8 +161,8 @@ func TestUploadBigAvatar(t *testing.T) { var buff bytes.Buffer png.Encode(&buff, myImage) - assert.NoError(t, PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository) + assert.NoError(t, db.PrepareTestDatabase()) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository) err := repo.UploadAvatar(buff.Bytes()) assert.Error(t, err) @@ -173,8 +174,8 @@ func TestDeleteAvatar(t *testing.T) { var buff bytes.Buffer png.Encode(&buff, myImage) - assert.NoError(t, PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository) + assert.NoError(t, db.PrepareTestDatabase()) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository) err := repo.UploadAvatar(buff.Bytes()) assert.NoError(t, err) @@ -186,37 +187,37 @@ func TestDeleteAvatar(t *testing.T) { } func TestDoctorUserStarNum(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) assert.NoError(t, DoctorUserStarNum()) } func TestRepoGetReviewers(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // test public repo - repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + repo1 := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) reviewers, err := repo1.GetReviewers(2, 2) assert.NoError(t, err) assert.Len(t, reviewers, 4) // test private repo - repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) + repo2 := db.AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) reviewers, err = repo2.GetReviewers(2, 2) assert.NoError(t, err) assert.Empty(t, reviewers) } func TestRepoGetReviewerTeams(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) + repo2 := db.AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) teams, err := repo2.GetReviewerTeams() assert.NoError(t, err) assert.Empty(t, teams) - repo3 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) + repo3 := db.AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) teams, err = repo3.GetReviewerTeams() assert.NoError(t, err) assert.Len(t, teams, 2) diff --git a/models/repo_transfer.go b/models/repo_transfer.go index 09b6290293d57..082c3d19dc041 100644 --- a/models/repo_transfer.go +++ b/models/repo_transfer.go @@ -8,6 +8,7 @@ import ( "fmt" "os" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -28,6 +29,10 @@ type RepoTransfer struct { UpdatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL updated"` } +func init() { + db.RegisterModel(new(RepoTransfer)) +} + // LoadAttributes fetches the transfer recipient from the database func (r *RepoTransfer) LoadAttributes() error { if r.Recipient == nil { @@ -93,7 +98,7 @@ func (r *RepoTransfer) CanUserAcceptTransfer(u *User) bool { func GetPendingRepositoryTransfer(repo *Repository) (*RepoTransfer, error) { transfer := new(RepoTransfer) - has, err := x.Where("repo_id = ? ", repo.ID).Get(transfer) + has, err := db.GetEngine(db.DefaultContext).Where("repo_id = ? ", repo.ID).Get(transfer) if err != nil { return nil, err } @@ -105,7 +110,7 @@ func GetPendingRepositoryTransfer(repo *Repository) (*RepoTransfer, error) { return transfer, nil } -func deleteRepositoryTransfer(e Engine, repoID int64) error { +func deleteRepositoryTransfer(e db.Engine, repoID int64) error { _, err := e.Where("repo_id = ?", repoID).Delete(&RepoTransfer{}) return err } @@ -113,7 +118,7 @@ func deleteRepositoryTransfer(e Engine, repoID int64) error { // CancelRepositoryTransfer marks the repository as ready and remove pending transfer entry, // thus cancel the transfer process. func CancelRepositoryTransfer(repo *Repository) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -145,7 +150,7 @@ func TestRepositoryReadyForTransfer(status RepositoryStatus) error { // CreatePendingRepositoryTransfer transfer a repo from one owner to a new one. // it marks the repository transfer as "pending" func CreatePendingRepositoryTransfer(doer, newOwner *User, repoID int64, teams []*Team) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -227,7 +232,7 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err e } }() - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return fmt.Errorf("sess.Begin: %v", err) @@ -261,7 +266,7 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err e } // Remove redundant collaborators. - collaborators, err := repo.getCollaborators(sess, ListOptions{}) + collaborators, err := repo.getCollaborators(sess, db.ListOptions{}) if err != nil { return fmt.Errorf("getCollaborators: %v", err) } @@ -269,6 +274,14 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err e // Dummy object. collaboration := &Collaboration{RepoID: repo.ID} for _, c := range collaborators { + if c.IsGhost() { + collaboration.ID = c.Collaboration.ID + if _, err := sess.Delete(collaboration); err != nil { + return fmt.Errorf("remove collaborator '%d': %v", c.ID, err) + } + collaboration.ID = 0 + } + if c.ID != newOwner.ID { isMember, err := isOrganizationMember(sess, newOwner.ID, c.ID) if err != nil { @@ -281,6 +294,7 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err e if _, err := sess.Delete(collaboration); err != nil { return fmt.Errorf("remove collaborator '%d': %v", c.ID, err) } + collaboration.UserID = 0 } // Remove old team-repository relations. diff --git a/models/repo_transfer_test.go b/models/repo_transfer_test.go index 8b1aba896be50..4c6b7254c21a0 100644 --- a/models/repo_transfer_test.go +++ b/models/repo_transfer_test.go @@ -7,14 +7,15 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestRepositoryTransfer(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - doer := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) + doer := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) transfer, err := GetPendingRepositoryTransfer(repo) assert.NoError(t, err) @@ -28,7 +29,7 @@ func TestRepositoryTransfer(t *testing.T) { assert.Nil(t, transfer) assert.True(t, IsErrNoPendingTransfer(err)) - user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + user2 := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) assert.NoError(t, CreatePendingRepositoryTransfer(doer, user2, repo.ID, nil)) @@ -37,7 +38,7 @@ func TestRepositoryTransfer(t *testing.T) { assert.NoError(t, transfer.LoadAttributes()) assert.Equal(t, "user2", transfer.Recipient.Name) - user6 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + user6 := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) // Only transfer can be started at any given time err = CreatePendingRepositoryTransfer(doer, user6, repo.ID, nil) diff --git a/models/repo_unit.go b/models/repo_unit.go index 2a20205fe4962..474f65bf03352 100644 --- a/models/repo_unit.go +++ b/models/repo_unit.go @@ -7,6 +7,8 @@ package models import ( "fmt" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/timeutil" @@ -23,6 +25,10 @@ type RepoUnit struct { CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"` } +func init() { + db.RegisterModel(new(RepoUnit)) +} + // UnitConfig describes common unit config type UnitConfig struct{} @@ -148,7 +154,7 @@ func (cfg *PullRequestsConfig) AllowedMergeStyleCount() int { func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) { switch colName { case "type": - switch UnitType(Cell2Int64(val)) { + switch UnitType(login.Cell2Int64(val)) { case UnitTypeCode, UnitTypeReleases, UnitTypeWiki, UnitTypeProjects: r.Config = new(UnitConfig) case UnitTypeExternalWiki: @@ -200,7 +206,7 @@ func (r *RepoUnit) ExternalTrackerConfig() *ExternalTrackerConfig { return r.Config.(*ExternalTrackerConfig) } -func getUnitsByRepoID(e Engine, repoID int64) (units []*RepoUnit, err error) { +func getUnitsByRepoID(e db.Engine, repoID int64) (units []*RepoUnit, err error) { var tmpUnits []*RepoUnit if err := e.Where("repo_id = ?", repoID).Find(&tmpUnits); err != nil { return nil, err @@ -214,3 +220,9 @@ func getUnitsByRepoID(e Engine, repoID int64) (units []*RepoUnit, err error) { return units, nil } + +// UpdateRepoUnit updates the provided repo unit +func UpdateRepoUnit(unit *RepoUnit) error { + _, err := db.GetEngine(db.DefaultContext).ID(unit.ID).Update(unit) + return err +} diff --git a/models/repo_watch.go b/models/repo_watch.go index 656696b34f2e8..b37d47874e343 100644 --- a/models/repo_watch.go +++ b/models/repo_watch.go @@ -7,6 +7,7 @@ package models import ( "fmt" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" ) @@ -35,8 +36,12 @@ type Watch struct { UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } +func init() { + db.RegisterModel(new(Watch)) +} + // getWatch gets what kind of subscription a user has on a given repository; returns dummy record if none found -func getWatch(e Engine, userID, repoID int64) (Watch, error) { +func getWatch(e db.Engine, userID, repoID int64) (Watch, error) { watch := Watch{UserID: userID, RepoID: repoID} has, err := e.Get(&watch) if err != nil { @@ -55,11 +60,11 @@ func isWatchMode(mode RepoWatchMode) bool { // IsWatching checks if user has watched given repository. func IsWatching(userID, repoID int64) bool { - watch, err := getWatch(x, userID, repoID) + watch, err := getWatch(db.GetEngine(db.DefaultContext), userID, repoID) return err == nil && isWatchMode(watch.Mode) } -func watchRepoMode(e Engine, watch Watch, mode RepoWatchMode) (err error) { +func watchRepoMode(e db.Engine, watch Watch, mode RepoWatchMode) (err error) { if watch.Mode == mode { return nil } @@ -102,13 +107,13 @@ func watchRepoMode(e Engine, watch Watch, mode RepoWatchMode) (err error) { // WatchRepoMode watch repository in specific mode. func WatchRepoMode(userID, repoID int64, mode RepoWatchMode) (err error) { var watch Watch - if watch, err = getWatch(x, userID, repoID); err != nil { + if watch, err = getWatch(db.GetEngine(db.DefaultContext), userID, repoID); err != nil { return err } - return watchRepoMode(x, watch, mode) + return watchRepoMode(db.GetEngine(db.DefaultContext), watch, mode) } -func watchRepo(e Engine, userID, repoID int64, doWatch bool) (err error) { +func watchRepo(e db.Engine, userID, repoID int64, doWatch bool) (err error) { var watch Watch if watch, err = getWatch(e, userID, repoID); err != nil { return err @@ -125,10 +130,10 @@ func watchRepo(e Engine, userID, repoID int64, doWatch bool) (err error) { // WatchRepo watch or unwatch repository. func WatchRepo(userID, repoID int64, watch bool) (err error) { - return watchRepo(x, userID, repoID, watch) + return watchRepo(db.GetEngine(db.DefaultContext), userID, repoID, watch) } -func getWatchers(e Engine, repoID int64) ([]*Watch, error) { +func getWatchers(e db.Engine, repoID int64) ([]*Watch, error) { watches := make([]*Watch, 0, 10) return watches, e.Where("`watch`.repo_id=?", repoID). And("`watch`.mode<>?", RepoWatchModeDont). @@ -140,17 +145,17 @@ func getWatchers(e Engine, repoID int64) ([]*Watch, error) { // GetWatchers returns all watchers of given repository. func GetWatchers(repoID int64) ([]*Watch, error) { - return getWatchers(x, repoID) + return getWatchers(db.GetEngine(db.DefaultContext), repoID) } // GetRepoWatchersIDs returns IDs of watchers for a given repo ID // but avoids joining with `user` for performance reasons // User permissions must be verified elsewhere if required func GetRepoWatchersIDs(repoID int64) ([]int64, error) { - return getRepoWatchersIDs(x, repoID) + return getRepoWatchersIDs(db.GetEngine(db.DefaultContext), repoID) } -func getRepoWatchersIDs(e Engine, repoID int64) ([]int64, error) { +func getRepoWatchersIDs(e db.Engine, repoID int64) ([]int64, error) { ids := make([]int64, 0, 64) return ids, e.Table("watch"). Where("watch.repo_id=?", repoID). @@ -160,12 +165,12 @@ func getRepoWatchersIDs(e Engine, repoID int64) ([]int64, error) { } // GetWatchers returns range of users watching given repository. -func (repo *Repository) GetWatchers(opts ListOptions) ([]*User, error) { - sess := x.Where("watch.repo_id=?", repo.ID). +func (repo *Repository) GetWatchers(opts db.ListOptions) ([]*User, error) { + sess := db.GetEngine(db.DefaultContext).Where("watch.repo_id=?", repo.ID). Join("LEFT", "watch", "`user`.id=`watch`.user_id"). And("`watch`.mode<>?", RepoWatchModeDont) if opts.Page > 0 { - sess = opts.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, &opts) users := make([]*User, 0, opts.PageSize) return users, sess.Find(&users) @@ -175,7 +180,7 @@ func (repo *Repository) GetWatchers(opts ListOptions) ([]*User, error) { return users, sess.Find(&users) } -func notifyWatchers(e Engine, actions ...*Action) error { +func notifyWatchers(e db.Engine, actions ...*Action) error { var watchers []*Watch var repo *Repository var err error @@ -279,12 +284,12 @@ func notifyWatchers(e Engine, actions ...*Action) error { // NotifyWatchers creates batch of actions for every watcher. func NotifyWatchers(actions ...*Action) error { - return notifyWatchers(x, actions...) + return notifyWatchers(db.GetEngine(db.DefaultContext), actions...) } // NotifyWatchersActions creates batch of actions for every watcher. func NotifyWatchersActions(acts []*Action) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -297,7 +302,7 @@ func NotifyWatchersActions(acts []*Action) error { return sess.Commit() } -func watchIfAuto(e Engine, userID, repoID int64, isWrite bool) error { +func watchIfAuto(e db.Engine, userID, repoID int64, isWrite bool) error { if !isWrite || !setting.Service.AutoWatchOnChanges { return nil } @@ -313,5 +318,5 @@ func watchIfAuto(e Engine, userID, repoID int64, isWrite bool) error { // WatchIfAuto subscribes to repo if AutoWatchOnChanges is set func WatchIfAuto(userID, repoID int64, isWrite bool) error { - return watchIfAuto(x, userID, repoID, isWrite) + return watchIfAuto(db.GetEngine(db.DefaultContext), userID, repoID, isWrite) } diff --git a/models/repo_watch_test.go b/models/repo_watch_test.go index e1bbc40238d29..52222af2ca143 100644 --- a/models/repo_watch_test.go +++ b/models/repo_watch_test.go @@ -7,13 +7,14 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" ) func TestIsWatching(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) assert.True(t, IsWatching(1, 1)) assert.True(t, IsWatching(4, 1)) @@ -21,27 +22,27 @@ func TestIsWatching(t *testing.T) { assert.False(t, IsWatching(1, 5)) assert.False(t, IsWatching(8, 1)) - assert.False(t, IsWatching(NonexistentID, NonexistentID)) + assert.False(t, IsWatching(db.NonexistentID, db.NonexistentID)) } func TestWatchRepo(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) const repoID = 3 const userID = 2 assert.NoError(t, WatchRepo(userID, repoID, true)) - AssertExistsAndLoadBean(t, &Watch{RepoID: repoID, UserID: userID}) + db.AssertExistsAndLoadBean(t, &Watch{RepoID: repoID, UserID: userID}) CheckConsistencyFor(t, &Repository{ID: repoID}) assert.NoError(t, WatchRepo(userID, repoID, false)) - AssertNotExistsBean(t, &Watch{RepoID: repoID, UserID: userID}) + db.AssertNotExistsBean(t, &Watch{RepoID: repoID, UserID: userID}) CheckConsistencyFor(t, &Repository{ID: repoID}) } func TestGetWatchers(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) watches, err := GetWatchers(repo.ID) assert.NoError(t, err) // One watchers are inactive, thus minus 1 @@ -50,30 +51,30 @@ func TestGetWatchers(t *testing.T) { assert.EqualValues(t, repo.ID, watch.RepoID) } - watches, err = GetWatchers(NonexistentID) + watches, err = GetWatchers(db.NonexistentID) assert.NoError(t, err) assert.Len(t, watches, 0) } func TestRepository_GetWatchers(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - watchers, err := repo.GetWatchers(ListOptions{Page: 1}) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + watchers, err := repo.GetWatchers(db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, repo.NumWatches) for _, watcher := range watchers { - AssertExistsAndLoadBean(t, &Watch{UserID: watcher.ID, RepoID: repo.ID}) + db.AssertExistsAndLoadBean(t, &Watch{UserID: watcher.ID, RepoID: repo.ID}) } - repo = AssertExistsAndLoadBean(t, &Repository{ID: 9}).(*Repository) - watchers, err = repo.GetWatchers(ListOptions{Page: 1}) + repo = db.AssertExistsAndLoadBean(t, &Repository{ID: 9}).(*Repository) + watchers, err = repo.GetWatchers(db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, 0) } func TestNotifyWatchers(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) action := &Action{ ActUserID: 8, @@ -83,25 +84,25 @@ func TestNotifyWatchers(t *testing.T) { assert.NoError(t, NotifyWatchers(action)) // One watchers are inactive, thus action is only created for user 8, 1, 4, 11 - AssertExistsAndLoadBean(t, &Action{ + db.AssertExistsAndLoadBean(t, &Action{ ActUserID: action.ActUserID, UserID: 8, RepoID: action.RepoID, OpType: action.OpType, }) - AssertExistsAndLoadBean(t, &Action{ + db.AssertExistsAndLoadBean(t, &Action{ ActUserID: action.ActUserID, UserID: 1, RepoID: action.RepoID, OpType: action.OpType, }) - AssertExistsAndLoadBean(t, &Action{ + db.AssertExistsAndLoadBean(t, &Action{ ActUserID: action.ActUserID, UserID: 4, RepoID: action.RepoID, OpType: action.OpType, }) - AssertExistsAndLoadBean(t, &Action{ + db.AssertExistsAndLoadBean(t, &Action{ ActUserID: action.ActUserID, UserID: 11, RepoID: action.RepoID, @@ -110,10 +111,10 @@ func TestNotifyWatchers(t *testing.T) { } func TestWatchIfAuto(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - watchers, err := repo.GetWatchers(ListOptions{Page: 1}) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + watchers, err := repo.GetWatchers(db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, repo.NumWatches) @@ -123,13 +124,13 @@ func TestWatchIfAuto(t *testing.T) { // Must not add watch assert.NoError(t, WatchIfAuto(8, 1, true)) - watchers, err = repo.GetWatchers(ListOptions{Page: 1}) + watchers, err = repo.GetWatchers(db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount) // Should not add watch assert.NoError(t, WatchIfAuto(10, 1, true)) - watchers, err = repo.GetWatchers(ListOptions{Page: 1}) + watchers, err = repo.GetWatchers(db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount) @@ -137,52 +138,52 @@ func TestWatchIfAuto(t *testing.T) { // Must not add watch assert.NoError(t, WatchIfAuto(8, 1, true)) - watchers, err = repo.GetWatchers(ListOptions{Page: 1}) + watchers, err = repo.GetWatchers(db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount) // Should not add watch assert.NoError(t, WatchIfAuto(12, 1, false)) - watchers, err = repo.GetWatchers(ListOptions{Page: 1}) + watchers, err = repo.GetWatchers(db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount) // Should add watch assert.NoError(t, WatchIfAuto(12, 1, true)) - watchers, err = repo.GetWatchers(ListOptions{Page: 1}) + watchers, err = repo.GetWatchers(db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount+1) // Should remove watch, inhibit from adding auto assert.NoError(t, WatchRepo(12, 1, false)) - watchers, err = repo.GetWatchers(ListOptions{Page: 1}) + watchers, err = repo.GetWatchers(db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount) // Must not add watch assert.NoError(t, WatchIfAuto(12, 1, true)) - watchers, err = repo.GetWatchers(ListOptions{Page: 1}) + watchers, err = repo.GetWatchers(db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount) } func TestWatchRepoMode(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 0) + db.AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 0) assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeAuto)) - AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1) - AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: RepoWatchModeAuto}, 1) + db.AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1) + db.AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: RepoWatchModeAuto}, 1) assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeNormal)) - AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1) - AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: RepoWatchModeNormal}, 1) + db.AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1) + db.AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: RepoWatchModeNormal}, 1) assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeDont)) - AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1) - AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: RepoWatchModeDont}, 1) + db.AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1) + db.AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: RepoWatchModeDont}, 1) assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeNone)) - AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 0) + db.AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 0) } diff --git a/models/review.go b/models/review.go index 1ffff8feb6470..d456383d9c2d7 100644 --- a/models/review.go +++ b/models/review.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/timeutil" @@ -77,7 +78,11 @@ type Review struct { Comments []*Comment `xorm:"-"` } -func (r *Review) loadCodeComments(e Engine) (err error) { +func init() { + db.RegisterModel(new(Review)) +} + +func (r *Review) loadCodeComments(e db.Engine) (err error) { if r.CodeComments != nil { return } @@ -90,10 +95,10 @@ func (r *Review) loadCodeComments(e Engine) (err error) { // LoadCodeComments loads CodeComments func (r *Review) LoadCodeComments() error { - return r.loadCodeComments(x) + return r.loadCodeComments(db.GetEngine(db.DefaultContext)) } -func (r *Review) loadIssue(e Engine) (err error) { +func (r *Review) loadIssue(e db.Engine) (err error) { if r.Issue != nil { return } @@ -101,7 +106,7 @@ func (r *Review) loadIssue(e Engine) (err error) { return } -func (r *Review) loadReviewer(e Engine) (err error) { +func (r *Review) loadReviewer(e db.Engine) (err error) { if r.ReviewerID == 0 || r.Reviewer != nil { return } @@ -109,7 +114,7 @@ func (r *Review) loadReviewer(e Engine) (err error) { return } -func (r *Review) loadReviewerTeam(e Engine) (err error) { +func (r *Review) loadReviewerTeam(e db.Engine) (err error) { if r.ReviewerTeamID == 0 || r.ReviewerTeam != nil { return } @@ -120,15 +125,15 @@ func (r *Review) loadReviewerTeam(e Engine) (err error) { // LoadReviewer loads reviewer func (r *Review) LoadReviewer() error { - return r.loadReviewer(x) + return r.loadReviewer(db.GetEngine(db.DefaultContext)) } // LoadReviewerTeam loads reviewer team func (r *Review) LoadReviewerTeam() error { - return r.loadReviewerTeam(x) + return r.loadReviewerTeam(db.GetEngine(db.DefaultContext)) } -func (r *Review) loadAttributes(e Engine) (err error) { +func (r *Review) loadAttributes(e db.Engine) (err error) { if err = r.loadIssue(e); err != nil { return } @@ -146,10 +151,10 @@ func (r *Review) loadAttributes(e Engine) (err error) { // LoadAttributes loads all attributes except CodeComments func (r *Review) LoadAttributes() error { - return r.loadAttributes(x) + return r.loadAttributes(db.GetEngine(db.DefaultContext)) } -func getReviewByID(e Engine, id int64) (*Review, error) { +func getReviewByID(e db.Engine, id int64) (*Review, error) { review := new(Review) if has, err := e.ID(id).Get(review); err != nil { return nil, err @@ -162,12 +167,12 @@ func getReviewByID(e Engine, id int64) (*Review, error) { // GetReviewByID returns the review by the given ID func GetReviewByID(id int64) (*Review, error) { - return getReviewByID(x, id) + return getReviewByID(db.GetEngine(db.DefaultContext), id) } // FindReviewOptions represent possible filters to find reviews type FindReviewOptions struct { - ListOptions + db.ListOptions Type ReviewType IssueID int64 ReviewerID int64 @@ -191,11 +196,11 @@ func (opts *FindReviewOptions) toCond() builder.Cond { return cond } -func findReviews(e Engine, opts FindReviewOptions) ([]*Review, error) { +func findReviews(e db.Engine, opts FindReviewOptions) ([]*Review, error) { reviews := make([]*Review, 0, 10) sess := e.Where(opts.toCond()) if opts.Page > 0 { - sess = opts.ListOptions.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, &opts) } return reviews, sess. Asc("created_unix"). @@ -205,12 +210,12 @@ func findReviews(e Engine, opts FindReviewOptions) ([]*Review, error) { // FindReviews returns reviews passing FindReviewOptions func FindReviews(opts FindReviewOptions) ([]*Review, error) { - return findReviews(x, opts) + return findReviews(db.GetEngine(db.DefaultContext), opts) } // CountReviews returns count of reviews passing FindReviewOptions func CountReviews(opts FindReviewOptions) (int64, error) { - return x.Where(opts.toCond()).Count(&Review{}) + return db.GetEngine(db.DefaultContext).Where(opts.toCond()).Count(&Review{}) } // CreateReviewOptions represent the options to create a review. Type, Issue and Reviewer are required. @@ -227,10 +232,10 @@ type CreateReviewOptions struct { // IsOfficialReviewer check if at least one of the provided reviewers can make official reviews in issue (counts towards required approvals) func IsOfficialReviewer(issue *Issue, reviewers ...*User) (bool, error) { - return isOfficialReviewer(x, issue, reviewers...) + return isOfficialReviewer(db.GetEngine(db.DefaultContext), issue, reviewers...) } -func isOfficialReviewer(e Engine, issue *Issue, reviewers ...*User) (bool, error) { +func isOfficialReviewer(e db.Engine, issue *Issue, reviewers ...*User) (bool, error) { pr, err := getPullRequestByIssueID(e, issue.ID) if err != nil { return false, err @@ -254,10 +259,10 @@ func isOfficialReviewer(e Engine, issue *Issue, reviewers ...*User) (bool, error // IsOfficialReviewerTeam check if reviewer in this team can make official reviews in issue (counts towards required approvals) func IsOfficialReviewerTeam(issue *Issue, team *Team) (bool, error) { - return isOfficialReviewerTeam(x, issue, team) + return isOfficialReviewerTeam(db.GetEngine(db.DefaultContext), issue, team) } -func isOfficialReviewerTeam(e Engine, issue *Issue, team *Team) (bool, error) { +func isOfficialReviewerTeam(e db.Engine, issue *Issue, team *Team) (bool, error) { pr, err := getPullRequestByIssueID(e, issue.ID) if err != nil { return false, err @@ -276,7 +281,7 @@ func isOfficialReviewerTeam(e Engine, issue *Issue, team *Team) (bool, error) { return base.Int64sContains(pr.ProtectedBranch.ApprovalsWhitelistTeamIDs, team.ID), nil } -func createReview(e Engine, opts CreateReviewOptions) (*Review, error) { +func createReview(e db.Engine, opts CreateReviewOptions) (*Review, error) { review := &Review{ Type: opts.Type, Issue: opts.Issue, @@ -305,10 +310,10 @@ func createReview(e Engine, opts CreateReviewOptions) (*Review, error) { // CreateReview creates a new review based on opts func CreateReview(opts CreateReviewOptions) (*Review, error) { - return createReview(x, opts) + return createReview(db.GetEngine(db.DefaultContext), opts) } -func getCurrentReview(e Engine, reviewer *User, issue *Issue) (*Review, error) { +func getCurrentReview(e db.Engine, reviewer *User, issue *Issue) (*Review, error) { if reviewer == nil { return nil, nil } @@ -330,12 +335,12 @@ func getCurrentReview(e Engine, reviewer *User, issue *Issue) (*Review, error) { // ReviewExists returns whether a review exists for a particular line of code in the PR func ReviewExists(issue *Issue, treePath string, line int64) (bool, error) { - return x.Cols("id").Exist(&Comment{IssueID: issue.ID, TreePath: treePath, Line: line, Type: CommentTypeCode}) + return db.GetEngine(db.DefaultContext).Cols("id").Exist(&Comment{IssueID: issue.ID, TreePath: treePath, Line: line, Type: CommentTypeCode}) } // GetCurrentReview returns the current pending review of reviewer for given issue func GetCurrentReview(reviewer *User, issue *Issue) (*Review, error) { - return getCurrentReview(x, reviewer, issue) + return getCurrentReview(db.GetEngine(db.DefaultContext), reviewer, issue) } // ContentEmptyErr represents an content empty error @@ -353,7 +358,7 @@ func IsContentEmptyErr(err error) bool { // SubmitReview creates a review out of the existing pending review or creates a new one if no pending review exist func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content, commitID string, stale bool, attachmentUUIDs []string) (*Review, *Comment, error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return nil, nil, err @@ -439,7 +444,7 @@ func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content, comm // try to remove team review request if need if issue.Repo.Owner.IsOrganization() && (reviewType == ReviewTypeApprove || reviewType == ReviewTypeReject) { teamReviewRequests := make([]*Review, 0, 10) - if err := sess.SQL("SELECT * FROM review WHERE reviewer_team_id > 0 AND type = ?", ReviewTypeRequest).Find(&teamReviewRequests); err != nil { + if err := sess.SQL("SELECT * FROM review WHERE issue_id = ? AND reviewer_team_id > 0 AND type = ?", issue.ID, ReviewTypeRequest).Find(&teamReviewRequests); err != nil { return nil, nil, err } @@ -465,7 +470,7 @@ func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content, comm func GetReviewersByIssueID(issueID int64) ([]*Review, error) { reviews := make([]*Review, 0, 10) - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return nil, err @@ -497,7 +502,7 @@ func GetReviewersFromOriginalAuthorsByIssueID(issueID int64) ([]*Review, error) reviews := make([]*Review, 0, 10) // Get latest review of each reviewer, sorted in order they were made - if err := x.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = 0 AND type in (?, ?, ?) AND original_author_id <> 0 GROUP BY issue_id, original_author_id) ORDER BY review.updated_unix ASC", + if err := db.GetEngine(db.DefaultContext).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = 0 AND type in (?, ?, ?) AND original_author_id <> 0 GROUP BY issue_id, original_author_id) ORDER BY review.updated_unix ASC", issueID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest). Find(&reviews); err != nil { return nil, err @@ -508,10 +513,10 @@ func GetReviewersFromOriginalAuthorsByIssueID(issueID int64) ([]*Review, error) // GetReviewByIssueIDAndUserID get the latest review of reviewer for a pull request func GetReviewByIssueIDAndUserID(issueID, userID int64) (*Review, error) { - return getReviewByIssueIDAndUserID(x, issueID, userID) + return getReviewByIssueIDAndUserID(db.GetEngine(db.DefaultContext), issueID, userID) } -func getReviewByIssueIDAndUserID(e Engine, issueID, userID int64) (*Review, error) { +func getReviewByIssueIDAndUserID(e db.Engine, issueID, userID int64) (*Review, error) { review := new(Review) has, err := e.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_id = ? AND original_author_id = 0 AND type in (?, ?, ?))", @@ -530,10 +535,10 @@ func getReviewByIssueIDAndUserID(e Engine, issueID, userID int64) (*Review, erro // GetTeamReviewerByIssueIDAndTeamID get the latest review requst of reviewer team for a pull request func GetTeamReviewerByIssueIDAndTeamID(issueID, teamID int64) (review *Review, err error) { - return getTeamReviewerByIssueIDAndTeamID(x, issueID, teamID) + return getTeamReviewerByIssueIDAndTeamID(db.GetEngine(db.DefaultContext), issueID, teamID) } -func getTeamReviewerByIssueIDAndTeamID(e Engine, issueID, teamID int64) (review *Review, err error) { +func getTeamReviewerByIssueIDAndTeamID(e db.Engine, issueID, teamID int64) (review *Review, err error) { review = new(Review) has := false @@ -552,14 +557,14 @@ func getTeamReviewerByIssueIDAndTeamID(e Engine, issueID, teamID int64) (review // MarkReviewsAsStale marks existing reviews as stale func MarkReviewsAsStale(issueID int64) (err error) { - _, err = x.Exec("UPDATE `review` SET stale=? WHERE issue_id=?", true, issueID) + _, err = db.GetEngine(db.DefaultContext).Exec("UPDATE `review` SET stale=? WHERE issue_id=?", true, issueID) return } // MarkReviewsAsNotStale marks existing reviews as not stale for a giving commit SHA func MarkReviewsAsNotStale(issueID int64, commitID string) (err error) { - _, err = x.Exec("UPDATE `review` SET stale=? WHERE issue_id=? AND commit_id=?", false, issueID, commitID) + _, err = db.GetEngine(db.DefaultContext).Exec("UPDATE `review` SET stale=? WHERE issue_id=? AND commit_id=?", false, issueID, commitID) return } @@ -576,14 +581,14 @@ func DismissReview(review *Review, isDismiss bool) (err error) { return ErrReviewNotExist{} } - _, err = x.ID(review.ID).Cols("dismissed").Update(review) + _, err = db.GetEngine(db.DefaultContext).ID(review.ID).Cols("dismissed").Update(review) return } // InsertReviews inserts review and review comments func InsertReviews(reviews []*Review) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { @@ -625,7 +630,7 @@ func InsertReviews(reviews []*Review) error { // AddReviewRequest add a review request from one reviewer func AddReviewRequest(issue *Issue, reviewer, doer *User) (*Comment, error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return nil, err @@ -679,7 +684,7 @@ func AddReviewRequest(issue *Issue, reviewer, doer *User) (*Comment, error) { // RemoveReviewRequest remove a review request from one reviewer func RemoveReviewRequest(issue *Issue, reviewer, doer *User) (*Comment, error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return nil, err @@ -732,7 +737,7 @@ func RemoveReviewRequest(issue *Issue, reviewer, doer *User) (*Comment, error) { // AddTeamReviewRequest add a review request from one team func AddTeamReviewRequest(issue *Issue, reviewer *Team, doer *User) (*Comment, error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return nil, err @@ -791,7 +796,7 @@ func AddTeamReviewRequest(issue *Issue, reviewer *Team, doer *User) (*Comment, e // RemoveTeamReviewRequest remove a review request from one team func RemoveTeamReviewRequest(issue *Issue, reviewer *Team, doer *User) (*Comment, error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return nil, err @@ -859,7 +864,7 @@ func MarkConversation(comment *Comment, doer *User, isResolve bool) (err error) return nil } - if _, err = x.Exec("UPDATE `comment` SET resolve_doer_id=? WHERE id=?", doer.ID, comment.ID); err != nil { + if _, err = db.GetEngine(db.DefaultContext).Exec("UPDATE `comment` SET resolve_doer_id=? WHERE id=?", doer.ID, comment.ID); err != nil { return err } } else { @@ -867,7 +872,7 @@ func MarkConversation(comment *Comment, doer *User, isResolve bool) (err error) return nil } - if _, err = x.Exec("UPDATE `comment` SET resolve_doer_id=? WHERE id=?", 0, comment.ID); err != nil { + if _, err = db.GetEngine(db.DefaultContext).Exec("UPDATE `comment` SET resolve_doer_id=? WHERE id=?", 0, comment.ID); err != nil { return err } } @@ -909,7 +914,7 @@ func CanMarkConversation(issue *Issue, doer *User) (permResult bool, err error) // DeleteReview delete a review and it's code comments func DeleteReview(r *Review) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { @@ -963,7 +968,7 @@ func (r *Review) GetCodeCommentsCount() int { conds = conds.And(builder.Eq{"invalidated": false}) } - count, err := x.Where(conds).Count(new(Comment)) + count, err := db.GetEngine(db.DefaultContext).Where(conds).Count(new(Comment)) if err != nil { return 0 } @@ -978,7 +983,7 @@ func (r *Review) HTMLURL() string { ReviewID: r.ID, } comment := new(Comment) - has, err := x.Where(opts.toConds()).Get(comment) + has, err := db.GetEngine(db.DefaultContext).Where(opts.toConds()).Get(comment) if err != nil || !has { return "" } diff --git a/models/review_test.go b/models/review_test.go index accc1841933ec..c809a3d66222c 100644 --- a/models/review_test.go +++ b/models/review_test.go @@ -7,11 +7,12 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestGetReviewByID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) review, err := GetReviewByID(1) assert.NoError(t, err) assert.Equal(t, "Demo Review", review.Content) @@ -23,23 +24,23 @@ func TestGetReviewByID(t *testing.T) { } func TestReview_LoadAttributes(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - review := AssertExistsAndLoadBean(t, &Review{ID: 1}).(*Review) + assert.NoError(t, db.PrepareTestDatabase()) + review := db.AssertExistsAndLoadBean(t, &Review{ID: 1}).(*Review) assert.NoError(t, review.LoadAttributes()) assert.NotNil(t, review.Issue) assert.NotNil(t, review.Reviewer) - invalidReview1 := AssertExistsAndLoadBean(t, &Review{ID: 2}).(*Review) + invalidReview1 := db.AssertExistsAndLoadBean(t, &Review{ID: 2}).(*Review) assert.Error(t, invalidReview1.LoadAttributes()) - invalidReview2 := AssertExistsAndLoadBean(t, &Review{ID: 3}).(*Review) + invalidReview2 := db.AssertExistsAndLoadBean(t, &Review{ID: 3}).(*Review) assert.Error(t, invalidReview2.LoadAttributes()) } func TestReview_LoadCodeComments(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - review := AssertExistsAndLoadBean(t, &Review{ID: 4}).(*Review) + review := db.AssertExistsAndLoadBean(t, &Review{ID: 4}).(*Review) assert.NoError(t, review.LoadAttributes()) assert.NoError(t, review.LoadCodeComments()) assert.Len(t, review.CodeComments, 1) @@ -56,7 +57,7 @@ func TestReviewType_Icon(t *testing.T) { } func TestFindReviews(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) reviews, err := FindReviews(FindReviewOptions{ Type: ReviewTypeApprove, IssueID: 2, @@ -68,9 +69,9 @@ func TestFindReviews(t *testing.T) { } func TestGetCurrentReview(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - issue := AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue) - user := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + issue := db.AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue) + user := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) review, err := GetCurrentReview(user, issue) assert.NoError(t, err) @@ -78,7 +79,7 @@ func TestGetCurrentReview(t *testing.T) { assert.Equal(t, ReviewTypePending, review.Type) assert.Equal(t, "Pending Review", review.Content) - user2 := AssertExistsAndLoadBean(t, &User{ID: 7}).(*User) + user2 := db.AssertExistsAndLoadBean(t, &User{ID: 7}).(*User) review2, err := GetCurrentReview(user2, issue) assert.Error(t, err) assert.True(t, IsErrReviewNotExist(err)) @@ -86,10 +87,10 @@ func TestGetCurrentReview(t *testing.T) { } func TestCreateReview(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - issue := AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue) - user := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + issue := db.AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue) + user := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) review, err := CreateReview(CreateReviewOptions{ Content: "New Review", @@ -99,16 +100,16 @@ func TestCreateReview(t *testing.T) { }) assert.NoError(t, err) assert.Equal(t, "New Review", review.Content) - AssertExistsAndLoadBean(t, &Review{Content: "New Review"}) + db.AssertExistsAndLoadBean(t, &Review{Content: "New Review"}) } func TestGetReviewersByIssueID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - issue := AssertExistsAndLoadBean(t, &Issue{ID: 3}).(*Issue) - user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - user3 := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) - user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) + issue := db.AssertExistsAndLoadBean(t, &Issue{ID: 3}).(*Issue) + user2 := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + user3 := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + user4 := db.AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) expectedReviews := []*Review{} expectedReviews = append(expectedReviews, @@ -143,45 +144,45 @@ func TestGetReviewersByIssueID(t *testing.T) { } func TestDismissReview(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - rejectReviewExample := AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) - requestReviewExample := AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) - approveReviewExample := AssertExistsAndLoadBean(t, &Review{ID: 8}).(*Review) + rejectReviewExample := db.AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) + requestReviewExample := db.AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) + approveReviewExample := db.AssertExistsAndLoadBean(t, &Review{ID: 8}).(*Review) assert.False(t, rejectReviewExample.Dismissed) assert.False(t, requestReviewExample.Dismissed) assert.False(t, approveReviewExample.Dismissed) assert.NoError(t, DismissReview(rejectReviewExample, true)) - rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) - requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) + rejectReviewExample = db.AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) + requestReviewExample = db.AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) assert.True(t, rejectReviewExample.Dismissed) assert.False(t, requestReviewExample.Dismissed) assert.NoError(t, DismissReview(requestReviewExample, true)) - rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) - requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) + rejectReviewExample = db.AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) + requestReviewExample = db.AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) assert.True(t, rejectReviewExample.Dismissed) assert.False(t, requestReviewExample.Dismissed) assert.False(t, approveReviewExample.Dismissed) assert.NoError(t, DismissReview(requestReviewExample, true)) - rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) - requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) + rejectReviewExample = db.AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) + requestReviewExample = db.AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) assert.True(t, rejectReviewExample.Dismissed) assert.False(t, requestReviewExample.Dismissed) assert.False(t, approveReviewExample.Dismissed) assert.NoError(t, DismissReview(requestReviewExample, false)) - rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) - requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) + rejectReviewExample = db.AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) + requestReviewExample = db.AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) assert.True(t, rejectReviewExample.Dismissed) assert.False(t, requestReviewExample.Dismissed) assert.False(t, approveReviewExample.Dismissed) assert.NoError(t, DismissReview(requestReviewExample, false)) - rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) - requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) + rejectReviewExample = db.AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) + requestReviewExample = db.AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) assert.True(t, rejectReviewExample.Dismissed) assert.False(t, requestReviewExample.Dismissed) assert.False(t, approveReviewExample.Dismissed) diff --git a/models/session.go b/models/session.go index b2e4837bedd1f..65fe2bef4f6c5 100644 --- a/models/session.go +++ b/models/session.go @@ -7,6 +7,7 @@ package models import ( "fmt" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" ) @@ -17,9 +18,13 @@ type Session struct { Expiry timeutil.TimeStamp // has to be Expiry to match with go-chi/session } +func init() { + db.RegisterModel(new(Session)) +} + // UpdateSession updates the session with provided id func UpdateSession(key string, data []byte) error { - _, err := x.ID(key).Update(&Session{ + _, err := db.GetEngine(db.DefaultContext).ID(key).Update(&Session{ Data: data, Expiry: timeutil.TimeStampNow(), }) @@ -31,7 +36,7 @@ func ReadSession(key string) (*Session, error) { session := Session{ Key: key, } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return nil, err @@ -55,12 +60,12 @@ func ExistSession(key string) (bool, error) { session := Session{ Key: key, } - return x.Get(&session) + return db.GetEngine(db.DefaultContext).Get(&session) } // DestroySession destroys a session func DestroySession(key string) error { - _, err := x.Delete(&Session{ + _, err := db.GetEngine(db.DefaultContext).Delete(&Session{ Key: key, }) return err @@ -68,7 +73,7 @@ func DestroySession(key string) error { // RegenerateSession regenerates a session from the old id func RegenerateSession(oldKey, newKey string) (*Session, error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return nil, err @@ -112,11 +117,11 @@ func RegenerateSession(oldKey, newKey string) (*Session, error) { // CountSessions returns the number of sessions func CountSessions() (int64, error) { - return x.Count(&Session{}) + return db.GetEngine(db.DefaultContext).Count(&Session{}) } // CleanupSessions cleans up expired sessions func CleanupSessions(maxLifetime int64) error { - _, err := x.Where("expiry <= ?", timeutil.TimeStampNow().Add(-maxLifetime)).Delete(&Session{}) + _, err := db.GetEngine(db.DefaultContext).Where("expiry <= ?", timeutil.TimeStampNow().Add(-maxLifetime)).Delete(&Session{}) return err } diff --git a/models/ssh_key.go b/models/ssh_key.go index a2f4c610e4047..c08fb72e75133 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -10,6 +10,8 @@ import ( "strings" "time" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -47,6 +49,10 @@ type PublicKey struct { HasUsed bool `xorm:"-"` } +func init() { + db.RegisterModel(new(PublicKey)) +} + // AfterLoad is invoked from XORM after setting the values of all fields of this object. func (key *PublicKey) AfterLoad() { key.HasUsed = key.UpdatedUnix > key.CreatedUnix @@ -65,7 +71,7 @@ func (key *PublicKey) AuthorizedString() string { return AuthorizedStringForKey(key) } -func addKey(e Engine, key *PublicKey) (err error) { +func addKey(e db.Engine, key *PublicKey) (err error) { if len(key.Fingerprint) == 0 { key.Fingerprint, err = calcFingerprint(key.Content) if err != nil { @@ -90,7 +96,7 @@ func AddPublicKey(ownerID int64, name, content string, loginSourceID int64) (*Pu return nil, err } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return nil, err @@ -129,7 +135,7 @@ func AddPublicKey(ownerID int64, name, content string, loginSourceID int64) (*Pu // GetPublicKeyByID returns public key by given ID. func GetPublicKeyByID(keyID int64) (*PublicKey, error) { key := new(PublicKey) - has, err := x. + has, err := db.GetEngine(db.DefaultContext). ID(keyID). Get(key) if err != nil { @@ -140,7 +146,7 @@ func GetPublicKeyByID(keyID int64) (*PublicKey, error) { return key, nil } -func searchPublicKeyByContentWithEngine(e Engine, content string) (*PublicKey, error) { +func searchPublicKeyByContentWithEngine(e db.Engine, content string) (*PublicKey, error) { key := new(PublicKey) has, err := e. Where("content like ?", content+"%"). @@ -156,10 +162,10 @@ func searchPublicKeyByContentWithEngine(e Engine, content string) (*PublicKey, e // SearchPublicKeyByContent searches content as prefix (leak e-mail part) // and returns public key found. func SearchPublicKeyByContent(content string) (*PublicKey, error) { - return searchPublicKeyByContentWithEngine(x, content) + return searchPublicKeyByContentWithEngine(db.GetEngine(db.DefaultContext), content) } -func searchPublicKeyByContentExactWithEngine(e Engine, content string) (*PublicKey, error) { +func searchPublicKeyByContentExactWithEngine(e db.Engine, content string) (*PublicKey, error) { key := new(PublicKey) has, err := e. Where("content = ?", content). @@ -175,7 +181,7 @@ func searchPublicKeyByContentExactWithEngine(e Engine, content string) (*PublicK // SearchPublicKeyByContentExact searches content // and returns public key found. func SearchPublicKeyByContentExact(content string) (*PublicKey, error) { - return searchPublicKeyByContentExactWithEngine(x, content) + return searchPublicKeyByContentExactWithEngine(db.GetEngine(db.DefaultContext), content) } // SearchPublicKey returns a list of public keys matching the provided arguments. @@ -188,14 +194,14 @@ func SearchPublicKey(uid int64, fingerprint string) ([]*PublicKey, error) { if fingerprint != "" { cond = cond.And(builder.Eq{"fingerprint": fingerprint}) } - return keys, x.Where(cond).Find(&keys) + return keys, db.GetEngine(db.DefaultContext).Where(cond).Find(&keys) } // ListPublicKeys returns a list of public keys belongs to given user. -func ListPublicKeys(uid int64, listOptions ListOptions) ([]*PublicKey, error) { - sess := x.Where("owner_id = ? AND type != ?", uid, KeyTypePrincipal) +func ListPublicKeys(uid int64, listOptions db.ListOptions) ([]*PublicKey, error) { + sess := db.GetEngine(db.DefaultContext).Where("owner_id = ? AND type != ?", uid, KeyTypePrincipal) if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, &listOptions) keys := make([]*PublicKey, 0, listOptions.PageSize) return keys, sess.Find(&keys) @@ -207,14 +213,14 @@ func ListPublicKeys(uid int64, listOptions ListOptions) ([]*PublicKey, error) { // CountPublicKeys count public keys a user has func CountPublicKeys(userID int64) (int64, error) { - sess := x.Where("owner_id = ? AND type != ?", userID, KeyTypePrincipal) + sess := db.GetEngine(db.DefaultContext).Where("owner_id = ? AND type != ?", userID, KeyTypePrincipal) return sess.Count(&PublicKey{}) } // ListPublicKeysBySource returns a list of synchronized public keys for a given user and login source. func ListPublicKeysBySource(uid, loginSourceID int64) ([]*PublicKey, error) { keys := make([]*PublicKey, 0, 5) - return keys, x. + return keys, db.GetEngine(db.DefaultContext). Where("owner_id = ? AND login_source_id = ?", uid, loginSourceID). Find(&keys) } @@ -223,13 +229,13 @@ func ListPublicKeysBySource(uid, loginSourceID int64) ([]*PublicKey, error) { func UpdatePublicKeyUpdated(id int64) error { // Check if key exists before update as affected rows count is unreliable // and will return 0 affected rows if two updates are made at the same time - if cnt, err := x.ID(id).Count(&PublicKey{}); err != nil { + if cnt, err := db.GetEngine(db.DefaultContext).ID(id).Count(&PublicKey{}); err != nil { return err } else if cnt != 1 { return ErrKeyNotExist{id} } - _, err := x.ID(id).Cols("updated_unix").Update(&PublicKey{ + _, err := db.GetEngine(db.DefaultContext).ID(id).Cols("updated_unix").Update(&PublicKey{ UpdatedUnix: timeutil.TimeStampNow(), }) if err != nil { @@ -239,7 +245,7 @@ func UpdatePublicKeyUpdated(id int64) error { } // deletePublicKeys does the actual key deletion but does not update authorized_keys file. -func deletePublicKeys(e Engine, keyIDs ...int64) error { +func deletePublicKeys(e db.Engine, keyIDs ...int64) error { if len(keyIDs) == 0 { return nil } @@ -250,7 +256,7 @@ func deletePublicKeys(e Engine, keyIDs ...int64) error { // PublicKeysAreExternallyManaged returns whether the provided KeyID represents an externally managed Key func PublicKeysAreExternallyManaged(keys []*PublicKey) ([]bool, error) { - sources := make([]*LoginSource, 0, 5) + sources := make([]*login.Source, 0, 5) externals := make([]bool, len(keys)) keyloop: for i, key := range keys { @@ -259,7 +265,7 @@ keyloop: continue keyloop } - var source *LoginSource + var source *login.Source sourceloop: for _, s := range sources { @@ -271,11 +277,11 @@ keyloop: if source == nil { var err error - source, err = GetLoginSourceByID(key.LoginSourceID) + source, err = login.GetSourceByID(key.LoginSourceID) if err != nil { - if IsErrLoginSourceNotExist(err) { + if login.IsErrSourceNotExist(err) { externals[i] = false - sources[i] = &LoginSource{ + sources[i] = &login.Source{ ID: key.LoginSourceID, } continue keyloop @@ -284,7 +290,7 @@ keyloop: } } - if sshKeyProvider, ok := source.Cfg.(SSHKeyProvider); ok && sshKeyProvider.ProvidesSSHKeys() { + if sshKeyProvider, ok := source.Cfg.(login.SSHKeyProvider); ok && sshKeyProvider.ProvidesSSHKeys() { // Disable setting SSH keys for this user externals[i] = true } @@ -302,14 +308,14 @@ func PublicKeyIsExternallyManaged(id int64) (bool, error) { if key.LoginSourceID == 0 { return false, nil } - source, err := GetLoginSourceByID(key.LoginSourceID) + source, err := login.GetSourceByID(key.LoginSourceID) if err != nil { - if IsErrLoginSourceNotExist(err) { + if login.IsErrSourceNotExist(err) { return false, nil } return false, err } - if sshKeyProvider, ok := source.Cfg.(SSHKeyProvider); ok && sshKeyProvider.ProvidesSSHKeys() { + if sshKeyProvider, ok := source.Cfg.(login.SSHKeyProvider); ok && sshKeyProvider.ProvidesSSHKeys() { // Disable setting SSH keys for this user return true, nil } @@ -328,7 +334,7 @@ func DeletePublicKey(doer *User, id int64) (err error) { return ErrKeyAccessDenied{doer.ID, key.ID, "public"} } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -353,7 +359,7 @@ func DeletePublicKey(doer *User, id int64) (err error) { // deleteKeysMarkedForDeletion returns true if ssh keys needs update func deleteKeysMarkedForDeletion(keys []string) (bool, error) { // Start session - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return false, err @@ -382,7 +388,7 @@ func deleteKeysMarkedForDeletion(keys []string) (bool, error) { } // AddPublicKeysBySource add a users public keys. Returns true if there are changes. -func AddPublicKeysBySource(usr *User, s *LoginSource, sshPublicKeys []string) bool { +func AddPublicKeysBySource(usr *User, s *login.Source, sshPublicKeys []string) bool { var sshKeysNeedUpdate bool for _, sshKey := range sshPublicKeys { var err error @@ -420,7 +426,7 @@ func AddPublicKeysBySource(usr *User, s *LoginSource, sshPublicKeys []string) bo } // SynchronizePublicKeys updates a users public keys. Returns true if there are changes. -func SynchronizePublicKeys(usr *User, s *LoginSource, sshPublicKeys []string) bool { +func SynchronizePublicKeys(usr *User, s *login.Source, sshPublicKeys []string) bool { var sshKeysNeedUpdate bool log.Trace("synchronizePublicKeys[%s]: Handling Public SSH Key synchronization for user %s", s.Name, usr.Name) diff --git a/models/ssh_key_authorized_keys.go b/models/ssh_key_authorized_keys.go index 5736477a0ddb0..ed17a12e9a8ab 100644 --- a/models/ssh_key_authorized_keys.go +++ b/models/ssh_key_authorized_keys.go @@ -14,6 +14,7 @@ import ( "sync" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -114,13 +115,13 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error { } // RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again. -// Note: x.Iterate does not get latest data after insert/delete, so we have to call this function +// Note: db.GetEngine(db.DefaultContext).Iterate does not get latest data after insert/delete, so we have to call this function // outside any session scope independently. func RewriteAllPublicKeys() error { - return rewriteAllPublicKeys(x) + return rewriteAllPublicKeys(db.GetEngine(db.DefaultContext)) } -func rewriteAllPublicKeys(e Engine) error { +func rewriteAllPublicKeys(e db.Engine) error { // Don't rewrite key if internal server if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile { return nil @@ -178,10 +179,10 @@ func rewriteAllPublicKeys(e Engine) error { // RegeneratePublicKeys regenerates the authorized_keys file func RegeneratePublicKeys(t io.StringWriter) error { - return regeneratePublicKeys(x, t) + return regeneratePublicKeys(db.GetEngine(db.DefaultContext), t) } -func regeneratePublicKeys(e Engine, t io.StringWriter) error { +func regeneratePublicKeys(e db.Engine, t io.StringWriter) error { if err := e.Where("type != ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) { _, err = t.WriteString((bean.(*PublicKey)).AuthorizedString()) return err diff --git a/models/ssh_key_authorized_principals.go b/models/ssh_key_authorized_principals.go index f90ab267a9178..c053b4b6d55c2 100644 --- a/models/ssh_key_authorized_principals.go +++ b/models/ssh_key_authorized_principals.go @@ -13,6 +13,7 @@ import ( "strings" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -39,13 +40,13 @@ import ( const authorizedPrincipalsFile = "authorized_principals" // RewriteAllPrincipalKeys removes any authorized principal and rewrite all keys from database again. -// Note: x.Iterate does not get latest data after insert/delete, so we have to call this function +// Note: db.GetEngine(db.DefaultContext).Iterate does not get latest data after insert/delete, so we have to call this function // outside any session scope independently. func RewriteAllPrincipalKeys() error { - return rewriteAllPrincipalKeys(x) + return rewriteAllPrincipalKeys(db.GetEngine(db.DefaultContext)) } -func rewriteAllPrincipalKeys(e Engine) error { +func rewriteAllPrincipalKeys(e db.Engine) error { // Don't rewrite key if internal server if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedPrincipalsFile { return nil @@ -101,10 +102,10 @@ func rewriteAllPrincipalKeys(e Engine) error { // RegeneratePrincipalKeys regenerates the authorized_principals file func RegeneratePrincipalKeys(t io.StringWriter) error { - return regeneratePrincipalKeys(x, t) + return regeneratePrincipalKeys(db.GetEngine(db.DefaultContext), t) } -func regeneratePrincipalKeys(e Engine, t io.StringWriter) error { +func regeneratePrincipalKeys(e db.Engine, t io.StringWriter) error { if err := e.Where("type = ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) { _, err = t.WriteString((bean.(*PublicKey)).AuthorizedString()) return err diff --git a/models/ssh_key_deploy.go b/models/ssh_key_deploy.go index e7d486b9f56cb..34cf03e925186 100644 --- a/models/ssh_key_deploy.go +++ b/models/ssh_key_deploy.go @@ -8,6 +8,7 @@ import ( "fmt" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" "xorm.io/builder" "xorm.io/xorm" @@ -60,7 +61,11 @@ func (key *DeployKey) IsReadOnly() bool { return key.Mode == AccessModeRead } -func checkDeployKey(e Engine, keyID, repoID int64, name string) error { +func init() { + db.RegisterModel(new(DeployKey)) +} + +func checkDeployKey(e db.Engine, keyID, repoID int64, name string) error { // Note: We want error detail, not just true or false here. has, err := e. Where("key_id = ? AND repo_id = ?", keyID, repoID). @@ -102,7 +107,7 @@ func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string // HasDeployKey returns true if public key is a deploy key of given repository. func HasDeployKey(keyID, repoID int64) bool { - has, _ := x. + has, _ := db.GetEngine(db.DefaultContext). Where("key_id = ? AND repo_id = ?", keyID, repoID). Get(new(DeployKey)) return has @@ -120,7 +125,7 @@ func AddDeployKey(repoID int64, name, content string, readOnly bool) (*DeployKey accessMode = AccessModeWrite } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return nil, err @@ -159,10 +164,10 @@ func AddDeployKey(repoID int64, name, content string, readOnly bool) (*DeployKey // GetDeployKeyByID returns deploy key by given ID. func GetDeployKeyByID(id int64) (*DeployKey, error) { - return getDeployKeyByID(x, id) + return getDeployKeyByID(db.GetEngine(db.DefaultContext), id) } -func getDeployKeyByID(e Engine, id int64) (*DeployKey, error) { +func getDeployKeyByID(e db.Engine, id int64) (*DeployKey, error) { key := new(DeployKey) has, err := e.ID(id).Get(key) if err != nil { @@ -175,10 +180,10 @@ func getDeployKeyByID(e Engine, id int64) (*DeployKey, error) { // GetDeployKeyByRepo returns deploy key by given public key ID and repository ID. func GetDeployKeyByRepo(keyID, repoID int64) (*DeployKey, error) { - return getDeployKeyByRepo(x, keyID, repoID) + return getDeployKeyByRepo(db.GetEngine(db.DefaultContext), keyID, repoID) } -func getDeployKeyByRepo(e Engine, keyID, repoID int64) (*DeployKey, error) { +func getDeployKeyByRepo(e db.Engine, keyID, repoID int64) (*DeployKey, error) { key := &DeployKey{ KeyID: keyID, RepoID: repoID, @@ -194,19 +199,19 @@ func getDeployKeyByRepo(e Engine, keyID, repoID int64) (*DeployKey, error) { // UpdateDeployKeyCols updates deploy key information in the specified columns. func UpdateDeployKeyCols(key *DeployKey, cols ...string) error { - _, err := x.ID(key.ID).Cols(cols...).Update(key) + _, err := db.GetEngine(db.DefaultContext).ID(key.ID).Cols(cols...).Update(key) return err } // UpdateDeployKey updates deploy key information. func UpdateDeployKey(key *DeployKey) error { - _, err := x.ID(key.ID).AllCols().Update(key) + _, err := db.GetEngine(db.DefaultContext).ID(key.ID).AllCols().Update(key) return err } // DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed. func DeleteDeployKey(doer *User, id int64) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -217,7 +222,7 @@ func DeleteDeployKey(doer *User, id int64) error { return sess.Commit() } -func deleteDeployKey(sess Engine, doer *User, id int64) error { +func deleteDeployKey(sess db.Engine, doer *User, id int64) error { key, err := getDeployKeyByID(sess, id) if err != nil { if IsErrDeployKeyNotExist(err) { @@ -266,7 +271,7 @@ func deleteDeployKey(sess Engine, doer *User, id int64) error { // ListDeployKeysOptions are options for ListDeployKeys type ListDeployKeysOptions struct { - ListOptions + db.ListOptions RepoID int64 KeyID int64 Fingerprint string @@ -288,14 +293,14 @@ func (opt ListDeployKeysOptions) toCond() builder.Cond { // ListDeployKeys returns a list of deploy keys matching the provided arguments. func ListDeployKeys(opts *ListDeployKeysOptions) ([]*DeployKey, error) { - return listDeployKeys(x, opts) + return listDeployKeys(db.GetEngine(db.DefaultContext), opts) } -func listDeployKeys(e Engine, opts *ListDeployKeysOptions) ([]*DeployKey, error) { +func listDeployKeys(e db.Engine, opts *ListDeployKeysOptions) ([]*DeployKey, error) { sess := e.Where(opts.toCond()) if opts.Page != 0 { - sess = opts.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, opts) keys := make([]*DeployKey, 0, opts.PageSize) return keys, sess.Find(&keys) @@ -307,5 +312,5 @@ func listDeployKeys(e Engine, opts *ListDeployKeysOptions) ([]*DeployKey, error) // CountDeployKeys returns count deploy keys matching the provided arguments. func CountDeployKeys(opts *ListDeployKeysOptions) (int64, error) { - return x.Where(opts.toCond()).Count(&DeployKey{}) + return db.GetEngine(db.DefaultContext).Where(opts.toCond()).Count(&DeployKey{}) } diff --git a/models/ssh_key_fingerprint.go b/models/ssh_key_fingerprint.go index 96cc7d9c484f1..93c455e48938a 100644 --- a/models/ssh_key_fingerprint.go +++ b/models/ssh_key_fingerprint.go @@ -9,6 +9,7 @@ import ( "fmt" "strings" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" @@ -29,7 +30,7 @@ import ( // checkKeyFingerprint only checks if key fingerprint has been used as public key, // it is OK to use same key as deploy key for multiple repositories/users. -func checkKeyFingerprint(e Engine, fingerprint string) error { +func checkKeyFingerprint(e db.Engine, fingerprint string) error { has, err := e.Get(&PublicKey{ Fingerprint: fingerprint, }) diff --git a/models/ssh_key_parse.go b/models/ssh_key_parse.go index a86b7de02a8f8..d2c24b0a2a502 100644 --- a/models/ssh_key_parse.go +++ b/models/ssh_key_parse.go @@ -13,8 +13,8 @@ import ( "encoding/pem" "errors" "fmt" - "io/ioutil" "math/big" + "os" "strconv" "strings" @@ -263,7 +263,7 @@ func SSHNativeParsePublicKey(keyLine string) (string, int, error) { // writeTmpKeyFile writes key content to a temporary file // and returns the name of that file, along with any possible errors. func writeTmpKeyFile(content string) (string, error) { - tmpFile, err := ioutil.TempFile(setting.SSH.KeyTestPath, "gitea_keytest") + tmpFile, err := os.CreateTemp(setting.SSH.KeyTestPath, "gitea_keytest") if err != nil { return "", fmt.Errorf("TempFile: %v", err) } diff --git a/models/ssh_key_principals.go b/models/ssh_key_principals.go index 3459e43c8b042..44b2ee0bb4e60 100644 --- a/models/ssh_key_principals.go +++ b/models/ssh_key_principals.go @@ -9,6 +9,7 @@ import ( "fmt" "strings" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" ) @@ -23,7 +24,7 @@ import ( // AddPrincipalKey adds new principal to database and authorized_principals file. func AddPrincipalKey(ownerID int64, content string, loginSourceID int64) (*PublicKey, error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return nil, err @@ -60,7 +61,7 @@ func AddPrincipalKey(ownerID int64, content string, loginSourceID int64) (*Publi return key, RewriteAllPrincipalKeys() } -func addPrincipalKey(e Engine, key *PublicKey) (err error) { +func addPrincipalKey(e db.Engine, key *PublicKey) (err error) { // Save Key representing a principal. if _, err = e.Insert(key); err != nil { return err @@ -111,10 +112,10 @@ func CheckPrincipalKeyString(user *User, content string) (_ string, err error) { } // ListPrincipalKeys returns a list of principals belongs to given user. -func ListPrincipalKeys(uid int64, listOptions ListOptions) ([]*PublicKey, error) { - sess := x.Where("owner_id = ? AND type = ?", uid, KeyTypePrincipal) +func ListPrincipalKeys(uid int64, listOptions db.ListOptions) ([]*PublicKey, error) { + sess := db.GetEngine(db.DefaultContext).Where("owner_id = ? AND type = ?", uid, KeyTypePrincipal) if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, &listOptions) keys := make([]*PublicKey, 0, listOptions.PageSize) return keys, sess.Find(&keys) diff --git a/models/star.go b/models/star.go index 2d9496caf504d..ee7791513d971 100644 --- a/models/star.go +++ b/models/star.go @@ -5,6 +5,7 @@ package models import ( + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" ) @@ -16,9 +17,13 @@ type Star struct { CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` } +func init() { + db.RegisterModel(new(Star)) +} + // StarRepo or unstar repository. func StarRepo(userID, repoID int64, star bool) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { @@ -60,20 +65,20 @@ func StarRepo(userID, repoID int64, star bool) error { // IsStaring checks if user has starred given repository. func IsStaring(userID, repoID int64) bool { - return isStaring(x, userID, repoID) + return isStaring(db.GetEngine(db.DefaultContext), userID, repoID) } -func isStaring(e Engine, userID, repoID int64) bool { +func isStaring(e db.Engine, userID, repoID int64) bool { has, _ := e.Get(&Star{UID: userID, RepoID: repoID}) return has } // GetStargazers returns the users that starred the repo. -func (repo *Repository) GetStargazers(opts ListOptions) ([]*User, error) { - sess := x.Where("star.repo_id = ?", repo.ID). +func (repo *Repository) GetStargazers(opts db.ListOptions) ([]*User, error) { + sess := db.GetEngine(db.DefaultContext).Where("star.repo_id = ?", repo.ID). Join("LEFT", "star", "`user`.id = star.uid") if opts.Page > 0 { - sess = opts.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, &opts) users := make([]*User, 0, opts.PageSize) return users, sess.Find(&users) @@ -88,7 +93,7 @@ func (u *User) GetStarredRepos(private bool, page, pageSize int, orderBy string) if len(orderBy) == 0 { orderBy = "updated_unix DESC" } - sess := x. + sess := db.GetEngine(db.DefaultContext). Join("INNER", "star", "star.repo_id = repository.id"). Where("star.uid = ?", u.ID). OrderBy(orderBy) @@ -108,7 +113,7 @@ func (u *User) GetStarredRepos(private bool, page, pageSize int, orderBy string) return } - if err = repos.loadAttributes(x); err != nil { + if err = repos.loadAttributes(db.GetEngine(db.DefaultContext)); err != nil { return } @@ -117,7 +122,7 @@ func (u *User) GetStarredRepos(private bool, page, pageSize int, orderBy string) // GetStarredRepoCount returns the numbers of repo the user starred. func (u *User) GetStarredRepoCount(private bool) (int64, error) { - sess := x. + sess := db.GetEngine(db.DefaultContext). Join("INNER", "star", "star.repo_id = repository.id"). Where("star.uid = ?", u.ID) diff --git a/models/star_test.go b/models/star_test.go index 96292380081ea..326da8a861d94 100644 --- a/models/star_test.go +++ b/models/star_test.go @@ -7,33 +7,34 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestStarRepo(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) const userID = 2 const repoID = 1 - AssertNotExistsBean(t, &Star{UID: userID, RepoID: repoID}) + db.AssertNotExistsBean(t, &Star{UID: userID, RepoID: repoID}) assert.NoError(t, StarRepo(userID, repoID, true)) - AssertExistsAndLoadBean(t, &Star{UID: userID, RepoID: repoID}) + db.AssertExistsAndLoadBean(t, &Star{UID: userID, RepoID: repoID}) assert.NoError(t, StarRepo(userID, repoID, true)) - AssertExistsAndLoadBean(t, &Star{UID: userID, RepoID: repoID}) + db.AssertExistsAndLoadBean(t, &Star{UID: userID, RepoID: repoID}) assert.NoError(t, StarRepo(userID, repoID, false)) - AssertNotExistsBean(t, &Star{UID: userID, RepoID: repoID}) + db.AssertNotExistsBean(t, &Star{UID: userID, RepoID: repoID}) } func TestIsStaring(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) assert.True(t, IsStaring(2, 4)) assert.False(t, IsStaring(3, 4)) } func TestRepository_GetStargazers(t *testing.T) { // repo with stargazers - assert.NoError(t, PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 4}).(*Repository) - gazers, err := repo.GetStargazers(ListOptions{Page: 0}) + assert.NoError(t, db.PrepareTestDatabase()) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 4}).(*Repository) + gazers, err := repo.GetStargazers(db.ListOptions{Page: 0}) assert.NoError(t, err) if assert.Len(t, gazers, 1) { assert.Equal(t, int64(2), gazers[0].ID) @@ -42,18 +43,18 @@ func TestRepository_GetStargazers(t *testing.T) { func TestRepository_GetStargazers2(t *testing.T) { // repo with stargazers - assert.NoError(t, PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) - gazers, err := repo.GetStargazers(ListOptions{Page: 0}) + assert.NoError(t, db.PrepareTestDatabase()) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) + gazers, err := repo.GetStargazers(db.ListOptions{Page: 0}) assert.NoError(t, err) assert.Len(t, gazers, 0) } func TestUser_GetStarredRepos(t *testing.T) { // user who has starred repos - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + user := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) starred, err := user.GetStarredRepos(false, 1, 10, "") assert.NoError(t, err) if assert.Len(t, starred, 1) { @@ -70,9 +71,9 @@ func TestUser_GetStarredRepos(t *testing.T) { func TestUser_GetStarredRepos2(t *testing.T) { // user who has no starred repos - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + user := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) starred, err := user.GetStarredRepos(false, 1, 10, "") assert.NoError(t, err) assert.Len(t, starred, 0) @@ -83,9 +84,9 @@ func TestUser_GetStarredRepos2(t *testing.T) { } func TestUserGetStarredRepoCount(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + user := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) counts, err := user.GetStarredRepoCount(false) assert.NoError(t, err) assert.Equal(t, int64(1), counts) diff --git a/models/statistic.go b/models/statistic.go new file mode 100644 index 0000000000000..5e72dc713dcca --- /dev/null +++ b/models/statistic.go @@ -0,0 +1,107 @@ +// Copyright 2021 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 models + +import ( + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" + "code.gitea.io/gitea/modules/setting" +) + +// Statistic contains the database statistics +type Statistic struct { + Counter struct { + User, Org, PublicKey, + Repo, Watch, Star, Action, Access, + Issue, IssueClosed, IssueOpen, + Comment, Oauth, Follow, + Mirror, Release, LoginSource, Webhook, + Milestone, Label, HookTask, + Team, UpdateTask, Project, + ProjectBoard, Attachment int64 + IssueByLabel []IssueByLabelCount + IssueByRepository []IssueByRepositoryCount + } +} + +// IssueByLabelCount contains the number of issue group by label +type IssueByLabelCount struct { + Count int64 + Label string +} + +// IssueByRepositoryCount contains the number of issue group by repository +type IssueByRepositoryCount struct { + Count int64 + OwnerName string + Repository string +} + +// GetStatistic returns the database statistics +func GetStatistic() (stats Statistic) { + e := db.GetEngine(db.DefaultContext) + stats.Counter.User = CountUsers() + stats.Counter.Org = CountOrganizations() + stats.Counter.PublicKey, _ = e.Count(new(PublicKey)) + stats.Counter.Repo = CountRepositories(true) + stats.Counter.Watch, _ = e.Count(new(Watch)) + stats.Counter.Star, _ = e.Count(new(Star)) + stats.Counter.Action, _ = e.Count(new(Action)) + stats.Counter.Access, _ = e.Count(new(Access)) + + type IssueCount struct { + Count int64 + IsClosed bool + } + + if setting.Metrics.EnabledIssueByLabel { + stats.Counter.IssueByLabel = []IssueByLabelCount{} + + _ = e.Select("COUNT(*) AS count, l.name AS label"). + Join("LEFT", "label l", "l.id=il.label_id"). + Table("issue_label il"). + GroupBy("l.name"). + Find(&stats.Counter.IssueByLabel) + } + + if setting.Metrics.EnabledIssueByRepository { + stats.Counter.IssueByRepository = []IssueByRepositoryCount{} + + _ = e.Select("COUNT(*) AS count, r.owner_name, r.name AS repository"). + Join("LEFT", "repository r", "r.id=i.repo_id"). + Table("issue i"). + GroupBy("r.owner_name, r.name"). + Find(&stats.Counter.IssueByRepository) + } + + issueCounts := []IssueCount{} + + _ = e.Select("COUNT(*) AS count, is_closed").Table("issue").GroupBy("is_closed").Find(&issueCounts) + for _, c := range issueCounts { + if c.IsClosed { + stats.Counter.IssueClosed = c.Count + } else { + stats.Counter.IssueOpen = c.Count + } + } + + stats.Counter.Issue = stats.Counter.IssueClosed + stats.Counter.IssueOpen + + stats.Counter.Comment, _ = e.Count(new(Comment)) + stats.Counter.Oauth = 0 + stats.Counter.Follow, _ = e.Count(new(Follow)) + stats.Counter.Mirror, _ = e.Count(new(Mirror)) + stats.Counter.Release, _ = e.Count(new(Release)) + stats.Counter.LoginSource = login.CountSources() + stats.Counter.Webhook, _ = e.Count(new(Webhook)) + stats.Counter.Milestone, _ = e.Count(new(Milestone)) + stats.Counter.Label, _ = e.Count(new(Label)) + stats.Counter.HookTask, _ = e.Count(new(HookTask)) + stats.Counter.Team, _ = e.Count(new(Team)) + stats.Counter.Attachment, _ = e.Count(new(Attachment)) + stats.Counter.Project, _ = e.Count(new(Project)) + stats.Counter.ProjectBoard, _ = e.Count(new(ProjectBoard)) + return +} diff --git a/models/task.go b/models/task.go index 7dfcaea2e3558..7da9307c95967 100644 --- a/models/task.go +++ b/models/task.go @@ -7,6 +7,7 @@ package models import ( "fmt" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/json" migration "code.gitea.io/gitea/modules/migrations/base" "code.gitea.io/gitea/modules/secret" @@ -36,6 +37,10 @@ type Task struct { Created timeutil.TimeStamp `xorm:"created"` } +func init() { + db.RegisterModel(new(Task)) +} + // TranslatableMessage represents JSON struct that can be translated with a Locale type TranslatableMessage struct { Format string @@ -44,10 +49,10 @@ type TranslatableMessage struct { // LoadRepo loads repository of the task func (task *Task) LoadRepo() error { - return task.loadRepo(x) + return task.loadRepo(db.GetEngine(db.DefaultContext)) } -func (task *Task) loadRepo(e Engine) error { +func (task *Task) loadRepo(e db.Engine) error { if task.Repo != nil { return nil } @@ -71,7 +76,7 @@ func (task *Task) LoadDoer() error { } var doer User - has, err := x.ID(task.DoerID).Get(&doer) + has, err := db.GetEngine(db.DefaultContext).ID(task.DoerID).Get(&doer) if err != nil { return err } else if !has { @@ -91,7 +96,7 @@ func (task *Task) LoadOwner() error { } var owner User - has, err := x.ID(task.OwnerID).Get(&owner) + has, err := db.GetEngine(db.DefaultContext).ID(task.OwnerID).Get(&owner) if err != nil { return err } else if !has { @@ -106,7 +111,7 @@ func (task *Task) LoadOwner() error { // UpdateCols updates some columns func (task *Task) UpdateCols(cols ...string) error { - _, err := x.ID(task.ID).Cols(cols...).Update(task) + _, err := db.GetEngine(db.DefaultContext).ID(task.ID).Cols(cols...).Update(task) return err } @@ -165,7 +170,7 @@ func GetMigratingTask(repoID int64) (*Task, error) { RepoID: repoID, Type: structs.TaskTypeMigrateRepo, } - has, err := x.Get(&task) + has, err := db.GetEngine(db.DefaultContext).Get(&task) if err != nil { return nil, err } else if !has { @@ -181,7 +186,7 @@ func GetMigratingTaskByID(id, doerID int64) (*Task, *migration.MigrateOptions, e DoerID: doerID, Type: structs.TaskTypeMigrateRepo, } - has, err := x.Get(&task) + has, err := db.GetEngine(db.DefaultContext).Get(&task) if err != nil { return nil, nil, err } else if !has { @@ -212,16 +217,16 @@ func (opts FindTaskOptions) ToConds() builder.Cond { // FindTasks find all tasks func FindTasks(opts FindTaskOptions) ([]*Task, error) { tasks := make([]*Task, 0, 10) - err := x.Where(opts.ToConds()).Find(&tasks) + err := db.GetEngine(db.DefaultContext).Where(opts.ToConds()).Find(&tasks) return tasks, err } // CreateTask creates a task on database func CreateTask(task *Task) error { - return createTask(x, task) + return createTask(db.GetEngine(db.DefaultContext), task) } -func createTask(e Engine, task *Task) error { +func createTask(e db.Engine, task *Task) error { _, err := e.Insert(task) return err } @@ -248,7 +253,7 @@ func FinishMigrateTask(task *Task) error { } task.PayloadContent = string(confBytes) - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err diff --git a/models/token.go b/models/token.go index 9baa763f1c225..b3712fce5e5cc 100644 --- a/models/token.go +++ b/models/token.go @@ -7,9 +7,13 @@ package models import ( "crypto/subtle" + "fmt" "time" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -41,6 +45,21 @@ func (t *AccessToken) AfterLoad() { t.HasRecentActivity = t.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow() } +func init() { + db.RegisterModel(new(AccessToken), func() error { + if setting.SuccessfulTokensCacheSize > 0 { + var err error + successfulAccessTokenCache, err = lru.New(setting.SuccessfulTokensCacheSize) + if err != nil { + return fmt.Errorf("unable to allocate AccessToken cache: %v", err) + } + } else { + successfulAccessTokenCache = nil + } + return nil + }) +} + // NewAccessToken creates new access token. func NewAccessToken(t *AccessToken) error { salt, err := util.RandomString(10) @@ -49,9 +68,9 @@ func NewAccessToken(t *AccessToken) error { } t.TokenSalt = salt t.Token = base.EncodeSha1(gouuid.New().String()) - t.TokenHash = hashToken(t.Token, t.TokenSalt) + t.TokenHash = login.HashToken(t.Token, t.TokenSalt) t.TokenLastEight = t.Token[len(t.Token)-8:] - _, err = x.Insert(t) + _, err = db.GetEngine(db.DefaultContext).Insert(t) return err } @@ -92,7 +111,7 @@ func GetAccessTokenBySHA(token string) (*AccessToken, error) { TokenLastEight: lastEight, } // Re-get the token from the db in case it has been deleted in the intervening period - has, err := x.ID(id).Get(token) + has, err := db.GetEngine(db.DefaultContext).ID(id).Get(token) if err != nil { return nil, err } @@ -103,7 +122,7 @@ func GetAccessTokenBySHA(token string) (*AccessToken, error) { } var tokens []AccessToken - err := x.Table(&AccessToken{}).Where("token_last_eight = ?", lastEight).Find(&tokens) + err := db.GetEngine(db.DefaultContext).Table(&AccessToken{}).Where("token_last_eight = ?", lastEight).Find(&tokens) if err != nil { return nil, err } else if len(tokens) == 0 { @@ -111,7 +130,7 @@ func GetAccessTokenBySHA(token string) (*AccessToken, error) { } for _, t := range tokens { - tempHash := hashToken(token, t.TokenSalt) + tempHash := login.HashToken(token, t.TokenSalt) if subtle.ConstantTimeCompare([]byte(t.TokenHash), []byte(tempHash)) == 1 { if successfulAccessTokenCache != nil { successfulAccessTokenCache.Add(token, t.ID) @@ -124,28 +143,28 @@ func GetAccessTokenBySHA(token string) (*AccessToken, error) { // AccessTokenByNameExists checks if a token name has been used already by a user. func AccessTokenByNameExists(token *AccessToken) (bool, error) { - return x.Table("access_token").Where("name = ?", token.Name).And("uid = ?", token.UID).Exist() + return db.GetEngine(db.DefaultContext).Table("access_token").Where("name = ?", token.Name).And("uid = ?", token.UID).Exist() } // ListAccessTokensOptions contain filter options type ListAccessTokensOptions struct { - ListOptions + db.ListOptions Name string UserID int64 } // ListAccessTokens returns a list of access tokens belongs to given user. func ListAccessTokens(opts ListAccessTokensOptions) ([]*AccessToken, error) { - sess := x.Where("uid=?", opts.UserID) + sess := db.GetEngine(db.DefaultContext).Where("uid=?", opts.UserID) if len(opts.Name) != 0 { sess = sess.Where("name=?", opts.Name) } - sess = sess.Desc("id") + sess = sess.Desc("created_unix") if opts.Page != 0 { - sess = opts.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, &opts) tokens := make([]*AccessToken, 0, opts.PageSize) return tokens, sess.Find(&tokens) @@ -157,13 +176,13 @@ func ListAccessTokens(opts ListAccessTokensOptions) ([]*AccessToken, error) { // UpdateAccessToken updates information of access token. func UpdateAccessToken(t *AccessToken) error { - _, err := x.ID(t.ID).AllCols().Update(t) + _, err := db.GetEngine(db.DefaultContext).ID(t.ID).AllCols().Update(t) return err } // CountAccessTokens count access tokens belongs to given user by options func CountAccessTokens(opts ListAccessTokensOptions) (int64, error) { - sess := x.Where("uid=?", opts.UserID) + sess := db.GetEngine(db.DefaultContext).Where("uid=?", opts.UserID) if len(opts.Name) != 0 { sess = sess.Where("name=?", opts.Name) } @@ -172,7 +191,7 @@ func CountAccessTokens(opts ListAccessTokensOptions) (int64, error) { // DeleteAccessTokenByID deletes access token by given ID. func DeleteAccessTokenByID(id, userID int64) error { - cnt, err := x.ID(id).Delete(&AccessToken{ + cnt, err := db.GetEngine(db.DefaultContext).ID(id).Delete(&AccessToken{ UID: userID, }) if err != nil { diff --git a/models/token_test.go b/models/token_test.go index 0f24c7527d2d0..21d827ea61583 100644 --- a/models/token_test.go +++ b/models/token_test.go @@ -7,17 +7,18 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestNewAccessToken(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) token := &AccessToken{ UID: 3, Name: "Token C", } assert.NoError(t, NewAccessToken(token)) - AssertExistsAndLoadBean(t, token) + db.AssertExistsAndLoadBean(t, token) invalidToken := &AccessToken{ ID: token.ID, // duplicate @@ -30,7 +31,7 @@ func TestNewAccessToken(t *testing.T) { func TestAccessTokenByNameExists(t *testing.T) { name := "Token Gitea" - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) token := &AccessToken{ UID: 3, Name: name, @@ -43,7 +44,7 @@ func TestAccessTokenByNameExists(t *testing.T) { // Save it to the database assert.NoError(t, NewAccessToken(token)) - AssertExistsAndLoadBean(t, token) + db.AssertExistsAndLoadBean(t, token) // This token must be found by name in the DB now exist, err = AccessTokenByNameExists(token) @@ -63,7 +64,7 @@ func TestAccessTokenByNameExists(t *testing.T) { } func TestGetAccessTokenBySHA(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) token, err := GetAccessTokenBySHA("d2c6c1ba3890b309189a8e618c72a162e4efbf36") assert.NoError(t, err) assert.Equal(t, int64(1), token.UID) @@ -81,7 +82,7 @@ func TestGetAccessTokenBySHA(t *testing.T) { } func TestListAccessTokens(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) tokens, err := ListAccessTokens(ListAccessTokensOptions{UserID: 1}) assert.NoError(t, err) if assert.Len(t, tokens, 2) { @@ -104,24 +105,24 @@ func TestListAccessTokens(t *testing.T) { } func TestUpdateAccessToken(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) token, err := GetAccessTokenBySHA("4c6f36e6cf498e2a448662f915d932c09c5a146c") assert.NoError(t, err) token.Name = "Token Z" assert.NoError(t, UpdateAccessToken(token)) - AssertExistsAndLoadBean(t, token) + db.AssertExistsAndLoadBean(t, token) } func TestDeleteAccessTokenByID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) token, err := GetAccessTokenBySHA("4c6f36e6cf498e2a448662f915d932c09c5a146c") assert.NoError(t, err) assert.Equal(t, int64(1), token.UID) assert.NoError(t, DeleteAccessTokenByID(token.ID, 1)) - AssertNotExistsBean(t, token) + db.AssertNotExistsBean(t, token) err = DeleteAccessTokenByID(100, 100) assert.Error(t, err) diff --git a/models/topic.go b/models/topic.go index 7fc34f5bef19e..6eb8c67b8dd85 100644 --- a/models/topic.go +++ b/models/topic.go @@ -9,16 +9,15 @@ import ( "regexp" "strings" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" "xorm.io/builder" ) func init() { - tables = append(tables, - new(Topic), - new(RepoTopic), - ) + db.RegisterModel(new(Topic)) + db.RegisterModel(new(RepoTopic)) } var topicPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`) @@ -89,7 +88,7 @@ func SanitizeAndValidateTopics(topics []string) (validTopics, invalidTopics []st // GetTopicByName retrieves topic by name func GetTopicByName(name string) (*Topic, error) { var topic Topic - if has, err := x.Where("name = ?", name).Get(&topic); err != nil { + if has, err := db.GetEngine(db.DefaultContext).Where("name = ?", name).Get(&topic); err != nil { return nil, err } else if !has { return nil, ErrTopicNotExist{name} @@ -99,7 +98,7 @@ func GetTopicByName(name string) (*Topic, error) { // addTopicByNameToRepo adds a topic name to a repo and increments the topic count. // Returns topic after the addition -func addTopicByNameToRepo(e Engine, repoID int64, topicName string) (*Topic, error) { +func addTopicByNameToRepo(e db.Engine, repoID int64, topicName string) (*Topic, error) { var topic Topic has, err := e.Where("name = ?", topicName).Get(&topic) if err != nil { @@ -129,7 +128,7 @@ func addTopicByNameToRepo(e Engine, repoID int64, topicName string) (*Topic, err } // removeTopicFromRepo remove a topic from a repo and decrements the topic repo count -func removeTopicFromRepo(e Engine, repoID int64, topic *Topic) error { +func removeTopicFromRepo(e db.Engine, repoID int64, topic *Topic) error { topic.RepoCount-- if _, err := e.ID(topic.ID).Cols("repo_count").Update(topic); err != nil { return err @@ -146,7 +145,7 @@ func removeTopicFromRepo(e Engine, repoID int64, topic *Topic) error { } // removeTopicsFromRepo remove all topics from the repo and decrements respective topics repo count -func removeTopicsFromRepo(e Engine, repoID int64) error { +func removeTopicsFromRepo(e db.Engine, repoID int64) error { _, err := e.Where( builder.In("id", builder.Select("topic_id").From("repo_topic").Where(builder.Eq{"repo_id": repoID}), @@ -165,7 +164,7 @@ func removeTopicsFromRepo(e Engine, repoID int64) error { // FindTopicOptions represents the options when fdin topics type FindTopicOptions struct { - ListOptions + db.ListOptions RepoID int64 Keyword string } @@ -185,12 +184,12 @@ func (opts *FindTopicOptions) toConds() builder.Cond { // FindTopics retrieves the topics via FindTopicOptions func FindTopics(opts *FindTopicOptions) ([]*Topic, int64, error) { - sess := x.Select("topic.*").Where(opts.toConds()) + sess := db.GetEngine(db.DefaultContext).Select("topic.*").Where(opts.toConds()) if opts.RepoID > 0 { sess.Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id") } if opts.PageSize != 0 && opts.Page != 0 { - sess = opts.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, opts) } topics := make([]*Topic, 0, 10) total, err := sess.Desc("topic.repo_count").FindAndCount(&topics) @@ -199,7 +198,7 @@ func FindTopics(opts *FindTopicOptions) ([]*Topic, int64, error) { // CountTopics counts the number of topics matching the FindTopicOptions func CountTopics(opts *FindTopicOptions) (int64, error) { - sess := x.Where(opts.toConds()) + sess := db.GetEngine(db.DefaultContext).Where(opts.toConds()) if opts.RepoID > 0 { sess.Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id") } @@ -208,10 +207,10 @@ func CountTopics(opts *FindTopicOptions) (int64, error) { // GetRepoTopicByName retrieves topic from name for a repo if it exist func GetRepoTopicByName(repoID int64, topicName string) (*Topic, error) { - return getRepoTopicByName(x, repoID, topicName) + return getRepoTopicByName(db.GetEngine(db.DefaultContext), repoID, topicName) } -func getRepoTopicByName(e Engine, repoID int64, topicName string) (*Topic, error) { +func getRepoTopicByName(e db.Engine, repoID int64, topicName string) (*Topic, error) { cond := builder.NewCond() var topic Topic cond = cond.And(builder.Eq{"repo_topic.repo_id": repoID}).And(builder.Eq{"topic.name": topicName}) @@ -226,7 +225,7 @@ func getRepoTopicByName(e Engine, repoID int64, topicName string) (*Topic, error // AddTopic adds a topic name to a repository (if it does not already have it) func AddTopic(repoID int64, topicName string) (*Topic, error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return nil, err @@ -273,7 +272,7 @@ func DeleteTopic(repoID int64, topicName string) (*Topic, error) { return nil, nil } - err = removeTopicFromRepo(x, repoID, topic) + err = removeTopicFromRepo(db.GetEngine(db.DefaultContext), repoID, topic) return topic, err } @@ -287,7 +286,7 @@ func SaveTopics(repoID int64, topicNames ...string) error { return err } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { diff --git a/models/topic_test.go b/models/topic_test.go index 9386a71e35d23..b069deaba3c20 100644 --- a/models/topic_test.go +++ b/models/topic_test.go @@ -7,6 +7,7 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) @@ -15,14 +16,14 @@ func TestAddTopic(t *testing.T) { repo1NrOfTopics := 3 repo2NrOfTopics := 2 - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) topics, _, err := FindTopics(&FindTopicOptions{}) assert.NoError(t, err) assert.Len(t, topics, totalNrOfTopics) topics, total, err := FindTopics(&FindTopicOptions{ - ListOptions: ListOptions{Page: 1, PageSize: 2}, + ListOptions: db.ListOptions{Page: 1, PageSize: 2}, }) assert.NoError(t, err) assert.Len(t, topics, 2) diff --git a/models/update.go b/models/update.go index 421c44bb4040c..0898ab54c1255 100644 --- a/models/update.go +++ b/models/update.go @@ -5,16 +5,19 @@ package models import ( + "context" "fmt" "strings" + + "code.gitea.io/gitea/models/db" ) // PushUpdateDeleteTagsContext updates a number of delete tags with context -func PushUpdateDeleteTagsContext(ctx DBContext, repo *Repository, tags []string) error { - return pushUpdateDeleteTags(ctx.e, repo, tags) +func PushUpdateDeleteTagsContext(ctx context.Context, repo *Repository, tags []string) error { + return pushUpdateDeleteTags(db.GetEngine(ctx), repo, tags) } -func pushUpdateDeleteTags(e Engine, repo *Repository, tags []string) error { +func pushUpdateDeleteTags(e db.Engine, repo *Repository, tags []string) error { if len(tags) == 0 { return nil } @@ -53,14 +56,14 @@ func PushUpdateDeleteTag(repo *Repository, tagName string) error { return fmt.Errorf("GetRelease: %v", err) } if rel.IsTag { - if _, err = x.ID(rel.ID).Delete(new(Release)); err != nil { + if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).Delete(new(Release)); err != nil { return fmt.Errorf("Delete: %v", err) } } else { rel.IsDraft = true rel.NumCommits = 0 rel.Sha1 = "" - if _, err = x.ID(rel.ID).AllCols().Update(rel); err != nil { + if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).AllCols().Update(rel); err != nil { return fmt.Errorf("Update: %v", err) } } @@ -77,7 +80,7 @@ func SaveOrUpdateTag(repo *Repository, newRel *Release) error { if rel == nil { rel = newRel - if _, err = x.Insert(rel); err != nil { + if _, err = db.GetEngine(db.DefaultContext).Insert(rel); err != nil { return fmt.Errorf("InsertOne: %v", err) } } else { @@ -88,7 +91,7 @@ func SaveOrUpdateTag(repo *Repository, newRel *Release) error { if rel.IsTag && newRel.PublisherID > 0 { rel.PublisherID = newRel.PublisherID } - if _, err = x.ID(rel.ID).AllCols().Update(rel); err != nil { + if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).AllCols().Update(rel); err != nil { return fmt.Errorf("Update: %v", err) } } diff --git a/models/upload.go b/models/upload.go index de2032fb720c5..503220db577b6 100644 --- a/models/upload.go +++ b/models/upload.go @@ -12,6 +12,7 @@ import ( "os" "path" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -34,6 +35,10 @@ type Upload struct { Name string } +func init() { + db.RegisterModel(new(Upload)) +} + // UploadLocalPath returns where uploads is stored in local file system based on given UUID. func UploadLocalPath(uuid string) string { return path.Join(setting.Repository.Upload.TempPath, uuid[0:1], uuid[1:2], uuid) @@ -68,7 +73,7 @@ func NewUpload(name string, buf []byte, file multipart.File) (_ *Upload, err err return nil, fmt.Errorf("Copy: %v", err) } - if _, err := x.Insert(upload); err != nil { + if _, err := db.GetEngine(db.DefaultContext).Insert(upload); err != nil { return nil, err } @@ -78,7 +83,7 @@ func NewUpload(name string, buf []byte, file multipart.File) (_ *Upload, err err // GetUploadByUUID returns the Upload by UUID func GetUploadByUUID(uuid string) (*Upload, error) { upload := &Upload{} - has, err := x.Where("uuid=?", uuid).Get(upload) + has, err := db.GetEngine(db.DefaultContext).Where("uuid=?", uuid).Get(upload) if err != nil { return nil, err } else if !has { @@ -95,7 +100,7 @@ func GetUploadsByUUIDs(uuids []string) ([]*Upload, error) { // Silently drop invalid uuids. uploads := make([]*Upload, 0, len(uuids)) - return uploads, x.In("uuid", uuids).Find(&uploads) + return uploads, db.GetEngine(db.DefaultContext).In("uuid", uuids).Find(&uploads) } // DeleteUploads deletes multiple uploads @@ -104,7 +109,7 @@ func DeleteUploads(uploads ...*Upload) (err error) { return nil } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err diff --git a/models/user.go b/models/user.go index 92452e0767f03..934b834e96328 100644 --- a/models/user.go +++ b/models/user.go @@ -20,6 +20,8 @@ import ( "time" "unicode/utf8" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" @@ -105,7 +107,7 @@ type User struct { // is to change his/her password after registration. MustChangePassword bool `xorm:"NOT NULL DEFAULT false"` - LoginType LoginType + LoginType login.Type LoginSource int64 `xorm:"NOT NULL DEFAULT 0"` LoginName string Type UserType @@ -162,9 +164,13 @@ type User struct { KeepActivityPrivate bool `xorm:"NOT NULL DEFAULT false"` } +func init() { + db.RegisterModel(new(User)) +} + // SearchOrganizationsOptions options to filter organizations type SearchOrganizationsOptions struct { - ListOptions + db.ListOptions All bool } @@ -231,17 +237,17 @@ func (u *User) GetEmail() string { // GetAllUsers returns a slice of all individual users found in DB. func GetAllUsers() ([]*User, error) { users := make([]*User, 0) - return users, x.OrderBy("id").Where("type = ?", UserTypeIndividual).Find(&users) + return users, db.GetEngine(db.DefaultContext).OrderBy("id").Where("type = ?", UserTypeIndividual).Find(&users) } // IsLocal returns true if user login type is LoginPlain. func (u *User) IsLocal() bool { - return u.LoginType <= LoginPlain + return u.LoginType <= login.Plain } // IsOAuth2 returns true if user login type is LoginOAuth2. func (u *User) IsOAuth2() bool { - return u.LoginType == LoginOAuth2 + return u.LoginType == login.OAuth2 } // HasForkedRepo checks if user has already forked a repository with given ID. @@ -294,7 +300,7 @@ func (u *User) CanImportLocal() bool { // DashboardLink returns the user dashboard page link. func (u *User) DashboardLink() string { if u.IsOrganization() { - return u.OrganisationLink() + "/dashboard/" + return u.OrganisationLink() + "/dashboard" } return setting.AppSubURL + "/" } @@ -326,13 +332,13 @@ func (u *User) GenerateEmailActivateCode(email string) string { } // GetFollowers returns range of user's followers. -func (u *User) GetFollowers(listOptions ListOptions) ([]*User, error) { - sess := x. +func (u *User) GetFollowers(listOptions db.ListOptions) ([]*User, error) { + sess := db.GetEngine(db.DefaultContext). Where("follow.follow_id=?", u.ID). Join("LEFT", "follow", "`user`.id=follow.user_id") if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, &listOptions) users := make([]*User, 0, listOptions.PageSize) return users, sess.Find(&users) @@ -348,13 +354,13 @@ func (u *User) IsFollowing(followID int64) bool { } // GetFollowing returns range of user's following. -func (u *User) GetFollowing(listOptions ListOptions) ([]*User, error) { - sess := x. +func (u *User) GetFollowing(listOptions db.ListOptions) ([]*User, error) { + sess := db.GetEngine(db.DefaultContext). Where("follow.user_id=?", u.ID). Join("LEFT", "follow", "`user`.id=follow.follow_id") if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, &listOptions) users := make([]*User, 0, listOptions.PageSize) return users, sess.Find(&users) @@ -432,10 +438,10 @@ func (u *User) IsPasswordSet() bool { // IsVisibleToUser check if viewer is able to see user profile func (u *User) IsVisibleToUser(viewer *User) bool { - return u.isVisibleToUser(x, viewer) + return u.isVisibleToUser(db.GetEngine(db.DefaultContext), viewer) } -func (u *User) isVisibleToUser(e Engine, viewer *User) bool { +func (u *User) isVisibleToUser(e db.Engine, viewer *User) bool { if viewer != nil && viewer.IsAdmin { return true } @@ -460,7 +466,7 @@ func (u *User) isVisibleToUser(e Engine, viewer *User) bool { } // Now we need to check if they in some organization together - count, err := x.Table("team_user"). + count, err := db.GetEngine(db.DefaultContext).Table("team_user"). Where( builder.And( builder.Eq{"uid": viewer.ID}, @@ -503,10 +509,10 @@ func (u *User) IsUserOrgOwner(orgID int64) bool { // HasMemberWithUserID returns true if user with userID is part of the u organisation. func (u *User) HasMemberWithUserID(userID int64) bool { - return u.hasMemberWithUserID(x, userID) + return u.hasMemberWithUserID(db.GetEngine(db.DefaultContext), userID) } -func (u *User) hasMemberWithUserID(e Engine, userID int64) bool { +func (u *User) hasMemberWithUserID(e db.Engine, userID int64) bool { isMember, err := isOrganizationMember(e, u.ID, userID) if err != nil { log.Error("IsOrganizationMember: %v", err) @@ -525,7 +531,7 @@ func (u *User) IsPublicMember(orgID int64) bool { return isMember } -func (u *User) getOrganizationCount(e Engine) (int64, error) { +func (u *User) getOrganizationCount(e db.Engine) (int64, error) { return e. Where("uid=?", u.ID). Count(new(OrgUser)) @@ -533,11 +539,11 @@ func (u *User) getOrganizationCount(e Engine) (int64, error) { // GetOrganizationCount returns count of membership of organization of user. func (u *User) GetOrganizationCount() (int64, error) { - return u.getOrganizationCount(x) + return u.getOrganizationCount(db.GetEngine(db.DefaultContext)) } // GetRepositories returns repositories that user owns, including private repositories. -func (u *User) GetRepositories(listOpts ListOptions, names ...string) (err error) { +func (u *User) GetRepositories(listOpts db.ListOptions, names ...string) (err error) { u.Repos, _, err = GetUserRepositories(&SearchRepoOptions{Actor: u, Private: true, ListOptions: listOpts, LowerNames: names}) return err } @@ -547,7 +553,7 @@ func (u *User) GetRepositories(listOpts ListOptions, names ...string) (err error func (u *User) GetRepositoryIDs(units ...UnitType) ([]int64, error) { var ids []int64 - sess := x.Table("repository").Cols("repository.id") + sess := db.GetEngine(db.DefaultContext).Table("repository").Cols("repository.id") if len(units) > 0 { sess = sess.Join("INNER", "repo_unit", "repository.id = repo_unit.repo_id") @@ -562,7 +568,7 @@ func (u *User) GetRepositoryIDs(units ...UnitType) ([]int64, error) { func (u *User) GetActiveRepositoryIDs(units ...UnitType) ([]int64, error) { var ids []int64 - sess := x.Table("repository").Cols("repository.id") + sess := db.GetEngine(db.DefaultContext).Table("repository").Cols("repository.id") if len(units) > 0 { sess = sess.Join("INNER", "repo_unit", "repository.id = repo_unit.repo_id") @@ -579,7 +585,7 @@ func (u *User) GetActiveRepositoryIDs(units ...UnitType) ([]int64, error) { func (u *User) GetOrgRepositoryIDs(units ...UnitType) ([]int64, error) { var ids []int64 - if err := x.Table("repository"). + if err := db.GetEngine(db.DefaultContext).Table("repository"). Cols("repository.id"). Join("INNER", "team_user", "repository.owner_id = team_user.org_id"). Join("INNER", "team_repo", "(? != ? and repository.is_private != ?) OR (team_user.team_id = team_repo.team_id AND repository.id = team_repo.repo_id)", true, u.IsRestricted, true). @@ -600,7 +606,7 @@ func (u *User) GetOrgRepositoryIDs(units ...UnitType) ([]int64, error) { func (u *User) GetActiveOrgRepositoryIDs(units ...UnitType) ([]int64, error) { var ids []int64 - if err := x.Table("repository"). + if err := db.GetEngine(db.DefaultContext).Table("repository"). Cols("repository.id"). Join("INNER", "team_user", "repository.owner_id = team_user.org_id"). Join("INNER", "team_repo", "(? != ? and repository.is_private != ?) OR (team_user.team_id = team_repo.team_id AND repository.id = team_repo.repo_id)", true, u.IsRestricted, true). @@ -724,7 +730,7 @@ func (u *User) SetEmailNotifications(set string) error { return nil } -func isUserExist(e Engine, uid int64, name string) (bool, error) { +func isUserExist(e db.Engine, uid int64, name string) (bool, error) { if len(name) == 0 { return false, nil } @@ -738,7 +744,7 @@ func isUserExist(e Engine, uid int64, name string) (bool, error) { // If uid is presented, then check will rule out that one, // it is used when update a user name in settings page. func IsUserExist(uid int64, name string) (bool, error) { - return isUserExist(x, uid, name) + return isUserExist(db.GetEngine(db.DefaultContext), uid, name) } // GetUserSalt returns a random user salt token. @@ -874,7 +880,7 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e u.Visibility = overwriteDefault[0].Visibility } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -935,7 +941,7 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e return sess.Commit() } -func countUsers(e Engine) int64 { +func countUsers(e db.Engine) int64 { count, _ := e. Where("type=0"). Count(new(User)) @@ -944,7 +950,7 @@ func countUsers(e Engine) int64 { // CountUsers returns number of users. func CountUsers() int64 { - return countUsers(x) + return countUsers(db.GetEngine(db.DefaultContext)) } // get user by verify code @@ -992,7 +998,7 @@ func VerifyActiveEmailCode(code, email string) *EmailAddress { if base.VerifyTimeLimitCode(data, minutes, prefix) { emailAddress := &EmailAddress{UID: user.ID, Email: email} - if has, _ := x.Get(emailAddress); has { + if has, _ := db.GetEngine(db.DefaultContext).Get(emailAddress); has { return emailAddress } } @@ -1007,7 +1013,7 @@ func ChangeUserName(u *User, newUserName string) (err error) { return err } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -1045,7 +1051,7 @@ func ChangeUserName(u *User, newUserName string) (err error) { } // checkDupEmail checks whether there are the same email with the user -func checkDupEmail(e Engine, u *User) error { +func checkDupEmail(e db.Engine, u *User) error { u.Email = strings.ToLower(u.Email) has, err := e. Where("id!=?", u.ID). @@ -1070,7 +1076,7 @@ func validateUser(u *User) error { return ValidateEmail(u.Email) } -func updateUser(e Engine, u *User) error { +func updateUser(e db.Engine, u *User) error { if err := validateUser(u); err != nil { return err } @@ -1081,15 +1087,15 @@ func updateUser(e Engine, u *User) error { // UpdateUser updates user's information. func UpdateUser(u *User) error { - return updateUser(x, u) + return updateUser(db.GetEngine(db.DefaultContext), u) } // UpdateUserCols update user according special columns func UpdateUserCols(u *User, cols ...string) error { - return updateUserCols(x, u, cols...) + return updateUserCols(db.GetEngine(db.DefaultContext), u, cols...) } -func updateUserCols(e Engine, u *User, cols ...string) error { +func updateUserCols(e db.Engine, u *User, cols ...string) error { if err := validateUser(u); err != nil { return err } @@ -1100,7 +1106,7 @@ func updateUserCols(e Engine, u *User, cols ...string) error { // UpdateUserSetting updates user's settings. func UpdateUserSetting(u *User) (err error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -1117,7 +1123,7 @@ func UpdateUserSetting(u *User) (err error) { } // deleteBeans deletes all given beans, beans should contain delete conditions. -func deleteBeans(e Engine, beans ...interface{}) (err error) { +func deleteBeans(e db.Engine, beans ...interface{}) (err error) { for i := range beans { if _, err = e.Delete(beans[i]); err != nil { return err @@ -1126,7 +1132,7 @@ func deleteBeans(e Engine, beans ...interface{}) (err error) { return nil } -func deleteUser(e Engine, u *User) error { +func deleteUser(e db.Engine, u *User) error { // Note: A user owns any repository or belongs to any organization // cannot perform delete operation. @@ -1247,7 +1253,7 @@ func deleteUser(e Engine, u *User) error { // ***** END: PublicKey ***** // ***** START: GPGPublicKey ***** - keys, err := listGPGKeys(e, u.ID, ListOptions{}) + keys, err := listGPGKeys(e, u.ID, db.ListOptions{}) if err != nil { return fmt.Errorf("ListGPGKeys: %v", err) } @@ -1306,7 +1312,7 @@ func DeleteUser(u *User) (err error) { return fmt.Errorf("%s is an organization not a user", u.Name) } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -1324,13 +1330,13 @@ func DeleteUser(u *User) (err error) { func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) (err error) { users := make([]*User, 0, 10) if olderThan > 0 { - if err = x. + if err = db.GetEngine(db.DefaultContext). Where("is_active = ? and created_unix < ?", false, time.Now().Add(-olderThan).Unix()). Find(&users); err != nil { return fmt.Errorf("get all inactive users: %v", err) } } else { - if err = x. + if err = db.GetEngine(db.DefaultContext). Where("is_active = ?", false). Find(&users); err != nil { return fmt.Errorf("get all inactive users: %v", err) @@ -1352,7 +1358,7 @@ func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) (err erro } } - _, err = x. + _, err = db.GetEngine(db.DefaultContext). Where("is_activated = ?", false). Delete(new(EmailAddress)) return err @@ -1363,7 +1369,7 @@ func UserPath(userName string) string { return filepath.Join(setting.RepoRootPath, strings.ToLower(userName)) } -func getUserByID(e Engine, id int64) (*User, error) { +func getUserByID(e db.Engine, id int64) (*User, error) { u := new(User) has, err := e.ID(id).Get(u) if err != nil { @@ -1376,15 +1382,15 @@ func getUserByID(e Engine, id int64) (*User, error) { // GetUserByID returns the user object by given ID if exists. func GetUserByID(id int64) (*User, error) { - return getUserByID(x, id) + return getUserByID(db.GetEngine(db.DefaultContext), id) } // GetUserByName returns user by given name. func GetUserByName(name string) (*User, error) { - return getUserByName(x, name) + return getUserByName(db.GetEngine(db.DefaultContext), name) } -func getUserByName(e Engine, name string) (*User, error) { +func getUserByName(e db.Engine, name string) (*User, error) { if len(name) == 0 { return nil, ErrUserNotExist{0, name, 0} } @@ -1401,10 +1407,10 @@ func getUserByName(e Engine, name string) (*User, error) { // GetUserEmailsByNames returns a list of e-mails corresponds to names of users // that have their email notifications set to enabled or onmention. func GetUserEmailsByNames(names []string) []string { - return getUserEmailsByNames(x, names) + return getUserEmailsByNames(db.GetEngine(db.DefaultContext), names) } -func getUserEmailsByNames(e Engine, names []string) []string { +func getUserEmailsByNames(e db.Engine, names []string) []string { mails := make([]string, 0, len(names)) for _, name := range names { u, err := getUserByName(e, name) @@ -1426,7 +1432,7 @@ func GetMaileableUsersByIDs(ids []int64, isMention bool) ([]*User, error) { ous := make([]*User, 0, len(ids)) if isMention { - return ous, x.In("id", ids). + return ous, db.GetEngine(db.DefaultContext).In("id", ids). Where("`type` = ?", UserTypeIndividual). And("`prohibit_login` = ?", false). And("`is_active` = ?", true). @@ -1434,7 +1440,7 @@ func GetMaileableUsersByIDs(ids []int64, isMention bool) ([]*User, error) { Find(&ous) } - return ous, x.In("id", ids). + return ous, db.GetEngine(db.DefaultContext).In("id", ids). Where("`type` = ?", UserTypeIndividual). And("`prohibit_login` = ?", false). And("`is_active` = ?", true). @@ -1445,7 +1451,7 @@ func GetMaileableUsersByIDs(ids []int64, isMention bool) ([]*User, error) { // GetUserNamesByIDs returns usernames for all resolved users from a list of Ids. func GetUserNamesByIDs(ids []int64) ([]string, error) { unames := make([]string, 0, len(ids)) - err := x.In("id", ids). + err := db.GetEngine(db.DefaultContext).In("id", ids). Table("user"). Asc("name"). Cols("name"). @@ -1459,7 +1465,7 @@ func GetUsersByIDs(ids []int64) (UserList, error) { if len(ids) == 0 { return ous, nil } - err := x.In("id", ids). + err := db.GetEngine(db.DefaultContext).In("id", ids). Asc("name"). Find(&ous) return ous, err @@ -1483,9 +1489,9 @@ func GetUserIDsByNames(names []string, ignoreNonExistent bool) ([]int64, error) } // GetUsersBySource returns a list of Users for a login source -func GetUsersBySource(s *LoginSource) ([]*User, error) { +func GetUsersBySource(s *login.Source) ([]*User, error) { var users []*User - err := x.Where("login_type = ? AND login_source = ?", s.Type, s.ID).Find(&users) + err := db.GetEngine(db.DefaultContext).Where("login_type = ? AND login_source = ?", s.Type, s.ID).Find(&users) return users, err } @@ -1534,11 +1540,11 @@ func ValidateCommitsWithEmails(oldCommits []*git.Commit) []*UserCommit { // GetUserByEmail returns the user object by given e-mail if exists. func GetUserByEmail(email string) (*User, error) { - return GetUserByEmailContext(DefaultDBContext(), email) + return GetUserByEmailContext(db.DefaultContext, email) } // GetUserByEmailContext returns the user object by given e-mail if exists with db context -func GetUserByEmailContext(ctx DBContext, email string) (*User, error) { +func GetUserByEmailContext(ctx context.Context, email string) (*User, error) { if len(email) == 0 { return nil, ErrUserNotExist{0, email, 0} } @@ -1546,7 +1552,7 @@ func GetUserByEmailContext(ctx DBContext, email string) (*User, error) { email = strings.ToLower(email) // First try to find the user by primary email user := &User{Email: email} - has, err := ctx.e.Get(user) + has, err := db.GetEngine(ctx).Get(user) if err != nil { return nil, err } @@ -1556,19 +1562,19 @@ func GetUserByEmailContext(ctx DBContext, email string) (*User, error) { // Otherwise, check in alternative list for activated email addresses emailAddress := &EmailAddress{Email: email, IsActivated: true} - has, err = ctx.e.Get(emailAddress) + has, err = db.GetEngine(ctx).Get(emailAddress) if err != nil { return nil, err } if has { - return getUserByID(ctx.e, emailAddress.UID) + return getUserByID(db.GetEngine(ctx), emailAddress.UID) } // Finally, if email address is the protected email address: if strings.HasSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress)) { username := strings.TrimSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress)) user := &User{} - has, err := ctx.e.Where("lower_name=?", username).Get(user) + has, err := db.GetEngine(ctx).Where("lower_name=?", username).Get(user) if err != nil { return nil, err } @@ -1582,12 +1588,12 @@ func GetUserByEmailContext(ctx DBContext, email string) (*User, error) { // GetUser checks if a user already exists func GetUser(user *User) (bool, error) { - return x.Get(user) + return db.GetEngine(db.DefaultContext).Get(user) } // SearchUserOptions contains the options for searching type SearchUserOptions struct { - ListOptions + db.ListOptions Keyword string Type UserType UID int64 @@ -1659,7 +1665,7 @@ func (opts *SearchUserOptions) toConds() builder.Cond { // it returns results in given range and number of total results. func SearchUsers(opts *SearchUserOptions) (users []*User, _ int64, _ error) { cond := opts.toConds() - count, err := x.Where(cond).Count(new(User)) + count, err := db.GetEngine(db.DefaultContext).Where(cond).Count(new(User)) if err != nil { return nil, 0, fmt.Errorf("Count: %v", err) } @@ -1668,9 +1674,9 @@ func SearchUsers(opts *SearchUserOptions) (users []*User, _ int64, _ error) { opts.OrderBy = SearchOrderByAlphabetically } - sess := x.Where(cond).OrderBy(opts.OrderBy.String()) + sess := db.GetEngine(db.DefaultContext).Where(cond).OrderBy(opts.OrderBy.String()) if opts.Page != 0 { - sess = opts.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, opts) } users = make([]*User, 0, opts.PageSize) @@ -1678,15 +1684,15 @@ func SearchUsers(opts *SearchUserOptions) (users []*User, _ int64, _ error) { } // GetStarredRepos returns the repos starred by a particular user -func GetStarredRepos(userID int64, private bool, listOptions ListOptions) ([]*Repository, error) { - sess := x.Where("star.uid=?", userID). +func GetStarredRepos(userID int64, private bool, listOptions db.ListOptions) ([]*Repository, error) { + sess := db.GetEngine(db.DefaultContext).Where("star.uid=?", userID). Join("LEFT", "star", "`repository`.id=`star`.repo_id") if !private { sess = sess.And("is_private=?", false) } if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, &listOptions) repos := make([]*Repository, 0, listOptions.PageSize) return repos, sess.Find(&repos) @@ -1697,8 +1703,8 @@ func GetStarredRepos(userID int64, private bool, listOptions ListOptions) ([]*Re } // GetWatchedRepos returns the repos watched by a particular user -func GetWatchedRepos(userID int64, private bool, listOptions ListOptions) ([]*Repository, int64, error) { - sess := x.Where("watch.user_id=?", userID). +func GetWatchedRepos(userID int64, private bool, listOptions db.ListOptions) ([]*Repository, int64, error) { + sess := db.GetEngine(db.DefaultContext).Where("watch.user_id=?", userID). And("`watch`.mode<>?", RepoWatchModeDont). Join("LEFT", "watch", "`repository`.id=`watch`.repo_id") if !private { @@ -1706,7 +1712,7 @@ func GetWatchedRepos(userID int64, private bool, listOptions ListOptions) ([]*Re } if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, &listOptions) repos := make([]*Repository, 0, listOptions.PageSize) total, err := sess.FindAndCount(&repos) @@ -1724,7 +1730,7 @@ func IterateUser(f func(user *User) error) error { batchSize := setting.Database.IterateBufferSize for { users := make([]*User, 0, batchSize) - if err := x.Limit(batchSize, start).Find(&users); err != nil { + if err := db.GetEngine(db.DefaultContext).Limit(batchSize, start).Find(&users); err != nil { return err } if len(users) == 0 { diff --git a/models/user_avatar.go b/models/user_avatar.go index d336684a27d70..b8296568c2721 100644 --- a/models/user_avatar.go +++ b/models/user_avatar.go @@ -9,9 +9,9 @@ import ( "fmt" "image/png" "io" - "strconv" - "strings" + "code.gitea.io/gitea/models/avatars" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/avatar" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -25,10 +25,10 @@ func (u *User) CustomAvatarRelativePath() string { // GenerateRandomAvatar generates a random avatar for user. func (u *User) GenerateRandomAvatar() error { - return u.generateRandomAvatar(x) + return u.generateRandomAvatar(db.GetEngine(db.DefaultContext)) } -func (u *User) generateRandomAvatar(e Engine) error { +func (u *User) generateRandomAvatar(e db.Engine) error { seed := u.Email if len(seed) == 0 { seed = u.Name @@ -39,7 +39,7 @@ func (u *User) generateRandomAvatar(e Engine) error { return fmt.Errorf("RandomImage: %v", err) } - u.Avatar = HashEmail(seed) + u.Avatar = avatars.HashEmail(seed) // Don't share the images so that we can delete them easily if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error { @@ -59,61 +59,41 @@ func (u *User) generateRandomAvatar(e Engine) error { return nil } -// SizedRelAvatarLink returns a link to the user's avatar via -// the local explore page. Function returns immediately. -// When applicable, the link is for an avatar of the indicated size (in pixels). -func (u *User) SizedRelAvatarLink(size int) string { - return setting.AppSubURL + "/user/avatar/" + u.Name + "/" + strconv.Itoa(size) -} - -// RealSizedAvatarLink returns a link to the user's avatar. When -// applicable, the link is for an avatar of the indicated size (in pixels). -// -// This function make take time to return when federated avatars -// are in use, due to a DNS lookup need -// -func (u *User) RealSizedAvatarLink(size int) string { +// AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size +func (u *User) AvatarLinkWithSize(size int) string { if u.ID == -1 { - return DefaultAvatarLink() + // ghost user + return avatars.DefaultAvatarLink() } + useLocalAvatar := false + autoGenerateAvatar := false + switch { case u.UseCustomAvatar: - if u.Avatar == "" { - return DefaultAvatarLink() - } - if size > 0 { - return setting.AppSubURL + "/avatars/" + u.Avatar + "?size=" + strconv.Itoa(size) - } - return setting.AppSubURL + "/avatars/" + u.Avatar + useLocalAvatar = true case setting.DisableGravatar, setting.OfflineMode: - if u.Avatar == "" { + useLocalAvatar = true + autoGenerateAvatar = true + } + + if useLocalAvatar { + if u.Avatar == "" && autoGenerateAvatar { if err := u.GenerateRandomAvatar(); err != nil { log.Error("GenerateRandomAvatar: %v", err) } } - if size > 0 { - return setting.AppSubURL + "/avatars/" + u.Avatar + "?size=" + strconv.Itoa(size) + if u.Avatar == "" { + return avatars.DefaultAvatarLink() } - return setting.AppSubURL + "/avatars/" + u.Avatar + return avatars.GenerateUserAvatarImageLink(u.Avatar, size) } - return SizedAvatarLink(u.AvatarEmail, size) -} - -// RelAvatarLink returns a relative link to the user's avatar. The link -// may either be a sub-URL to this site, or a full URL to an external avatar -// service. -func (u *User) RelAvatarLink() string { - return u.SizedRelAvatarLink(DefaultAvatarSize) + return avatars.GenerateEmailAvatarFastLink(u.AvatarEmail, size) } -// AvatarLink returns user avatar absolute link. +// AvatarLink returns a avatar link with default size func (u *User) AvatarLink() string { - link := u.RelAvatarLink() - if link[0] == '/' && link[1] != '/' { - return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL)[1:] - } - return link + return u.AvatarLinkWithSize(0) } // UploadAvatar saves custom avatar for user. @@ -124,7 +104,7 @@ func (u *User) UploadAvatar(data []byte) error { return err } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -152,6 +132,15 @@ func (u *User) UploadAvatar(data []byte) error { return sess.Commit() } +// IsUploadAvatarChanged returns true if the current user's avatar would be changed with the provided data +func (u *User) IsUploadAvatarChanged(data []byte) bool { + if !u.UseCustomAvatar || len(u.Avatar) == 0 { + return true + } + avatarID := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", u.ID, md5.Sum(data))))) + return u.Avatar != avatarID +} + // DeleteAvatar deletes the user's custom avatar. func (u *User) DeleteAvatar() error { aPath := u.CustomAvatarRelativePath() @@ -164,7 +153,7 @@ func (u *User) DeleteAvatar() error { u.UseCustomAvatar = false u.Avatar = "" - if _, err := x.ID(u.ID).Cols("avatar, use_custom_avatar").Update(u); err != nil { + if _, err := db.GetEngine(db.DefaultContext).ID(u.ID).Cols("avatar, use_custom_avatar").Update(u); err != nil { return fmt.Errorf("UpdateUser: %v", err) } return nil diff --git a/models/user_follow.go b/models/user_follow.go index 8321d950774de..8832aa2f18dca 100644 --- a/models/user_follow.go +++ b/models/user_follow.go @@ -5,6 +5,7 @@ package models import ( + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" ) @@ -16,9 +17,13 @@ type Follow struct { CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` } +func init() { + db.RegisterModel(new(Follow)) +} + // IsFollowing returns true if user is following followID. func IsFollowing(userID, followID int64) bool { - has, _ := x.Get(&Follow{UserID: userID, FollowID: followID}) + has, _ := db.GetEngine(db.DefaultContext).Get(&Follow{UserID: userID, FollowID: followID}) return has } @@ -28,7 +33,7 @@ func FollowUser(userID, followID int64) (err error) { return nil } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -54,7 +59,7 @@ func UnfollowUser(userID, followID int64) (err error) { return nil } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err diff --git a/models/user_follow_test.go b/models/user_follow_test.go index a30944f03fa5e..ae8faad6fa976 100644 --- a/models/user_follow_test.go +++ b/models/user_follow_test.go @@ -7,24 +7,25 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestIsFollowing(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) assert.True(t, IsFollowing(4, 2)) assert.False(t, IsFollowing(2, 4)) - assert.False(t, IsFollowing(5, NonexistentID)) - assert.False(t, IsFollowing(NonexistentID, 5)) - assert.False(t, IsFollowing(NonexistentID, NonexistentID)) + assert.False(t, IsFollowing(5, db.NonexistentID)) + assert.False(t, IsFollowing(db.NonexistentID, 5)) + assert.False(t, IsFollowing(db.NonexistentID, db.NonexistentID)) } func TestFollowUser(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testSuccess := func(followerID, followedID int64) { assert.NoError(t, FollowUser(followerID, followedID)) - AssertExistsAndLoadBean(t, &Follow{UserID: followerID, FollowID: followedID}) + db.AssertExistsAndLoadBean(t, &Follow{UserID: followerID, FollowID: followedID}) } testSuccess(4, 2) testSuccess(5, 2) @@ -35,11 +36,11 @@ func TestFollowUser(t *testing.T) { } func TestUnfollowUser(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testSuccess := func(followerID, followedID int64) { assert.NoError(t, UnfollowUser(followerID, followedID)) - AssertNotExistsBean(t, &Follow{UserID: followerID, FollowID: followedID}) + db.AssertNotExistsBean(t, &Follow{UserID: followerID, FollowID: followedID}) } testSuccess(4, 2) testSuccess(5, 2) diff --git a/models/user_heatmap.go b/models/user_heatmap.go index 306bd1819b706..3e94a6f9b7031 100644 --- a/models/user_heatmap.go +++ b/models/user_heatmap.go @@ -5,6 +5,7 @@ package models import ( + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" ) @@ -58,7 +59,7 @@ func getUserHeatmapData(user *User, team *Team, doer *User) ([]*UserHeatmapData, return nil, err } - return hdata, x. + return hdata, db.GetEngine(db.DefaultContext). Select(groupBy+" AS timestamp, count(user_id) as contributions"). Table("action"). Where(cond). diff --git a/models/user_heatmap_test.go b/models/user_heatmap_test.go index bdf77666528c0..c40e3415935e2 100644 --- a/models/user_heatmap_test.go +++ b/models/user_heatmap_test.go @@ -8,6 +8,7 @@ import ( "fmt" "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/json" "github.com/stretchr/testify/assert" @@ -36,13 +37,13 @@ func TestGetUserHeatmapDataByUser(t *testing.T) { {10, 10, 3, `[{"timestamp":1603009800,"contributions":1},{"timestamp":1603010700,"contributions":2}]`}, } // Prepare - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) for i, tc := range testCases { - user := AssertExistsAndLoadBean(t, &User{ID: tc.userID}).(*User) + user := db.AssertExistsAndLoadBean(t, &User{ID: tc.userID}).(*User) doer := &User{ID: tc.doerID} - _, err := loadBeanIfExists(doer) + _, err := db.LoadBeanIfExists(doer) assert.NoError(t, err) if tc.doerID == 0 { doer = nil diff --git a/models/user_mail.go b/models/user_mail.go index f8b084a0064b5..caa931788d5bb 100644 --- a/models/user_mail.go +++ b/models/user_mail.go @@ -10,6 +10,7 @@ import ( "net/mail" "strings" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -28,6 +29,10 @@ type EmailAddress struct { IsPrimary bool `xorm:"DEFAULT(false) NOT NULL"` } +func init() { + db.RegisterModel(new(EmailAddress)) +} + // BeforeInsert will be invoked by XORM before inserting a record func (email *EmailAddress) BeforeInsert() { if email.LowerEmail == "" { @@ -53,7 +58,7 @@ func ValidateEmail(email string) error { // GetEmailAddresses returns all email addresses belongs to given user. func GetEmailAddresses(uid int64) ([]*EmailAddress, error) { emails := make([]*EmailAddress, 0, 5) - if err := x. + if err := db.GetEngine(db.DefaultContext). Where("uid=?", uid). Asc("id"). Find(&emails); err != nil { @@ -66,7 +71,7 @@ func GetEmailAddresses(uid int64) ([]*EmailAddress, error) { func GetEmailAddressByID(uid, id int64) (*EmailAddress, error) { // User ID is required for security reasons email := &EmailAddress{UID: uid} - if has, err := x.ID(id).Get(email); err != nil { + if has, err := db.GetEngine(db.DefaultContext).ID(id).Get(email); err != nil { return nil, err } else if !has { return nil, nil @@ -75,7 +80,7 @@ func GetEmailAddressByID(uid, id int64) (*EmailAddress, error) { } // isEmailActive check if email is activated with a different emailID -func isEmailActive(e Engine, email string, excludeEmailID int64) (bool, error) { +func isEmailActive(e db.Engine, email string, excludeEmailID int64) (bool, error) { if len(email) == 0 { return true, nil } @@ -99,7 +104,7 @@ func isEmailActive(e Engine, email string, excludeEmailID int64) (bool, error) { return false, nil } -func isEmailUsed(e Engine, email string) (bool, error) { +func isEmailUsed(e db.Engine, email string) (bool, error) { if len(email) == 0 { return true, nil } @@ -109,10 +114,10 @@ func isEmailUsed(e Engine, email string) (bool, error) { // IsEmailUsed returns true if the email has been used. func IsEmailUsed(email string) (bool, error) { - return isEmailUsed(x, email) + return isEmailUsed(db.GetEngine(db.DefaultContext), email) } -func addEmailAddress(e Engine, email *EmailAddress) error { +func addEmailAddress(e db.Engine, email *EmailAddress) error { email.Email = strings.TrimSpace(email.Email) used, err := isEmailUsed(e, email.Email) if err != nil { @@ -131,7 +136,7 @@ func addEmailAddress(e Engine, email *EmailAddress) error { // AddEmailAddress adds an email address to given user. func AddEmailAddress(email *EmailAddress) error { - return addEmailAddress(x, email) + return addEmailAddress(db.GetEngine(db.DefaultContext), email) } // AddEmailAddresses adds an email address to given user. @@ -154,7 +159,7 @@ func AddEmailAddresses(emails []*EmailAddress) error { } } - if _, err := x.Insert(emails); err != nil { + if _, err := db.GetEngine(db.DefaultContext).Insert(emails); err != nil { return fmt.Errorf("Insert: %v", err) } @@ -163,7 +168,7 @@ func AddEmailAddresses(emails []*EmailAddress) error { // Activate activates the email address to given user. func (email *EmailAddress) Activate() error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -174,7 +179,7 @@ func (email *EmailAddress) Activate() error { return sess.Commit() } -func (email *EmailAddress) updateActivation(e Engine, activate bool) error { +func (email *EmailAddress) updateActivation(e db.Engine, activate bool) error { user, err := getUserByID(e, email.UID) if err != nil { return err @@ -201,12 +206,12 @@ func DeleteEmailAddress(email *EmailAddress) (err error) { UID: email.UID, } if email.ID > 0 { - deleted, err = x.ID(email.ID).Delete(&address) + deleted, err = db.GetEngine(db.DefaultContext).ID(email.ID).Delete(&address) } else { if email.Email != "" && email.LowerEmail == "" { email.LowerEmail = strings.ToLower(email.Email) } - deleted, err = x. + deleted, err = db.GetEngine(db.DefaultContext). Where("lower_email=?", email.LowerEmail). Delete(&address) } @@ -232,7 +237,7 @@ func DeleteEmailAddresses(emails []*EmailAddress) (err error) { // MakeEmailPrimary sets primary email address of given user. func MakeEmailPrimary(email *EmailAddress) error { - has, err := x.Get(email) + has, err := db.GetEngine(db.DefaultContext).Get(email) if err != nil { return err } else if !has { @@ -244,14 +249,14 @@ func MakeEmailPrimary(email *EmailAddress) error { } user := &User{} - has, err = x.ID(email.UID).Get(user) + has, err = db.GetEngine(db.DefaultContext).ID(email.UID).Get(user) if err != nil { return err } else if !has { return ErrUserNotExist{email.UID, "", 0} } - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -296,7 +301,7 @@ const ( // SearchEmailOptions are options to search e-mail addresses for the admin panel type SearchEmailOptions struct { - ListOptions + db.ListOptions Keyword string SortType SearchEmailOrderBy IsPrimary util.OptionalBool @@ -341,7 +346,7 @@ func SearchEmails(opts *SearchEmailOptions) ([]*SearchEmailResult, int64, error) cond = cond.And(builder.Eq{"email_address.is_activated": false}) } - count, err := x.Join("INNER", "`user`", "`user`.ID = email_address.uid"). + count, err := db.GetEngine(db.DefaultContext).Join("INNER", "`user`", "`user`.ID = email_address.uid"). Where(cond).Count(new(EmailAddress)) if err != nil { return nil, 0, fmt.Errorf("Count: %v", err) @@ -352,10 +357,10 @@ func SearchEmails(opts *SearchEmailOptions) ([]*SearchEmailResult, int64, error) orderby = SearchEmailOrderByEmail.String() } - opts.setDefaultValues() + opts.SetDefaultValues() emails := make([]*SearchEmailResult, 0, opts.PageSize) - err = x.Table("email_address"). + err = db.GetEngine(db.DefaultContext).Table("email_address"). Select("email_address.*, `user`.name, `user`.full_name"). Join("INNER", "`user`", "`user`.ID = email_address.uid"). Where(cond). @@ -369,7 +374,7 @@ func SearchEmails(opts *SearchEmailOptions) ([]*SearchEmailResult, int64, error) // ActivateUserEmail will change the activated state of an email address, // either primary or secondary (all in the email_address table) func ActivateUserEmail(userID int64, email string, activate bool) (err error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err diff --git a/models/user_mail_test.go b/models/user_mail_test.go index 829a38c18dbbf..384f28b7bf4ed 100644 --- a/models/user_mail_test.go +++ b/models/user_mail_test.go @@ -7,13 +7,14 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) func TestGetEmailAddresses(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) emails, _ := GetEmailAddresses(int64(1)) if assert.Len(t, emails, 3) { @@ -30,7 +31,7 @@ func TestGetEmailAddresses(t *testing.T) { } func TestIsEmailUsed(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) isExist, _ := IsEmailUsed("") assert.True(t, isExist) @@ -41,7 +42,7 @@ func TestIsEmailUsed(t *testing.T) { } func TestAddEmailAddress(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) assert.NoError(t, AddEmailAddress(&EmailAddress{ Email: "user1234567890@example.com", @@ -60,7 +61,7 @@ func TestAddEmailAddress(t *testing.T) { } func TestAddEmailAddresses(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // insert multiple email address emails := make([]*EmailAddress, 2) @@ -83,7 +84,7 @@ func TestAddEmailAddresses(t *testing.T) { } func TestDeleteEmailAddress(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) assert.NoError(t, DeleteEmailAddress(&EmailAddress{ UID: int64(1), @@ -108,7 +109,7 @@ func TestDeleteEmailAddress(t *testing.T) { } func TestDeleteEmailAddresses(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // delete multiple email address emails := make([]*EmailAddress, 2) @@ -131,7 +132,7 @@ func TestDeleteEmailAddresses(t *testing.T) { } func TestMakeEmailPrimary(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) email := &EmailAddress{ Email: "user567890@example.com", @@ -165,7 +166,7 @@ func TestMakeEmailPrimary(t *testing.T) { } func TestActivate(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) email := &EmailAddress{ ID: int64(1), @@ -184,11 +185,11 @@ func TestActivate(t *testing.T) { } func TestListEmails(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // Must find all users and their emails opts := &SearchEmailOptions{ - ListOptions: ListOptions{ + ListOptions: db.ListOptions{ PageSize: 10000, }, } @@ -240,7 +241,7 @@ func TestListEmails(t *testing.T) { // Must find more than one page, but retrieve only one opts = &SearchEmailOptions{ - ListOptions: ListOptions{ + ListOptions: db.ListOptions{ PageSize: 5, Page: 1, }, diff --git a/models/user_openid.go b/models/user_openid.go index 597f19d77d6ee..17a58536a2384 100644 --- a/models/user_openid.go +++ b/models/user_openid.go @@ -7,6 +7,7 @@ package models import ( "errors" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/auth/openid" "code.gitea.io/gitea/modules/log" ) @@ -22,10 +23,14 @@ type UserOpenID struct { Show bool `xorm:"DEFAULT false"` } +func init() { + db.RegisterModel(new(UserOpenID)) +} + // GetUserOpenIDs returns all openid addresses that belongs to given user. func GetUserOpenIDs(uid int64) ([]*UserOpenID, error) { openids := make([]*UserOpenID, 0, 5) - if err := x. + if err := db.GetEngine(db.DefaultContext). Where("uid=?", uid). Asc("id"). Find(&openids); err != nil { @@ -36,7 +41,7 @@ func GetUserOpenIDs(uid int64) ([]*UserOpenID, error) { } // isOpenIDUsed returns true if the openid has been used. -func isOpenIDUsed(e Engine, uri string) (bool, error) { +func isOpenIDUsed(e db.Engine, uri string) (bool, error) { if len(uri) == 0 { return true, nil } @@ -45,7 +50,7 @@ func isOpenIDUsed(e Engine, uri string) (bool, error) { } // NOTE: make sure openid.URI is normalized already -func addUserOpenID(e Engine, openid *UserOpenID) error { +func addUserOpenID(e db.Engine, openid *UserOpenID) error { used, err := isOpenIDUsed(e, openid.URI) if err != nil { return err @@ -59,7 +64,7 @@ func addUserOpenID(e Engine, openid *UserOpenID) error { // AddUserOpenID adds an pre-verified/normalized OpenID URI to given user. func AddUserOpenID(openid *UserOpenID) error { - return addUserOpenID(x, openid) + return addUserOpenID(db.GetEngine(db.DefaultContext), openid) } // DeleteUserOpenID deletes an openid address of given user. @@ -70,9 +75,9 @@ func DeleteUserOpenID(openid *UserOpenID) (err error) { UID: openid.UID, } if openid.ID > 0 { - deleted, err = x.ID(openid.ID).Delete(&address) + deleted, err = db.GetEngine(db.DefaultContext).ID(openid.ID).Delete(&address) } else { - deleted, err = x. + deleted, err = db.GetEngine(db.DefaultContext). Where("openid=?", openid.URI). Delete(&address) } @@ -87,7 +92,7 @@ func DeleteUserOpenID(openid *UserOpenID) (err error) { // ToggleUserOpenIDVisibility toggles visibility of an openid address of given user. func ToggleUserOpenIDVisibility(id int64) (err error) { - _, err = x.Exec("update `user_open_id` set `show` = not `show` where `id` = ?", id) + _, err = db.GetEngine(db.DefaultContext).Exec("update `user_open_id` set `show` = not `show` where `id` = ?", id) return err } @@ -106,7 +111,7 @@ func GetUserByOpenID(uri string) (*User, error) { // Otherwise, check in openid table oid := &UserOpenID{} - has, err := x.Where("uri=?", uri).Get(oid) + has, err := db.GetEngine(db.DefaultContext).Where("uri=?", uri).Get(oid) if err != nil { return nil, err } diff --git a/models/user_openid_test.go b/models/user_openid_test.go index d04b072279c49..3c17b0742c87c 100644 --- a/models/user_openid_test.go +++ b/models/user_openid_test.go @@ -7,11 +7,12 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestGetUserOpenIDs(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) oids, err := GetUserOpenIDs(int64(1)) if assert.NoError(t, err) && assert.Len(t, oids, 2) { @@ -29,7 +30,7 @@ func TestGetUserOpenIDs(t *testing.T) { } func TestGetUserByOpenID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) _, err := GetUserByOpenID("https://unknown") if assert.Error(t, err) { @@ -48,7 +49,7 @@ func TestGetUserByOpenID(t *testing.T) { } func TestToggleUserOpenIDVisibility(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) oids, err := GetUserOpenIDs(int64(2)) if !assert.NoError(t, err) || !assert.Len(t, oids, 1) { return diff --git a/models/user_redirect.go b/models/user_redirect.go index 1da8b440888ea..fdc730775f70c 100644 --- a/models/user_redirect.go +++ b/models/user_redirect.go @@ -4,7 +4,11 @@ package models -import "strings" +import ( + "strings" + + "code.gitea.io/gitea/models/db" +) // UserRedirect represents that a user name should be redirected to another type UserRedirect struct { @@ -13,11 +17,15 @@ type UserRedirect struct { RedirectUserID int64 // userID to redirect to } +func init() { + db.RegisterModel(new(UserRedirect)) +} + // LookupUserRedirect look up userID if a user has a redirect name func LookupUserRedirect(userName string) (int64, error) { userName = strings.ToLower(userName) redirect := &UserRedirect{LowerName: userName} - if has, err := x.Get(redirect); err != nil { + if has, err := db.GetEngine(db.DefaultContext).Get(redirect); err != nil { return 0, err } else if !has { return 0, ErrUserRedirectNotExist{Name: userName} @@ -26,7 +34,7 @@ func LookupUserRedirect(userName string) (int64, error) { } // newUserRedirect create a new user redirect -func newUserRedirect(e Engine, ID int64, oldUserName, newUserName string) error { +func newUserRedirect(e db.Engine, ID int64, oldUserName, newUserName string) error { oldUserName = strings.ToLower(oldUserName) newUserName = strings.ToLower(newUserName) @@ -45,7 +53,7 @@ func newUserRedirect(e Engine, ID int64, oldUserName, newUserName string) error // deleteUserRedirect delete any redirect from the specified user name to // anything else -func deleteUserRedirect(e Engine, userName string) error { +func deleteUserRedirect(e db.Engine, userName string) error { userName = strings.ToLower(userName) _, err := e.Delete(&UserRedirect{LowerName: userName}) return err diff --git a/models/user_redirect_test.go b/models/user_redirect_test.go index 791c920bcf05d..346bf98b846e3 100644 --- a/models/user_redirect_test.go +++ b/models/user_redirect_test.go @@ -7,11 +7,12 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestLookupUserRedirect(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) userID, err := LookupUserRedirect("olduser1") assert.NoError(t, err) @@ -23,16 +24,16 @@ func TestLookupUserRedirect(t *testing.T) { func TestNewUserRedirect(t *testing.T) { // redirect to a completely new name - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) - assert.NoError(t, newUserRedirect(x, user.ID, user.Name, "newusername")) + user := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + assert.NoError(t, newUserRedirect(db.GetEngine(db.DefaultContext), user.ID, user.Name, "newusername")) - AssertExistsAndLoadBean(t, &UserRedirect{ + db.AssertExistsAndLoadBean(t, &UserRedirect{ LowerName: user.LowerName, RedirectUserID: user.ID, }) - AssertExistsAndLoadBean(t, &UserRedirect{ + db.AssertExistsAndLoadBean(t, &UserRedirect{ LowerName: "olduser1", RedirectUserID: user.ID, }) @@ -40,16 +41,16 @@ func TestNewUserRedirect(t *testing.T) { func TestNewUserRedirect2(t *testing.T) { // redirect to previously used name - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) - assert.NoError(t, newUserRedirect(x, user.ID, user.Name, "olduser1")) + user := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + assert.NoError(t, newUserRedirect(db.GetEngine(db.DefaultContext), user.ID, user.Name, "olduser1")) - AssertExistsAndLoadBean(t, &UserRedirect{ + db.AssertExistsAndLoadBean(t, &UserRedirect{ LowerName: user.LowerName, RedirectUserID: user.ID, }) - AssertNotExistsBean(t, &UserRedirect{ + db.AssertNotExistsBean(t, &UserRedirect{ LowerName: "olduser1", RedirectUserID: user.ID, }) @@ -57,12 +58,12 @@ func TestNewUserRedirect2(t *testing.T) { func TestNewUserRedirect3(t *testing.T) { // redirect for a previously-unredirected user - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - assert.NoError(t, newUserRedirect(x, user.ID, user.Name, "newusername")) + user := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + assert.NoError(t, newUserRedirect(db.GetEngine(db.DefaultContext), user.ID, user.Name, "newusername")) - AssertExistsAndLoadBean(t, &UserRedirect{ + db.AssertExistsAndLoadBean(t, &UserRedirect{ LowerName: user.LowerName, RedirectUserID: user.ID, }) diff --git a/models/user_test.go b/models/user_test.go index a76bca0ed557f..bf796a8c6252f 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -10,6 +10,8 @@ import ( "strings" "testing" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -17,8 +19,16 @@ import ( "github.com/stretchr/testify/assert" ) +func TestOAuth2Application_LoadUser(t *testing.T) { + assert.NoError(t, db.PrepareTestDatabase()) + app := db.AssertExistsAndLoadBean(t, &login.OAuth2Application{ID: 1}).(*login.OAuth2Application) + user, err := GetUserByID(app.UID) + assert.NoError(t, err) + assert.NotNil(t, user) +} + func TestUserIsPublicMember(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) tt := []struct { uid int64 @@ -44,7 +54,7 @@ func testUserIsPublicMember(t *testing.T, uid, orgID int64, expected bool) { } func TestIsUserOrgOwner(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) tt := []struct { uid int64 @@ -70,7 +80,7 @@ func testIsUserOrgOwner(t *testing.T, uid, orgID int64, expected bool) { } func TestGetUserEmailsByNames(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // ignore none active user email assert.Equal(t, []string{"user8@example.com"}, GetUserEmailsByNames([]string{"user8", "user9"})) @@ -80,12 +90,12 @@ func TestGetUserEmailsByNames(t *testing.T) { } func TestCanCreateOrganization(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - admin := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + admin := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) assert.True(t, admin.CanCreateOrganization()) - user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + user := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) assert.True(t, user.CanCreateOrganization()) // Disable user create organization permission. user.AllowCreateOrganization = false @@ -98,7 +108,7 @@ func TestCanCreateOrganization(t *testing.T) { } func TestSearchUsers(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testSuccess := func(opts *SearchUserOptions, expectedUserOrOrgIDs []int64) { users, _, err := SearchUsers(opts) assert.NoError(t, err) @@ -115,19 +125,19 @@ func TestSearchUsers(t *testing.T) { testSuccess(opts, expectedOrgIDs) } - testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: ListOptions{Page: 1, PageSize: 2}}, + testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1, PageSize: 2}}, []int64{3, 6}) - testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: ListOptions{Page: 2, PageSize: 2}}, + testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 2, PageSize: 2}}, []int64{7, 17}) - testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: ListOptions{Page: 3, PageSize: 2}}, + testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 3, PageSize: 2}}, []int64{19, 25}) - testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: ListOptions{Page: 4, PageSize: 2}}, + testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 4, PageSize: 2}}, []int64{26}) - testOrgSuccess(&SearchUserOptions{ListOptions: ListOptions{Page: 5, PageSize: 2}}, + testOrgSuccess(&SearchUserOptions{ListOptions: db.ListOptions{Page: 5, PageSize: 2}}, []int64{}) // test users @@ -136,30 +146,30 @@ func TestSearchUsers(t *testing.T) { testSuccess(opts, expectedUserIDs) } - testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: ListOptions{Page: 1}}, + testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}}, []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30}) - testUserSuccess(&SearchUserOptions{ListOptions: ListOptions{Page: 1}, IsActive: util.OptionalBoolFalse}, + testUserSuccess(&SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolFalse}, []int64{9}) - testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, + testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 28, 29, 30}) - testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, + testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, []int64{1, 10, 11, 12, 13, 14, 15, 16, 18}) // order by name asc default - testUserSuccess(&SearchUserOptions{Keyword: "user1", ListOptions: ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, + testUserSuccess(&SearchUserOptions{Keyword: "user1", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, []int64{1, 10, 11, 12, 13, 14, 15, 16, 18}) } func TestDeleteUser(t *testing.T) { test := func(userID int64) { - assert.NoError(t, PrepareTestDatabase()) - user := AssertExistsAndLoadBean(t, &User{ID: userID}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + user := db.AssertExistsAndLoadBean(t, &User{ID: userID}).(*User) ownedRepos := make([]*Repository, 0, 10) - assert.NoError(t, x.Find(&ownedRepos, &Repository{OwnerID: userID})) + assert.NoError(t, db.GetEngine(db.DefaultContext).Find(&ownedRepos, &Repository{OwnerID: userID})) if len(ownedRepos) > 0 { err := DeleteUser(user) assert.Error(t, err) @@ -168,7 +178,7 @@ func TestDeleteUser(t *testing.T) { } orgUsers := make([]*OrgUser, 0, 10) - assert.NoError(t, x.Find(&orgUsers, &OrgUser{UID: userID})) + assert.NoError(t, db.GetEngine(db.DefaultContext).Find(&orgUsers, &OrgUser{UID: userID})) for _, orgUser := range orgUsers { if err := RemoveOrgUser(orgUser.OrgID, orgUser.UID); err != nil { assert.True(t, IsErrLastOrgOwner(err)) @@ -176,7 +186,7 @@ func TestDeleteUser(t *testing.T) { } } assert.NoError(t, DeleteUser(user)) - AssertNotExistsBean(t, &User{ID: userID}) + db.AssertNotExistsBean(t, &User{ID: userID}) CheckConsistencyFor(t, &User{}, &Repository{}) } test(2) @@ -184,12 +194,12 @@ func TestDeleteUser(t *testing.T) { test(8) test(11) - org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) + org := db.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) assert.Error(t, DeleteUser(org)) } func TestEmailNotificationPreferences(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) for _, test := range []struct { expected string @@ -205,7 +215,7 @@ func TestEmailNotificationPreferences(t *testing.T) { {EmailNotificationsEnabled, 8}, {EmailNotificationsOnMention, 9}, } { - user := AssertExistsAndLoadBean(t, &User{ID: test.userID}).(*User) + user := db.AssertExistsAndLoadBean(t, &User{ID: test.userID}).(*User) assert.Equal(t, test.expected, user.EmailNotifications()) // Try all possible settings @@ -257,10 +267,10 @@ func BenchmarkHashPassword(b *testing.B) { } func TestGetOrgRepositoryIDs(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) - user5 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + user2 := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + user4 := db.AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) + user5 := db.AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) accessibleRepos, err := user2.GetOrgRepositoryIDs() assert.NoError(t, err) @@ -280,7 +290,7 @@ func TestGetOrgRepositoryIDs(t *testing.T) { func TestNewGitSig(t *testing.T) { users := make([]*User, 0, 20) - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() sess.Find(&users) @@ -295,7 +305,7 @@ func TestNewGitSig(t *testing.T) { func TestDisplayName(t *testing.T) { users := make([]*User, 0, 20) - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() sess.Find(&users) @@ -370,7 +380,7 @@ func TestCreateUser_Issue5882(t *testing.T) { } func TestGetUserIDsByNames(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // ignore non existing IDs, err := GetUserIDsByNames([]string{"user1", "user2", "none_existing_user"}, true) @@ -384,7 +394,7 @@ func TestGetUserIDsByNames(t *testing.T) { } func TestGetMaileableUsersByIDs(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) results, err := GetMaileableUsersByIDs([]int64{1, 4}, false) assert.NoError(t, err) @@ -403,10 +413,10 @@ func TestGetMaileableUsersByIDs(t *testing.T) { } func TestAddLdapSSHPublicKeys(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - s := &LoginSource{ID: 1} + user := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + s := &login.Source{ID: 1} testCases := []struct { keyString string @@ -471,19 +481,19 @@ ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ib } func TestUpdateUser(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + assert.NoError(t, db.PrepareTestDatabase()) + user := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) user.KeepActivityPrivate = true assert.NoError(t, UpdateUser(user)) - user = AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + user = db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) assert.True(t, user.KeepActivityPrivate) setting.Service.AllowedUserVisibilityModesSlice = []bool{true, false, false} user.KeepActivityPrivate = false user.Visibility = structs.VisibleTypePrivate assert.Error(t, UpdateUser(user)) - user = AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + user = db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) assert.True(t, user.KeepActivityPrivate) user.Email = "no mail@mail.org" diff --git a/models/userlist.go b/models/userlist.go index 53e380cd72cf6..aebdb4f48c253 100644 --- a/models/userlist.go +++ b/models/userlist.go @@ -7,6 +7,8 @@ package models import ( "fmt" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/log" ) @@ -28,7 +30,7 @@ func (users UserList) IsUserOrgOwner(orgID int64) map[int64]bool { for _, user := range users { results[user.ID] = false // Set default to false } - ownerMaps, err := users.loadOrganizationOwners(x, orgID) + ownerMaps, err := users.loadOrganizationOwners(db.GetEngine(db.DefaultContext), orgID) if err == nil { for _, owner := range ownerMaps { results[owner.UID] = true @@ -37,7 +39,7 @@ func (users UserList) IsUserOrgOwner(orgID int64) map[int64]bool { return results } -func (users UserList) loadOrganizationOwners(e Engine, orgID int64) (map[int64]*TeamUser, error) { +func (users UserList) loadOrganizationOwners(e db.Engine, orgID int64) (map[int64]*TeamUser, error) { if len(users) == 0 { return nil, nil } @@ -68,7 +70,7 @@ func (users UserList) GetTwoFaStatus() map[int64]bool { for _, user := range users { results[user.ID] = false // Set default to false } - tokenMaps, err := users.loadTwoFactorStatus(x) + tokenMaps, err := users.loadTwoFactorStatus(db.GetEngine(db.DefaultContext)) if err == nil { for _, token := range tokenMaps { results[token.UID] = true @@ -78,13 +80,13 @@ func (users UserList) GetTwoFaStatus() map[int64]bool { return results } -func (users UserList) loadTwoFactorStatus(e Engine) (map[int64]*TwoFactor, error) { +func (users UserList) loadTwoFactorStatus(e db.Engine) (map[int64]*login.TwoFactor, error) { if len(users) == 0 { return nil, nil } userIDs := users.getUserIDs() - tokenMaps := make(map[int64]*TwoFactor, len(userIDs)) + tokenMaps := make(map[int64]*login.TwoFactor, len(userIDs)) err := e. In("uid", userIDs). Find(&tokenMaps) diff --git a/models/userlist_test.go b/models/userlist_test.go index 5c1af6c200805..9e75f783f1921 100644 --- a/models/userlist_test.go +++ b/models/userlist_test.go @@ -8,11 +8,12 @@ import ( "fmt" "testing" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestUserListIsPublicMember(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) tt := []struct { orgid int64 expected map[int64]bool @@ -38,7 +39,7 @@ func testUserListIsPublicMember(t *testing.T, orgID int64, expected map[int64]bo } func TestUserListIsUserOrgOwner(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) tt := []struct { orgid int64 expected map[int64]bool @@ -64,7 +65,7 @@ func testUserListIsUserOrgOwner(t *testing.T, orgID int64, expected map[int64]bo } func TestUserListIsTwoFaEnrolled(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) tt := []struct { orgid int64 expected map[int64]bool diff --git a/models/webhook.go b/models/webhook.go index 79ce70a0de012..9d04f8f5e4fb4 100644 --- a/models/webhook.go +++ b/models/webhook.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -159,6 +160,11 @@ type Webhook struct { UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } +func init() { + db.RegisterModel(new(Webhook)) + db.RegisterModel(new(HookTask)) +} + // AfterLoad updates the webhook object upon setting a column func (w *Webhook) AfterLoad() { w.HookEvent = &HookEvent{} @@ -345,10 +351,10 @@ func (w *Webhook) EventsArray() []string { // CreateWebhook creates a new web hook. func CreateWebhook(w *Webhook) error { - return createWebhook(x, w) + return createWebhook(db.GetEngine(db.DefaultContext), w) } -func createWebhook(e Engine, w *Webhook) error { +func createWebhook(e db.Engine, w *Webhook) error { w.Type = strings.TrimSpace(w.Type) _, err := e.Insert(w) return err @@ -357,7 +363,7 @@ func createWebhook(e Engine, w *Webhook) error { // getWebhook uses argument bean as query condition, // ID must be specified and do not assign unnecessary fields. func getWebhook(bean *Webhook) (*Webhook, error) { - has, err := x.Get(bean) + has, err := db.GetEngine(db.DefaultContext).Get(bean) if err != nil { return nil, err } else if !has { @@ -391,7 +397,7 @@ func GetWebhookByOrgID(orgID, id int64) (*Webhook, error) { // ListWebhookOptions are options to filter webhooks on ListWebhooksByOpts type ListWebhookOptions struct { - ListOptions + db.ListOptions RepoID int64 OrgID int64 IsActive util.OptionalBool @@ -411,11 +417,11 @@ func (opts *ListWebhookOptions) toCond() builder.Cond { return cond } -func listWebhooksByOpts(e Engine, opts *ListWebhookOptions) ([]*Webhook, error) { +func listWebhooksByOpts(e db.Engine, opts *ListWebhookOptions) ([]*Webhook, error) { sess := e.Where(opts.toCond()) if opts.Page != 0 { - sess = opts.setSessionPagination(sess) + sess = db.SetSessionPagination(sess, opts) webhooks := make([]*Webhook, 0, opts.PageSize) err := sess.Find(&webhooks) return webhooks, err @@ -428,20 +434,20 @@ func listWebhooksByOpts(e Engine, opts *ListWebhookOptions) ([]*Webhook, error) // ListWebhooksByOpts return webhooks based on options func ListWebhooksByOpts(opts *ListWebhookOptions) ([]*Webhook, error) { - return listWebhooksByOpts(x, opts) + return listWebhooksByOpts(db.GetEngine(db.DefaultContext), opts) } // CountWebhooksByOpts count webhooks based on options and ignore pagination func CountWebhooksByOpts(opts *ListWebhookOptions) (int64, error) { - return x.Where(opts.toCond()).Count(&Webhook{}) + return db.GetEngine(db.DefaultContext).Where(opts.toCond()).Count(&Webhook{}) } // GetDefaultWebhooks returns all admin-default webhooks. func GetDefaultWebhooks() ([]*Webhook, error) { - return getDefaultWebhooks(x) + return getDefaultWebhooks(db.GetEngine(db.DefaultContext)) } -func getDefaultWebhooks(e Engine) ([]*Webhook, error) { +func getDefaultWebhooks(e db.Engine) ([]*Webhook, error) { webhooks := make([]*Webhook, 0, 5) return webhooks, e. Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, false). @@ -451,7 +457,7 @@ func getDefaultWebhooks(e Engine) ([]*Webhook, error) { // GetSystemOrDefaultWebhook returns admin system or default webhook by given ID. func GetSystemOrDefaultWebhook(id int64) (*Webhook, error) { webhook := &Webhook{ID: id} - has, err := x. + has, err := db.GetEngine(db.DefaultContext). Where("repo_id=? AND org_id=?", 0, 0). Get(webhook) if err != nil { @@ -464,10 +470,10 @@ func GetSystemOrDefaultWebhook(id int64) (*Webhook, error) { // GetSystemWebhooks returns all admin system webhooks. func GetSystemWebhooks() ([]*Webhook, error) { - return getSystemWebhooks(x) + return getSystemWebhooks(db.GetEngine(db.DefaultContext)) } -func getSystemWebhooks(e Engine) ([]*Webhook, error) { +func getSystemWebhooks(e db.Engine) ([]*Webhook, error) { webhooks := make([]*Webhook, 0, 5) return webhooks, e. Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, true). @@ -476,20 +482,20 @@ func getSystemWebhooks(e Engine) ([]*Webhook, error) { // UpdateWebhook updates information of webhook. func UpdateWebhook(w *Webhook) error { - _, err := x.ID(w.ID).AllCols().Update(w) + _, err := db.GetEngine(db.DefaultContext).ID(w.ID).AllCols().Update(w) return err } // UpdateWebhookLastStatus updates last status of webhook. func UpdateWebhookLastStatus(w *Webhook) error { - _, err := x.ID(w.ID).Cols("last_status").Update(w) + _, err := db.GetEngine(db.DefaultContext).ID(w.ID).Cols("last_status").Update(w) return err } // deleteWebhook uses argument bean as query condition, // ID must be specified and do not assign unnecessary fields. func deleteWebhook(bean *Webhook) (err error) { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err = sess.Begin(); err != nil { return err @@ -524,7 +530,7 @@ func DeleteWebhookByOrgID(orgID, id int64) error { // DeleteDefaultSystemWebhook deletes an admin-configured default or system webhook (where Org and Repo ID both 0) func DeleteDefaultSystemWebhook(id int64) error { - sess := x.NewSession() + sess := db.NewSession(db.DefaultContext) defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -547,7 +553,7 @@ func DeleteDefaultSystemWebhook(id int64) error { } // copyDefaultWebhooksToRepo creates copies of the default webhooks in a new repo -func copyDefaultWebhooksToRepo(e Engine, repoID int64) error { +func copyDefaultWebhooksToRepo(e db.Engine, repoID int64) error { ws, err := getDefaultWebhooks(e) if err != nil { return fmt.Errorf("GetDefaultWebhooks: %v", err) @@ -707,7 +713,7 @@ func (t *HookTask) simpleMarshalJSON(v interface{}) string { // HookTasks returns a list of hook tasks by given conditions. func HookTasks(hookID int64, page int) ([]*HookTask, error) { tasks := make([]*HookTask, 0, setting.Webhook.PagingNum) - return tasks, x. + return tasks, db.GetEngine(db.DefaultContext). Limit(setting.Webhook.PagingNum, (page-1)*setting.Webhook.PagingNum). Where("hook_id=?", hookID). Desc("id"). @@ -717,10 +723,10 @@ func HookTasks(hookID int64, page int) ([]*HookTask, error) { // CreateHookTask creates a new hook task, // it handles conversion from Payload to PayloadContent. func CreateHookTask(t *HookTask) error { - return createHookTask(x, t) + return createHookTask(db.GetEngine(db.DefaultContext), t) } -func createHookTask(e Engine, t *HookTask) error { +func createHookTask(e db.Engine, t *HookTask) error { data, err := t.Payloader.JSONPayload() if err != nil { return err @@ -733,14 +739,14 @@ func createHookTask(e Engine, t *HookTask) error { // UpdateHookTask updates information of hook task. func UpdateHookTask(t *HookTask) error { - _, err := x.ID(t.ID).AllCols().Update(t) + _, err := db.GetEngine(db.DefaultContext).ID(t.ID).AllCols().Update(t) return err } // FindUndeliveredHookTasks represents find the undelivered hook tasks func FindUndeliveredHookTasks() ([]*HookTask, error) { tasks := make([]*HookTask, 0, 10) - if err := x.Where("is_delivered=?", false).Find(&tasks); err != nil { + if err := db.GetEngine(db.DefaultContext).Where("is_delivered=?", false).Find(&tasks); err != nil { return nil, err } return tasks, nil @@ -749,7 +755,7 @@ func FindUndeliveredHookTasks() ([]*HookTask, error) { // FindRepoUndeliveredHookTasks represents find the undelivered hook tasks of one repository func FindRepoUndeliveredHookTasks(repoID int64) ([]*HookTask, error) { tasks := make([]*HookTask, 0, 5) - if err := x.Where("repo_id=? AND is_delivered=?", repoID, false).Find(&tasks); err != nil { + if err := db.GetEngine(db.DefaultContext).Where("repo_id=? AND is_delivered=?", repoID, false).Find(&tasks); err != nil { return nil, err } return tasks, nil @@ -761,7 +767,7 @@ func CleanupHookTaskTable(ctx context.Context, cleanupType HookTaskCleanupType, if cleanupType == OlderThan { deleteOlderThan := time.Now().Add(-olderThan).UnixNano() - deletes, err := x. + deletes, err := db.GetEngine(db.DefaultContext). Where("is_delivered = ? and delivered < ?", true, deleteOlderThan). Delete(new(HookTask)) if err != nil { @@ -770,7 +776,7 @@ func CleanupHookTaskTable(ctx context.Context, cleanupType HookTaskCleanupType, log.Trace("Deleted %d rows from hook_task", deletes) } else if cleanupType == PerWebhook { hookIDs := make([]int64, 0, 10) - err := x.Table("webhook"). + err := db.GetEngine(db.DefaultContext).Table("webhook"). Where("id > 0"). Cols("id"). Find(&hookIDs) @@ -795,7 +801,7 @@ func CleanupHookTaskTable(ctx context.Context, cleanupType HookTaskCleanupType, func deleteDeliveredHookTasksByWebhook(hookID int64, numberDeliveriesToKeep int) error { log.Trace("Deleting hook_task rows for webhook %d, keeping the most recent %d deliveries", hookID, numberDeliveriesToKeep) deliveryDates := make([]int64, 0, 10) - err := x.Table("hook_task"). + err := db.GetEngine(db.DefaultContext).Table("hook_task"). Where("hook_task.hook_id = ? AND hook_task.is_delivered = ? AND hook_task.delivered is not null", hookID, true). Cols("hook_task.delivered"). Join("INNER", "webhook", "hook_task.hook_id = webhook.id"). @@ -807,7 +813,7 @@ func deleteDeliveredHookTasksByWebhook(hookID int64, numberDeliveriesToKeep int) } if len(deliveryDates) > 0 { - deletes, err := x. + deletes, err := db.GetEngine(db.DefaultContext). Where("hook_id = ? and is_delivered = ? and delivered <= ?", hookID, true, deliveryDates[0]). Delete(new(HookTask)) if err != nil { diff --git a/models/webhook_test.go b/models/webhook_test.go index 625d643856c5f..d48fa365be9c5 100644 --- a/models/webhook_test.go +++ b/models/webhook_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -28,23 +29,23 @@ func TestIsValidHookContentType(t *testing.T) { } func TestWebhook_History(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - webhook := AssertExistsAndLoadBean(t, &Webhook{ID: 1}).(*Webhook) + assert.NoError(t, db.PrepareTestDatabase()) + webhook := db.AssertExistsAndLoadBean(t, &Webhook{ID: 1}).(*Webhook) tasks, err := webhook.History(0) assert.NoError(t, err) if assert.Len(t, tasks, 1) { assert.Equal(t, int64(1), tasks[0].ID) } - webhook = AssertExistsAndLoadBean(t, &Webhook{ID: 2}).(*Webhook) + webhook = db.AssertExistsAndLoadBean(t, &Webhook{ID: 2}).(*Webhook) tasks, err = webhook.History(0) assert.NoError(t, err) assert.Len(t, tasks, 0) } func TestWebhook_UpdateEvent(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - webhook := AssertExistsAndLoadBean(t, &Webhook{ID: 1}).(*Webhook) + assert.NoError(t, db.PrepareTestDatabase()) + webhook := db.AssertExistsAndLoadBean(t, &Webhook{ID: 1}).(*Webhook) hookEvent := &HookEvent{ PushOnly: true, SendEverything: false, @@ -90,35 +91,35 @@ func TestCreateWebhook(t *testing.T) { ContentType: ContentTypeJSON, Events: `{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}`, } - AssertNotExistsBean(t, hook) + db.AssertNotExistsBean(t, hook) assert.NoError(t, CreateWebhook(hook)) - AssertExistsAndLoadBean(t, hook) + db.AssertExistsAndLoadBean(t, hook) } func TestGetWebhookByRepoID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) hook, err := GetWebhookByRepoID(1, 1) assert.NoError(t, err) assert.Equal(t, int64(1), hook.ID) - _, err = GetWebhookByRepoID(NonexistentID, NonexistentID) + _, err = GetWebhookByRepoID(db.NonexistentID, db.NonexistentID) assert.Error(t, err) assert.True(t, IsErrWebhookNotExist(err)) } func TestGetWebhookByOrgID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) hook, err := GetWebhookByOrgID(3, 3) assert.NoError(t, err) assert.Equal(t, int64(3), hook.ID) - _, err = GetWebhookByOrgID(NonexistentID, NonexistentID) + _, err = GetWebhookByOrgID(db.NonexistentID, db.NonexistentID) assert.Error(t, err) assert.True(t, IsErrWebhookNotExist(err)) } func TestGetActiveWebhooksByRepoID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) hooks, err := ListWebhooksByOpts(&ListWebhookOptions{RepoID: 1, IsActive: util.OptionalBoolTrue}) assert.NoError(t, err) if assert.Len(t, hooks, 1) { @@ -128,7 +129,7 @@ func TestGetActiveWebhooksByRepoID(t *testing.T) { } func TestGetWebhooksByRepoID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) hooks, err := ListWebhooksByOpts(&ListWebhookOptions{RepoID: 1}) assert.NoError(t, err) if assert.Len(t, hooks, 2) { @@ -138,7 +139,7 @@ func TestGetWebhooksByRepoID(t *testing.T) { } func TestGetActiveWebhooksByOrgID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) hooks, err := ListWebhooksByOpts(&ListWebhookOptions{OrgID: 3, IsActive: util.OptionalBoolTrue}) assert.NoError(t, err) if assert.Len(t, hooks, 1) { @@ -148,7 +149,7 @@ func TestGetActiveWebhooksByOrgID(t *testing.T) { } func TestGetWebhooksByOrgID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) hooks, err := ListWebhooksByOpts(&ListWebhookOptions{OrgID: 3}) assert.NoError(t, err) if assert.Len(t, hooks, 1) { @@ -158,76 +159,76 @@ func TestGetWebhooksByOrgID(t *testing.T) { } func TestUpdateWebhook(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - hook := AssertExistsAndLoadBean(t, &Webhook{ID: 2}).(*Webhook) + assert.NoError(t, db.PrepareTestDatabase()) + hook := db.AssertExistsAndLoadBean(t, &Webhook{ID: 2}).(*Webhook) hook.IsActive = true hook.ContentType = ContentTypeForm - AssertNotExistsBean(t, hook) + db.AssertNotExistsBean(t, hook) assert.NoError(t, UpdateWebhook(hook)) - AssertExistsAndLoadBean(t, hook) + db.AssertExistsAndLoadBean(t, hook) } func TestDeleteWebhookByRepoID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - AssertExistsAndLoadBean(t, &Webhook{ID: 2, RepoID: 1}) + assert.NoError(t, db.PrepareTestDatabase()) + db.AssertExistsAndLoadBean(t, &Webhook{ID: 2, RepoID: 1}) assert.NoError(t, DeleteWebhookByRepoID(1, 2)) - AssertNotExistsBean(t, &Webhook{ID: 2, RepoID: 1}) + db.AssertNotExistsBean(t, &Webhook{ID: 2, RepoID: 1}) - err := DeleteWebhookByRepoID(NonexistentID, NonexistentID) + err := DeleteWebhookByRepoID(db.NonexistentID, db.NonexistentID) assert.Error(t, err) assert.True(t, IsErrWebhookNotExist(err)) } func TestDeleteWebhookByOrgID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - AssertExistsAndLoadBean(t, &Webhook{ID: 3, OrgID: 3}) + assert.NoError(t, db.PrepareTestDatabase()) + db.AssertExistsAndLoadBean(t, &Webhook{ID: 3, OrgID: 3}) assert.NoError(t, DeleteWebhookByOrgID(3, 3)) - AssertNotExistsBean(t, &Webhook{ID: 3, OrgID: 3}) + db.AssertNotExistsBean(t, &Webhook{ID: 3, OrgID: 3}) - err := DeleteWebhookByOrgID(NonexistentID, NonexistentID) + err := DeleteWebhookByOrgID(db.NonexistentID, db.NonexistentID) assert.Error(t, err) assert.True(t, IsErrWebhookNotExist(err)) } func TestHookTasks(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) hookTasks, err := HookTasks(1, 1) assert.NoError(t, err) if assert.Len(t, hookTasks, 1) { assert.Equal(t, int64(1), hookTasks[0].ID) } - hookTasks, err = HookTasks(NonexistentID, 1) + hookTasks, err = HookTasks(db.NonexistentID, 1) assert.NoError(t, err) assert.Len(t, hookTasks, 0) } func TestCreateHookTask(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) hookTask := &HookTask{ RepoID: 3, HookID: 3, Payloader: &api.PushPayload{}, } - AssertNotExistsBean(t, hookTask) + db.AssertNotExistsBean(t, hookTask) assert.NoError(t, CreateHookTask(hookTask)) - AssertExistsAndLoadBean(t, hookTask) + db.AssertExistsAndLoadBean(t, hookTask) } func TestUpdateHookTask(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - hook := AssertExistsAndLoadBean(t, &HookTask{ID: 1}).(*HookTask) + hook := db.AssertExistsAndLoadBean(t, &HookTask{ID: 1}).(*HookTask) hook.PayloadContent = "new payload content" hook.DeliveredString = "new delivered string" hook.IsDelivered = true - AssertNotExistsBean(t, hook) + db.AssertNotExistsBean(t, hook) assert.NoError(t, UpdateHookTask(hook)) - AssertExistsAndLoadBean(t, hook) + db.AssertExistsAndLoadBean(t, hook) } func TestCleanupHookTaskTable_PerWebhook_DeletesDelivered(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) hookTask := &HookTask{ RepoID: 3, HookID: 3, @@ -235,32 +236,32 @@ func TestCleanupHookTaskTable_PerWebhook_DeletesDelivered(t *testing.T) { IsDelivered: true, Delivered: time.Now().UnixNano(), } - AssertNotExistsBean(t, hookTask) + db.AssertNotExistsBean(t, hookTask) assert.NoError(t, CreateHookTask(hookTask)) - AssertExistsAndLoadBean(t, hookTask) + db.AssertExistsAndLoadBean(t, hookTask) assert.NoError(t, CleanupHookTaskTable(context.Background(), PerWebhook, 168*time.Hour, 0)) - AssertNotExistsBean(t, hookTask) + db.AssertNotExistsBean(t, hookTask) } func TestCleanupHookTaskTable_PerWebhook_LeavesUndelivered(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) hookTask := &HookTask{ RepoID: 2, HookID: 4, Payloader: &api.PushPayload{}, IsDelivered: false, } - AssertNotExistsBean(t, hookTask) + db.AssertNotExistsBean(t, hookTask) assert.NoError(t, CreateHookTask(hookTask)) - AssertExistsAndLoadBean(t, hookTask) + db.AssertExistsAndLoadBean(t, hookTask) assert.NoError(t, CleanupHookTaskTable(context.Background(), PerWebhook, 168*time.Hour, 0)) - AssertExistsAndLoadBean(t, hookTask) + db.AssertExistsAndLoadBean(t, hookTask) } func TestCleanupHookTaskTable_PerWebhook_LeavesMostRecentTask(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) hookTask := &HookTask{ RepoID: 2, HookID: 4, @@ -268,16 +269,16 @@ func TestCleanupHookTaskTable_PerWebhook_LeavesMostRecentTask(t *testing.T) { IsDelivered: true, Delivered: time.Now().UnixNano(), } - AssertNotExistsBean(t, hookTask) + db.AssertNotExistsBean(t, hookTask) assert.NoError(t, CreateHookTask(hookTask)) - AssertExistsAndLoadBean(t, hookTask) + db.AssertExistsAndLoadBean(t, hookTask) assert.NoError(t, CleanupHookTaskTable(context.Background(), PerWebhook, 168*time.Hour, 1)) - AssertExistsAndLoadBean(t, hookTask) + db.AssertExistsAndLoadBean(t, hookTask) } func TestCleanupHookTaskTable_OlderThan_DeletesDelivered(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) hookTask := &HookTask{ RepoID: 3, HookID: 3, @@ -285,32 +286,32 @@ func TestCleanupHookTaskTable_OlderThan_DeletesDelivered(t *testing.T) { IsDelivered: true, Delivered: time.Now().AddDate(0, 0, -8).UnixNano(), } - AssertNotExistsBean(t, hookTask) + db.AssertNotExistsBean(t, hookTask) assert.NoError(t, CreateHookTask(hookTask)) - AssertExistsAndLoadBean(t, hookTask) + db.AssertExistsAndLoadBean(t, hookTask) assert.NoError(t, CleanupHookTaskTable(context.Background(), OlderThan, 168*time.Hour, 0)) - AssertNotExistsBean(t, hookTask) + db.AssertNotExistsBean(t, hookTask) } func TestCleanupHookTaskTable_OlderThan_LeavesUndelivered(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) hookTask := &HookTask{ RepoID: 2, HookID: 4, Payloader: &api.PushPayload{}, IsDelivered: false, } - AssertNotExistsBean(t, hookTask) + db.AssertNotExistsBean(t, hookTask) assert.NoError(t, CreateHookTask(hookTask)) - AssertExistsAndLoadBean(t, hookTask) + db.AssertExistsAndLoadBean(t, hookTask) assert.NoError(t, CleanupHookTaskTable(context.Background(), OlderThan, 168*time.Hour, 0)) - AssertExistsAndLoadBean(t, hookTask) + db.AssertExistsAndLoadBean(t, hookTask) } func TestCleanupHookTaskTable_OlderThan_LeavesTaskEarlierThanAgeToDelete(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) hookTask := &HookTask{ RepoID: 2, HookID: 4, @@ -318,10 +319,10 @@ func TestCleanupHookTaskTable_OlderThan_LeavesTaskEarlierThanAgeToDelete(t *test IsDelivered: true, Delivered: time.Now().AddDate(0, 0, -6).UnixNano(), } - AssertNotExistsBean(t, hookTask) + db.AssertNotExistsBean(t, hookTask) assert.NoError(t, CreateHookTask(hookTask)) - AssertExistsAndLoadBean(t, hookTask) + db.AssertExistsAndLoadBean(t, hookTask) assert.NoError(t, CleanupHookTaskTable(context.Background(), OlderThan, 168*time.Hour, 0)) - AssertExistsAndLoadBean(t, hookTask) + db.AssertExistsAndLoadBean(t, hookTask) } diff --git a/models/wiki_test.go b/models/wiki_test.go index 244c4f2553b21..f1435e8178a6d 100644 --- a/models/wiki_test.go +++ b/models/wiki_test.go @@ -8,37 +8,38 @@ import ( "path/filepath" "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" ) func TestRepository_WikiCloneLink(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) cloneLink := repo.WikiCloneLink() assert.Equal(t, "ssh://runuser@try.gitea.io:3000/user2/repo1.wiki.git", cloneLink.SSH) assert.Equal(t, "https://try.gitea.io/user2/repo1.wiki.git", cloneLink.HTTPS) } func TestWikiPath(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) expected := filepath.Join(setting.RepoRootPath, "user2/repo1.wiki.git") assert.Equal(t, expected, WikiPath("user2", "repo1")) } func TestRepository_WikiPath(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + assert.NoError(t, db.PrepareTestDatabase()) + repo := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) expected := filepath.Join(setting.RepoRootPath, "user2/repo1.wiki.git") assert.Equal(t, expected, repo.WikiPath()) } func TestRepository_HasWiki(t *testing.T) { - PrepareTestEnv(t) - repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + db.PrepareTestEnv(t) + repo1 := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) assert.True(t, repo1.HasWiki()) - repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) + repo2 := db.AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) assert.False(t, repo2.HasWiki()) } diff --git a/modules/activitypub/keypair.go b/modules/activitypub/keypair.go new file mode 100644 index 0000000000000..fe6aba9058ae2 --- /dev/null +++ b/modules/activitypub/keypair.go @@ -0,0 +1,48 @@ +// Copyright 2021 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 activitypub + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" +) + +const rsaBits = 2048 + +// GenerateKeyPair generates a public and private keypair for signing actions by users for activitypub purposes +func GenerateKeyPair() (string, string, error) { + priv, _ := rsa.GenerateKey(rand.Reader, rsaBits) + privPem, err := pemBlockForPriv(priv) + if err != nil { + return "", "", err + } + pubPem, err := pemBlockForPub(&priv.PublicKey) + if err != nil { + return "", "", err + } + return privPem, pubPem, nil +} + +func pemBlockForPriv(priv *rsa.PrivateKey) (string, error) { + privBytes := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(priv), + }) + return string(privBytes), nil +} + +func pemBlockForPub(pub *rsa.PublicKey) (string, error) { + pubASN1, err := x509.MarshalPKIXPublicKey(pub) + if err != nil { + return "", err + } + pubBytes := pem.EncodeToMemory(&pem.Block{ + Type: "PUBLIC KEY", + Bytes: pubASN1, + }) + return string(pubBytes), nil +} diff --git a/modules/activitypub/keypair_test.go b/modules/activitypub/keypair_test.go new file mode 100644 index 0000000000000..5d876937b142c --- /dev/null +++ b/modules/activitypub/keypair_test.go @@ -0,0 +1,63 @@ +// Copyright 2021 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 activitypub + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/pem" + "regexp" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestKeygen(t *testing.T) { + priv, pub, err := GenerateKeyPair() + assert.NoError(t, err) + + assert.NotEmpty(t, priv) + assert.NotEmpty(t, pub) + + assert.Regexp(t, regexp.MustCompile("^-----BEGIN RSA PRIVATE KEY-----.*"), priv) + assert.Regexp(t, regexp.MustCompile("^-----BEGIN PUBLIC KEY-----.*"), pub) + +} + +func TestSignUsingKeys(t *testing.T) { + priv, pub, err := GenerateKeyPair() + assert.NoError(t, err) + + privPem, _ := pem.Decode([]byte(priv)) + if privPem == nil || privPem.Type != "RSA PRIVATE KEY" { + t.Fatal("key is wrong type") + } + + privParsed, err := x509.ParsePKCS1PrivateKey(privPem.Bytes) + assert.NoError(t, err) + + pubPem, _ := pem.Decode([]byte(pub)) + if pubPem == nil || pubPem.Type != "PUBLIC KEY" { + t.Fatal("key failed to decode") + } + + pubParsed, err := x509.ParsePKIXPublicKey(pubPem.Bytes) + assert.NoError(t, err) + + // Sign + msg := "activity pub is great!" + h := sha256.New() + h.Write([]byte(msg)) + d := h.Sum(nil) + sig, err := rsa.SignPKCS1v15(rand.Reader, privParsed, crypto.SHA256, d) + assert.NoError(t, err) + + // Verify + err = rsa.VerifyPKCS1v15(pubParsed.(*rsa.PublicKey), crypto.SHA256, d, sig) + assert.NoError(t, err) +} diff --git a/modules/avatar/avatar_test.go b/modules/avatar/avatar_test.go index f48266c8587e3..b958a9e23670a 100644 --- a/modules/avatar/avatar_test.go +++ b/modules/avatar/avatar_test.go @@ -5,7 +5,7 @@ package avatar import ( - "io/ioutil" + "os" "testing" "code.gitea.io/gitea/modules/setting" @@ -30,7 +30,7 @@ func Test_PrepareWithPNG(t *testing.T) { setting.Avatar.MaxWidth = 4096 setting.Avatar.MaxHeight = 4096 - data, err := ioutil.ReadFile("testdata/avatar.png") + data, err := os.ReadFile("testdata/avatar.png") assert.NoError(t, err) imgPtr, err := Prepare(data) @@ -44,7 +44,7 @@ func Test_PrepareWithJPEG(t *testing.T) { setting.Avatar.MaxWidth = 4096 setting.Avatar.MaxHeight = 4096 - data, err := ioutil.ReadFile("testdata/avatar.jpeg") + data, err := os.ReadFile("testdata/avatar.jpeg") assert.NoError(t, err) imgPtr, err := Prepare(data) @@ -65,7 +65,7 @@ func Test_PrepareWithInvalidImageSize(t *testing.T) { setting.Avatar.MaxWidth = 5 setting.Avatar.MaxHeight = 5 - data, err := ioutil.ReadFile("testdata/avatar.png") + data, err := os.ReadFile("testdata/avatar.png") assert.NoError(t, err) _, err = Prepare(data) diff --git a/modules/charset/charset.go b/modules/charset/charset.go index 3000864c2ea0e..47906e2638320 100644 --- a/modules/charset/charset.go +++ b/modules/charset/charset.go @@ -8,7 +8,6 @@ import ( "bytes" "fmt" "io" - "io/ioutil" "strings" "unicode/utf8" @@ -78,7 +77,7 @@ func ToUTF8WithErr(content []byte) (string, error) { // ToUTF8WithFallback detects the encoding of content and coverts to UTF-8 if possible func ToUTF8WithFallback(content []byte) []byte { - bs, _ := ioutil.ReadAll(ToUTF8WithFallbackReader(bytes.NewReader(content))) + bs, _ := io.ReadAll(ToUTF8WithFallbackReader(bytes.NewReader(content))) return bs } diff --git a/modules/context/api.go b/modules/context/api.go index 47ea8acfe05f3..e5216d911f8a6 100644 --- a/modules/context/api.go +++ b/modules/context/api.go @@ -14,6 +14,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -214,10 +215,14 @@ func (ctx *APIContext) RequireCSRF() { // CheckForOTP validates OTP func (ctx *APIContext) CheckForOTP() { + if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) { + return // Skip 2FA + } + otpHeader := ctx.Req.Header.Get("X-Gitea-OTP") - twofa, err := models.GetTwoFactorByUID(ctx.Context.User.ID) + twofa, err := login.GetTwoFactorByUID(ctx.Context.User.ID) if err != nil { - if models.IsErrTwoFactorNotEnrolled(err) { + if login.IsErrTwoFactorNotEnrolled(err) { return // No 2FA enrollment for this user } ctx.Context.Error(http.StatusInternalServerError) diff --git a/modules/context/auth.go b/modules/context/auth.go index ed220d542032b..7faa93d78b594 100644 --- a/modules/context/auth.go +++ b/modules/context/auth.go @@ -8,7 +8,7 @@ package context import ( "net/http" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web/middleware" @@ -151,9 +151,12 @@ func ToggleAPI(options *ToggleOptions) func(ctx *APIContext) { return } if ctx.IsSigned && ctx.IsBasicAuth { - twofa, err := models.GetTwoFactorByUID(ctx.User.ID) + if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) { + return // Skip 2FA + } + twofa, err := login.GetTwoFactorByUID(ctx.User.ID) if err != nil { - if models.IsErrTwoFactorNotEnrolled(err) { + if login.IsErrTwoFactorNotEnrolled(err) { return // No 2FA enrollment for this user } ctx.InternalServerError(err) diff --git a/modules/context/context.go b/modules/context/context.go index 3dbc2bb9dcd0f..bed47603224a5 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -547,6 +547,17 @@ func GetContext(req *http.Request) *Context { return req.Context().Value(contextKey).(*Context) } +// GetContextUser returns context user +func GetContextUser(req *http.Request) *models.User { + if apiContext, ok := req.Context().Value(apiContextKey).(*APIContext); ok { + return apiContext.User + } + if ctx, ok := req.Context().Value(contextKey).(*Context); ok { + return ctx.User + } + return nil +} + // SignedUserName returns signed user's name via context func SignedUserName(req *http.Request) string { if middleware.IsInternalPath(req) { diff --git a/modules/context/repo.go b/modules/context/repo.go index df71638350254..eceefd9e5925d 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -8,7 +8,7 @@ package context import ( "context" "fmt" - "io/ioutil" + "io" "net/url" "path" "strings" @@ -547,7 +547,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { return } - tags, err := ctx.Repo.GitRepo.GetTags() + tags, err := ctx.Repo.GitRepo.GetTags(0, 0) if err != nil { ctx.ServerError("GetTags", err) return @@ -915,7 +915,7 @@ func (ctx *Context) IssueTemplatesFromDefaultBranch() []api.IssueTemplate { _ = r.Close() } }() - data, err := ioutil.ReadAll(r) + data, err := io.ReadAll(r) if err != nil { log.Debug("ReadAll: %v", err) continue diff --git a/modules/convert/convert.go b/modules/convert/convert.go index 9a4714b4e054f..3c309a82f861d 100644 --- a/modules/convert/convert.go +++ b/modules/convert/convert.go @@ -12,6 +12,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/structs" @@ -127,6 +128,7 @@ func ToBranchProtection(bp *models.ProtectedBranch) *api.BranchProtection { DismissStaleApprovals: bp.DismissStaleApprovals, RequireSignedCommits: bp.RequireSignedCommits, ProtectedFilePatterns: bp.ProtectedFilePatterns, + UnprotectedFilePatterns: bp.UnprotectedFilePatterns, Created: bp.CreatedUnix.AsTime(), Updated: bp.UpdatedUnix.AsTime(), } @@ -337,8 +339,8 @@ func ToTopicResponse(topic *models.Topic) *api.TopicResponse { } } -// ToOAuth2Application convert from models.OAuth2Application to api.OAuth2Application -func ToOAuth2Application(app *models.OAuth2Application) *api.OAuth2Application { +// ToOAuth2Application convert from login.OAuth2Application to api.OAuth2Application +func ToOAuth2Application(app *login.OAuth2Application) *api.OAuth2Application { return &api.OAuth2Application{ ID: app.ID, Name: app.Name, diff --git a/modules/convert/git_commit_test.go b/modules/convert/git_commit_test.go index aa35571706e29..298a006cb19e8 100644 --- a/modules/convert/git_commit_test.go +++ b/modules/convert/git_commit_test.go @@ -9,6 +9,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -17,8 +18,8 @@ import ( ) func TestToCommitMeta(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) - headRepo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + assert.NoError(t, db.PrepareTestDatabase()) + headRepo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) sha1, _ := git.NewIDFromString("0000000000000000000000000000000000000000") signature := &git.Signature{Name: "Test Signature", Email: "test@email.com", When: time.Unix(0, 0)} tag := &git.Tag{ diff --git a/modules/convert/issue.go b/modules/convert/issue.go index da09aeaca41ef..3974d460e0e5d 100644 --- a/modules/convert/issue.go +++ b/modules/convert/issue.go @@ -5,9 +5,12 @@ package convert import ( + "fmt" "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" ) @@ -25,6 +28,9 @@ func ToAPIIssue(issue *models.Issue) *api.Issue { if err := issue.LoadRepo(); err != nil { return &api.Issue{} } + if err := issue.Repo.GetOwner(); err != nil { + return &api.Issue{} + } apiIssue := &api.Issue{ ID: issue.ID, @@ -35,7 +41,7 @@ func ToAPIIssue(issue *models.Issue) *api.Issue { Title: issue.Title, Body: issue.Content, Ref: issue.Ref, - Labels: ToLabelList(issue.Labels), + Labels: ToLabelList(issue.Labels, issue.Repo, issue.Repo.Owner), State: issue.State(), IsLocked: issue.IsLocked, Comments: issue.NumComments, @@ -168,20 +174,37 @@ func ToTrackedTimeList(tl models.TrackedTimeList) api.TrackedTimeList { } // ToLabel converts Label to API format -func ToLabel(label *models.Label) *api.Label { - return &api.Label{ +func ToLabel(label *models.Label, repo *models.Repository, org *models.User) *api.Label { + result := &api.Label{ ID: label.ID, Name: label.Name, Color: strings.TrimLeft(label.Color, "#"), Description: label.Description, } + + // calculate URL + if label.BelongsToRepo() && repo != nil { + if repo != nil { + result.URL = fmt.Sprintf("%s/labels/%d", repo.APIURL(), label.ID) + } else { + log.Error("ToLabel did not get repo to calculate url for label with id '%d'", label.ID) + } + } else { // BelongsToOrg + if org != nil { + result.URL = fmt.Sprintf("%sapi/v1/orgs/%s/labels/%d", setting.AppURL, org.Name, label.ID) + } else { + log.Error("ToLabel did not get org to calculate url for label with id '%d'", label.ID) + } + } + + return result } // ToLabelList converts list of Label to API format -func ToLabelList(labels []*models.Label) []*api.Label { +func ToLabelList(labels []*models.Label, repo *models.Repository, org *models.User) []*api.Label { result := make([]*api.Label, len(labels)) for i := range labels { - result[i] = ToLabel(labels[i]) + result[i] = ToLabel(labels[i], repo, org) } return result } diff --git a/modules/convert/issue_test.go b/modules/convert/issue_test.go index 2f8f56e99a643..7c6b05e816a5a 100644 --- a/modules/convert/issue_test.go +++ b/modules/convert/issue_test.go @@ -5,10 +5,13 @@ package convert import ( + "fmt" "testing" "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" @@ -16,13 +19,15 @@ import ( ) func TestLabel_ToLabel(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) - label := models.AssertExistsAndLoadBean(t, &models.Label{ID: 1}).(*models.Label) + assert.NoError(t, db.PrepareTestDatabase()) + label := db.AssertExistsAndLoadBean(t, &models.Label{ID: 1}).(*models.Label) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: label.RepoID}).(*models.Repository) assert.Equal(t, &api.Label{ ID: label.ID, Name: label.Name, Color: "abcdef", - }, ToLabel(label)) + URL: fmt.Sprintf("%sapi/v1/repos/user2/repo1/labels/%d", setting.AppURL, label.ID), + }, ToLabel(label, repo, nil)) } func TestMilestone_APIFormat(t *testing.T) { diff --git a/modules/convert/main_test.go b/modules/convert/main_test.go index 8720ff042760f..acb9802f97a07 100644 --- a/modules/convert/main_test.go +++ b/modules/convert/main_test.go @@ -8,9 +8,9 @@ import ( "path/filepath" "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..")) + db.MainTest(m, filepath.Join("..", "..")) } diff --git a/modules/convert/notification.go b/modules/convert/notification.go index b0888ee09f0cf..fae7be1257d2d 100644 --- a/modules/convert/notification.go +++ b/modules/convert/notification.go @@ -31,10 +31,12 @@ func ToNotificationThread(n *models.Notification) *api.NotificationThread { if n.Issue != nil { result.Subject.Title = n.Issue.Title result.Subject.URL = n.Issue.APIURL() + result.Subject.HTMLURL = n.Issue.HTMLURL() result.Subject.State = n.Issue.State() comment, err := n.Issue.GetLastComment() if err == nil && comment != nil { result.Subject.LatestCommentURL = comment.APIURL() + result.Subject.LatestCommentHTMLURL = comment.HTMLURL() } } case models.NotificationSourcePullRequest: @@ -42,10 +44,12 @@ func ToNotificationThread(n *models.Notification) *api.NotificationThread { if n.Issue != nil { result.Subject.Title = n.Issue.Title result.Subject.URL = n.Issue.APIURL() + result.Subject.HTMLURL = n.Issue.HTMLURL() result.Subject.State = n.Issue.State() comment, err := n.Issue.GetLastComment() if err == nil && comment != nil { result.Subject.LatestCommentURL = comment.APIURL() + result.Subject.LatestCommentHTMLURL = comment.HTMLURL() } pr, _ := n.Issue.GetPullRequest() @@ -54,16 +58,20 @@ func ToNotificationThread(n *models.Notification) *api.NotificationThread { } } case models.NotificationSourceCommit: + url := n.Repository.HTMLURL() + "/commit/" + n.CommitID result.Subject = &api.NotificationSubject{ - Type: api.NotifySubjectCommit, - Title: n.CommitID, - URL: n.Repository.HTMLURL() + "/commit/" + n.CommitID, + Type: api.NotifySubjectCommit, + Title: n.CommitID, + URL: url, + HTMLURL: url, } case models.NotificationSourceRepository: result.Subject = &api.NotificationSubject{ Type: api.NotifySubjectRepository, Title: n.Repository.FullName(), - URL: n.Repository.Link(), + // FIXME: this is a relative URL, rather useless and inconsistent, but keeping for backwards compat + URL: n.Repository.Link(), + HTMLURL: n.Repository.HTMLURL(), } } diff --git a/modules/convert/pull.go b/modules/convert/pull.go index 6c5d15c82ec44..ab17c13421357 100644 --- a/modules/convert/pull.go +++ b/modules/convert/pull.go @@ -17,7 +17,7 @@ import ( // ToAPIPullRequest assumes following fields have been assigned with valid values: // Required - Issue // Optional - Merger -func ToAPIPullRequest(pr *models.PullRequest) *api.PullRequest { +func ToAPIPullRequest(pr *models.PullRequest, doer *models.User) *api.PullRequest { var ( baseBranch *git.Branch headBranch *git.Branch @@ -41,6 +41,12 @@ func ToAPIPullRequest(pr *models.PullRequest) *api.PullRequest { return nil } + perm, err := models.GetUserRepoPermission(pr.BaseRepo, doer) + if err != nil { + log.Error("GetUserRepoPermission[%d]: %v", pr.BaseRepoID, err) + perm.AccessMode = models.AccessModeNone + } + apiPullRequest := &api.PullRequest{ ID: pr.ID, URL: pr.Issue.HTMLURL(), @@ -68,7 +74,7 @@ func ToAPIPullRequest(pr *models.PullRequest) *api.PullRequest { Name: pr.BaseBranch, Ref: pr.BaseBranch, RepoID: pr.BaseRepoID, - Repository: ToRepo(pr.BaseRepo, models.AccessModeNone), + Repository: ToRepo(pr.BaseRepo, perm.AccessMode), }, Head: &api.PRBranchInfo{ Name: pr.HeadBranch, @@ -114,8 +120,14 @@ func ToAPIPullRequest(pr *models.PullRequest) *api.PullRequest { } if pr.HeadRepo != nil && pr.Flow == models.PullRequestFlowGithub { + perm, err := models.GetUserRepoPermission(pr.HeadRepo, doer) + if err != nil { + log.Error("GetUserRepoPermission[%d]: %v", pr.HeadRepoID, err) + perm.AccessMode = models.AccessModeNone + } + apiPullRequest.Head.RepoID = pr.HeadRepo.ID - apiPullRequest.Head.Repository = ToRepo(pr.HeadRepo, models.AccessModeNone) + apiPullRequest.Head.Repository = ToRepo(pr.HeadRepo, perm.AccessMode) headGitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath()) if err != nil { diff --git a/modules/convert/pull_test.go b/modules/convert/pull_test.go index adf42d8ca4421..a2b1e12a37187 100644 --- a/modules/convert/pull_test.go +++ b/modules/convert/pull_test.go @@ -8,6 +8,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -15,29 +16,29 @@ import ( func TestPullRequest_APIFormat(t *testing.T) { //with HeadRepo - assert.NoError(t, models.PrepareTestDatabase()) - headRepo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - pr := models.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 1}).(*models.PullRequest) + assert.NoError(t, db.PrepareTestDatabase()) + headRepo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + pr := db.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 1}).(*models.PullRequest) assert.NoError(t, pr.LoadAttributes()) assert.NoError(t, pr.LoadIssue()) - apiPullRequest := ToAPIPullRequest(pr) + apiPullRequest := ToAPIPullRequest(pr, nil) assert.NotNil(t, apiPullRequest) assert.EqualValues(t, &structs.PRBranchInfo{ Name: "branch1", Ref: "refs/pull/2/head", Sha: "4a357436d925b5c974181ff12a994538ddc5a269", RepoID: 1, - Repository: ToRepo(headRepo, models.AccessModeNone), + Repository: ToRepo(headRepo, models.AccessModeRead), }, apiPullRequest.Head) //withOut HeadRepo - pr = models.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 1}).(*models.PullRequest) + pr = db.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 1}).(*models.PullRequest) assert.NoError(t, pr.LoadIssue()) assert.NoError(t, pr.LoadAttributes()) // simulate fork deletion pr.HeadRepo = nil pr.HeadRepoID = 100000 - apiPullRequest = ToAPIPullRequest(pr) + apiPullRequest = ToAPIPullRequest(pr, nil) assert.NotNil(t, apiPullRequest) assert.Nil(t, apiPullRequest.Head.Repository) assert.EqualValues(t, -1, apiPullRequest.Head.RepoID) diff --git a/modules/convert/user_test.go b/modules/convert/user_test.go index 679c4f98948ae..404adc2029102 100644 --- a/modules/convert/user_test.go +++ b/modules/convert/user_test.go @@ -8,20 +8,21 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" ) func TestUser_ToUser(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user1 := models.AssertExistsAndLoadBean(t, &models.User{ID: 1, IsAdmin: true}).(*models.User) + user1 := db.AssertExistsAndLoadBean(t, &models.User{ID: 1, IsAdmin: true}).(*models.User) apiUser := toUser(user1, true, true) assert.True(t, apiUser.IsAdmin) - user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2, IsAdmin: false}).(*models.User) + user2 := db.AssertExistsAndLoadBean(t, &models.User{ID: 2, IsAdmin: false}).(*models.User) apiUser = toUser(user2, true, true) assert.False(t, apiUser.IsAdmin) @@ -30,7 +31,7 @@ func TestUser_ToUser(t *testing.T) { assert.False(t, apiUser.IsAdmin) assert.EqualValues(t, api.VisibleTypePublic.String(), apiUser.Visibility) - user31 := models.AssertExistsAndLoadBean(t, &models.User{ID: 31, IsAdmin: false, Visibility: api.VisibleTypePrivate}).(*models.User) + user31 := db.AssertExistsAndLoadBean(t, &models.User{ID: 31, IsAdmin: false, Visibility: api.VisibleTypePrivate}).(*models.User) apiUser = toUser(user31, true, true) assert.False(t, apiUser.IsAdmin) diff --git a/modules/doctor/dbconsistency.go b/modules/doctor/dbconsistency.go index 23e8331e774a3..987bd58db8753 100644 --- a/modules/doctor/dbconsistency.go +++ b/modules/doctor/dbconsistency.go @@ -8,258 +8,180 @@ import ( "context" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/migrations" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" ) -func checkDBConsistency(logger log.Logger, autofix bool) error { - // make sure DB version is uptodate - if err := models.NewEngine(context.Background(), migrations.EnsureUpToDate); err != nil { - logger.Critical("Model version on the database does not match the current Gitea version. Model consistency will not be checked until the database is upgraded") - return err - } - - // find labels without existing repo or org - count, err := models.CountOrphanedLabels() - if err != nil { - logger.Critical("Error: %v whilst counting orphaned labels", err) - return err - } - if count > 0 { - if autofix { - if err = models.DeleteOrphanedLabels(); err != nil { - logger.Critical("Error: %v whilst deleting orphaned labels", err) - return err - } - logger.Info("%d labels without existing repository/organisation deleted", count) - } else { - logger.Warn("%d labels without existing repository/organisation", count) - } - } +type consistencyCheck struct { + Name string + Counter func() (int64, error) + Fixer func() (int64, error) + FixedMessage string +} - // find IssueLabels without existing label - count, err = models.CountOrphanedIssueLabels() +func (c *consistencyCheck) Run(logger log.Logger, autofix bool) error { + count, err := c.Counter() if err != nil { - logger.Critical("Error: %v whilst counting orphaned issue_labels", err) + logger.Critical("Error: %v whilst counting %s", err, c.Name) return err } if count > 0 { if autofix { - if err = models.DeleteOrphanedIssueLabels(); err != nil { - logger.Critical("Error: %v whilst deleting orphaned issue_labels", err) + var fixed int64 + if fixed, err = c.Fixer(); err != nil { + logger.Critical("Error: %v whilst fixing %s", err, c.Name) return err } - logger.Info("%d issue_labels without existing label deleted", count) - } else { - logger.Warn("%d issue_labels without existing label", count) - } - } - // find issues without existing repository - count, err = models.CountOrphanedIssues() - if err != nil { - logger.Critical("Error: %v whilst counting orphaned issues", err) - return err - } - if count > 0 { - if autofix { - if err = models.DeleteOrphanedIssues(); err != nil { - logger.Critical("Error: %v whilst deleting orphaned issues", err) - return err + prompt := "Deleted" + if c.FixedMessage != "" { + prompt = c.FixedMessage } - logger.Info("%d issues without existing repository deleted", count) - } else { - logger.Warn("%d issues without existing repository", count) - } - } - // find pulls without existing issues - count, err = models.CountOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id") - if err != nil { - logger.Critical("Error: %v whilst counting orphaned objects", err) - return err - } - if count > 0 { - if autofix { - if err = models.DeleteOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id"); err != nil { - logger.Critical("Error: %v whilst deleting orphaned objects", err) - return err - } - logger.Info("%d pull requests without existing issue deleted", count) - } else { - logger.Warn("%d pull requests without existing issue", count) - } - } - - // find tracked times without existing issues/pulls - count, err = models.CountOrphanedObjects("tracked_time", "issue", "tracked_time.issue_id=issue.id") - if err != nil { - logger.Critical("Error: %v whilst counting orphaned objects", err) - return err - } - if count > 0 { - if autofix { - if err = models.DeleteOrphanedObjects("tracked_time", "issue", "tracked_time.issue_id=issue.id"); err != nil { - logger.Critical("Error: %v whilst deleting orphaned objects", err) - return err + if fixed < 0 { + logger.Info(prompt+" %d %s", count, c.Name) + } else { + logger.Info(prompt+" %d/%d %s", fixed, count, c.Name) } - logger.Info("%d tracked times without existing issue deleted", count) } else { - logger.Warn("%d tracked times without existing issue", count) + logger.Warn("Found %d %s", count, c.Name) } } + return nil +} - // find null archived repositories - count, err = models.CountNullArchivedRepository() - if err != nil { - logger.Critical("Error: %v whilst counting null archived repositories", err) - return err - } - if count > 0 { - if autofix { - updatedCount, err := models.FixNullArchivedRepository() - if err != nil { - logger.Critical("Error: %v whilst fixing null archived repositories", err) - return err - } - logger.Info("%d repositories with null is_archived updated", updatedCount) - } else { - logger.Warn("%d repositories with null is_archived", count) - } +func asFixer(fn func() error) func() (int64, error) { + return func() (int64, error) { + err := fn() + return -1, err } +} - // find label comments with empty labels - count, err = models.CountCommentTypeLabelWithEmptyLabel() - if err != nil { - logger.Critical("Error: %v whilst counting label comments with empty labels", err) - return err - } - if count > 0 { - if autofix { - updatedCount, err := models.FixCommentTypeLabelWithEmptyLabel() - if err != nil { - logger.Critical("Error: %v whilst removing label comments with empty labels", err) - return err - } - logger.Info("%d label comments with empty labels removed", updatedCount) - } else { - logger.Warn("%d label comments with empty labels", count) - } +func genericOrphanCheck(name, subject, refobject, joincond string) consistencyCheck { + return consistencyCheck{ + Name: name, + Counter: func() (int64, error) { + return models.CountOrphanedObjects(subject, refobject, joincond) + }, + Fixer: func() (int64, error) { + err := models.DeleteOrphanedObjects(subject, refobject, joincond) + return -1, err + }, } +} - // find label comments with labels from outside the repository - count, err = models.CountCommentTypeLabelWithOutsideLabels() - if err != nil { - logger.Critical("Error: %v whilst counting label comments with outside labels", err) +func checkDBConsistency(logger log.Logger, autofix bool) error { + // make sure DB version is uptodate + if err := db.NewEngine(context.Background(), migrations.EnsureUpToDate); err != nil { + logger.Critical("Model version on the database does not match the current Gitea version. Model consistency will not be checked until the database is upgraded") return err } - if count > 0 { - if autofix { - updatedCount, err := models.FixCommentTypeLabelWithOutsideLabels() - if err != nil { - logger.Critical("Error: %v whilst removing label comments with outside labels", err) - return err - } - log.Info("%d label comments with outside labels removed", updatedCount) - } else { - log.Warn("%d label comments with outside labels", count) - } - } - // find issue_label with labels from outside the repository - count, err = models.CountIssueLabelWithOutsideLabels() - if err != nil { - logger.Critical("Error: %v whilst counting issue_labels from outside the repository or organisation", err) - return err - } - if count > 0 { - if autofix { - updatedCount, err := models.FixIssueLabelWithOutsideLabels() - if err != nil { - logger.Critical("Error: %v whilst removing issue_labels from outside the repository or organisation", err) - return err - } - logger.Info("%d issue_labels from outside the repository or organisation removed", updatedCount) - } else { - logger.Warn("%d issue_labels from outside the repository or organisation", count) - } + consistencyChecks := []consistencyCheck{ + { + // find labels without existing repo or org + Name: "Orphaned Labels without existing repository or organisation", + Counter: models.CountOrphanedLabels, + Fixer: asFixer(models.DeleteOrphanedLabels), + }, + { + // find IssueLabels without existing label + Name: "Orphaned Issue Labels without existing label", + Counter: models.CountOrphanedIssueLabels, + Fixer: asFixer(models.DeleteOrphanedIssueLabels), + }, + { + // find issues without existing repository + Name: "Orphaned Issues without existing repository", + Counter: models.CountOrphanedIssues, + Fixer: asFixer(models.DeleteOrphanedIssues), + }, + // find releases without existing repository + genericOrphanCheck("Orphaned Releases without existing repository", + "release", "repository", "release.repo_id=repository.id"), + // find pulls without existing issues + genericOrphanCheck("Orphaned PullRequests without existing issue", + "pull_request", "issue", "pull_request.issue_id=issue.id"), + // find tracked times without existing issues/pulls + genericOrphanCheck("Orphaned TrackedTimes without existing issue", + "tracked_time", "issue", "tracked_time.issue_id=issue.id"), + // find attachments without existing issues or releases + { + Name: "Orphaned Attachments without existing issues or releases", + Counter: models.CountOrphanedAttachments, + Fixer: asFixer(models.DeleteOrphanedAttachments), + }, + // find null archived repositories + { + Name: "Repositories with is_archived IS NULL", + Counter: models.CountNullArchivedRepository, + Fixer: models.FixNullArchivedRepository, + FixedMessage: "Fixed", + }, + // find label comments with empty labels + { + Name: "Label comments with empty labels", + Counter: models.CountCommentTypeLabelWithEmptyLabel, + Fixer: models.FixCommentTypeLabelWithEmptyLabel, + FixedMessage: "Fixed", + }, + // find label comments with labels from outside the repository + { + Name: "Label comments with labels from outside the repository", + Counter: models.CountCommentTypeLabelWithOutsideLabels, + Fixer: models.FixCommentTypeLabelWithOutsideLabels, + FixedMessage: "Removed", + }, + // find issue_label with labels from outside the repository + { + Name: "IssueLabels with Labels from outside the repository", + Counter: models.CountIssueLabelWithOutsideLabels, + Fixer: models.FixIssueLabelWithOutsideLabels, + FixedMessage: "Removed", + }, } // TODO: function to recalc all counters if setting.Database.UsePostgreSQL { - count, err = models.CountBadSequences() - if err != nil { - logger.Critical("Error: %v whilst checking sequence values", err) + consistencyChecks = append(consistencyChecks, consistencyCheck{ + Name: "Sequence values", + Counter: db.CountBadSequences, + Fixer: asFixer(db.FixBadSequences), + FixedMessage: "Updated", + }) + } + + consistencyChecks = append(consistencyChecks, + // find protected branches without existing repository + genericOrphanCheck("Protected Branches without existing repository", + "protected_branch", "repository", "protected_branch.repo_id=repository.id"), + // find deleted branches without existing repository + genericOrphanCheck("Deleted Branches without existing repository", + "deleted_branch", "repository", "deleted_branch.repo_id=repository.id"), + // find LFS locks without existing repository + genericOrphanCheck("LFS locks without existing repository", + "lfs_lock", "repository", "lfs_lock.repo_id=repository.id"), + // find collaborations without users + genericOrphanCheck("Collaborations without existing user", + "collaboration", "user", "collaboration.user_id=user.id"), + // find collaborations without repository + genericOrphanCheck("Collaborations without existing repository", + "collaboration", "repository", "collaboration.repo_id=repository.id"), + // find access without users + genericOrphanCheck("Access entries without existing user", + "access", "user", "access.user_id=user.id"), + // find access without repository + genericOrphanCheck("Access entries without existing repository", + "access", "repository", "access.repo_id=repository.id"), + ) + + for _, c := range consistencyChecks { + if err := c.Run(logger, autofix); err != nil { return err } - if count > 0 { - if autofix { - err := models.FixBadSequences() - if err != nil { - logger.Critical("Error: %v whilst attempting to fix sequences", err) - return err - } - logger.Info("%d sequences updated", count) - } else { - logger.Warn("%d sequences with incorrect values", count) - } - } - } - - // find protected branches without existing repository - count, err = models.CountOrphanedObjects("protected_branch", "repository", "protected_branch.repo_id=repository.id") - if err != nil { - logger.Critical("Error: %v whilst counting orphaned objects", err) - return err - } - if count > 0 { - if autofix { - if err = models.DeleteOrphanedObjects("protected_branch", "repository", "protected_branch.repo_id=repository.id"); err != nil { - logger.Critical("Error: %v whilst deleting orphaned objects", err) - return err - } - logger.Info("%d protected branches without existing repository deleted", count) - } else { - logger.Warn("%d protected branches without existing repository", count) - } - } - - // find deleted branches without existing repository - count, err = models.CountOrphanedObjects("deleted_branch", "repository", "deleted_branch.repo_id=repository.id") - if err != nil { - logger.Critical("Error: %v whilst counting orphaned objects", err) - return err - } - if count > 0 { - if autofix { - if err = models.DeleteOrphanedObjects("deleted_branch", "repository", "deleted_branch.repo_id=repository.id"); err != nil { - logger.Critical("Error: %v whilst deleting orphaned objects", err) - return err - } - logger.Info("%d deleted branches without existing repository deleted", count) - } else { - logger.Warn("%d deleted branches without existing repository", count) - } - } - - // find LFS locks without existing repository - count, err = models.CountOrphanedObjects("lfs_lock", "repository", "lfs_lock.repo_id=repository.id") - if err != nil { - logger.Critical("Error: %v whilst counting orphaned objects", err) - return err - } - if count > 0 { - if autofix { - if err = models.DeleteOrphanedObjects("lfs_lock", "repository", "lfs_lock.repo_id=repository.id"); err != nil { - logger.Critical("Error: %v whilst deleting orphaned objects", err) - return err - } - logger.Info("%d LFS locks without existing repository deleted", count) - } else { - logger.Warn("%d LFS locks without existing repository", count) - } } return nil diff --git a/modules/doctor/dbversion.go b/modules/doctor/dbversion.go index c5cac3bf91fc8..acc564bdb0664 100644 --- a/modules/doctor/dbversion.go +++ b/modules/doctor/dbversion.go @@ -7,13 +7,13 @@ package doctor import ( "context" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/migrations" "code.gitea.io/gitea/modules/log" ) func checkDBVersion(logger log.Logger, autofix bool) error { - if err := models.NewEngine(context.Background(), migrations.EnsureUpToDate); err != nil { + if err := db.NewEngine(context.Background(), migrations.EnsureUpToDate); err != nil { if !autofix { logger.Critical("Error: %v during ensure up to date", err) return err @@ -21,7 +21,7 @@ func checkDBVersion(logger log.Logger, autofix bool) error { logger.Warn("Got Error: %v during ensure up to date", err) logger.Warn("Attempting to migrate to the latest DB version to fix this.") - err = models.NewEngine(context.Background(), migrations.Migrate) + err = db.NewEngine(context.Background(), migrations.Migrate) if err != nil { logger.Critical("Error: %v during migration", err) } diff --git a/modules/doctor/doctor.go b/modules/doctor/doctor.go index 8c0d467304259..d5cc4a1850f54 100644 --- a/modules/doctor/doctor.go +++ b/modules/doctor/doctor.go @@ -9,7 +9,7 @@ import ( "sort" "strings" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" ) @@ -47,7 +47,7 @@ func initDBDisableConsole(disableConsole bool) error { setting.InitDBConfig() setting.NewXORMLogService(disableConsole) - if err := models.SetEngine(); err != nil { + if err := db.SetEngine(); err != nil { return fmt.Errorf("models.SetEngine: %v", err) } return nil diff --git a/modules/doctor/fix16961.go b/modules/doctor/fix16961.go new file mode 100644 index 0000000000000..60cc5ffe2fbaa --- /dev/null +++ b/modules/doctor/fix16961.go @@ -0,0 +1,318 @@ +// Copyright 2021 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 doctor + +import ( + "bytes" + "fmt" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/timeutil" + "xorm.io/builder" +) + +// #16831 revealed that the dump command that was broken in 1.14.3-1.14.6 and 1.15.0 (#15885). +// This led to repo_unit and login_source cfg not being converted to JSON in the dump +// Unfortunately although it was hoped that there were only a few users affected it +// appears that many users are affected. + +// We therefore need to provide a doctor command to fix this repeated issue #16961 + +func parseBool16961(bs []byte) (bool, error) { + if bytes.EqualFold(bs, []byte("%!s(bool=false)")) { + return false, nil + } + + if bytes.EqualFold(bs, []byte("%!s(bool=true)")) { + return true, nil + } + + return false, fmt.Errorf("unexpected bool format: %s", string(bs)) +} + +func fixUnitConfig16961(bs []byte, cfg *models.UnitConfig) (fixed bool, err error) { + err = models.JSONUnmarshalHandleDoubleEncode(bs, &cfg) + if err == nil { + return + } + + // Handle #16961 + if string(bs) != "&{}" && len(bs) != 0 { + return + } + + return true, nil +} + +func fixExternalWikiConfig16961(bs []byte, cfg *models.ExternalWikiConfig) (fixed bool, err error) { + err = models.JSONUnmarshalHandleDoubleEncode(bs, &cfg) + if err == nil { + return + } + + if len(bs) < 3 { + return + } + if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' { + return + } + cfg.ExternalWikiURL = string(bs[2 : len(bs)-1]) + return true, nil +} + +func fixExternalTrackerConfig16961(bs []byte, cfg *models.ExternalTrackerConfig) (fixed bool, err error) { + err = models.JSONUnmarshalHandleDoubleEncode(bs, &cfg) + if err == nil { + return + } + // Handle #16961 + if len(bs) < 3 { + return + } + + if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' { + return + } + + parts := bytes.Split(bs[2:len(bs)-1], []byte{' '}) + if len(parts) != 3 { + return + } + + cfg.ExternalTrackerURL = string(bytes.Join(parts[:len(parts)-2], []byte{' '})) + cfg.ExternalTrackerFormat = string(parts[len(parts)-2]) + cfg.ExternalTrackerStyle = string(parts[len(parts)-1]) + return true, nil +} + +func fixPullRequestsConfig16961(bs []byte, cfg *models.PullRequestsConfig) (fixed bool, err error) { + err = models.JSONUnmarshalHandleDoubleEncode(bs, &cfg) + if err == nil { + return + } + + // Handle #16961 + if len(bs) < 3 { + return + } + + if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' { + return + } + + // PullRequestsConfig was the following in 1.14 + // type PullRequestsConfig struct { + // IgnoreWhitespaceConflicts bool + // AllowMerge bool + // AllowRebase bool + // AllowRebaseMerge bool + // AllowSquash bool + // AllowManualMerge bool + // AutodetectManualMerge bool + // } + // + // 1.15 added in addition: + // DefaultDeleteBranchAfterMerge bool + // DefaultMergeStyle MergeStyle + parts := bytes.Split(bs[2:len(bs)-1], []byte{' '}) + if len(parts) < 7 { + return + } + + var parseErr error + cfg.IgnoreWhitespaceConflicts, parseErr = parseBool16961(parts[0]) + if parseErr != nil { + return + } + cfg.AllowMerge, parseErr = parseBool16961(parts[1]) + if parseErr != nil { + return + } + cfg.AllowRebase, parseErr = parseBool16961(parts[2]) + if parseErr != nil { + return + } + cfg.AllowRebaseMerge, parseErr = parseBool16961(parts[3]) + if parseErr != nil { + return + } + cfg.AllowSquash, parseErr = parseBool16961(parts[4]) + if parseErr != nil { + return + } + cfg.AllowManualMerge, parseErr = parseBool16961(parts[5]) + if parseErr != nil { + return + } + cfg.AutodetectManualMerge, parseErr = parseBool16961(parts[6]) + if parseErr != nil { + return + } + + // 1.14 unit + if len(parts) == 7 { + return true, nil + } + + if len(parts) < 9 { + return + } + + cfg.DefaultDeleteBranchAfterMerge, parseErr = parseBool16961(parts[7]) + if parseErr != nil { + return + } + + cfg.DefaultMergeStyle = models.MergeStyle(string(bytes.Join(parts[8:], []byte{' '}))) + return true, nil +} + +func fixIssuesConfig16961(bs []byte, cfg *models.IssuesConfig) (fixed bool, err error) { + err = models.JSONUnmarshalHandleDoubleEncode(bs, &cfg) + if err == nil { + return + } + + // Handle #16961 + if len(bs) < 3 { + return + } + + if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' { + return + } + + parts := bytes.Split(bs[2:len(bs)-1], []byte{' '}) + if len(parts) != 3 { + return + } + var parseErr error + cfg.EnableTimetracker, parseErr = parseBool16961(parts[0]) + if parseErr != nil { + return + } + cfg.AllowOnlyContributorsToTrackTime, parseErr = parseBool16961(parts[1]) + if parseErr != nil { + return + } + cfg.EnableDependencies, parseErr = parseBool16961(parts[2]) + if parseErr != nil { + return + } + return true, nil +} + +func fixBrokenRepoUnit16961(repoUnit *models.RepoUnit, bs []byte) (fixed bool, err error) { + // Shortcut empty or null values + if len(bs) == 0 { + return false, nil + } + + switch models.UnitType(repoUnit.Type) { + case models.UnitTypeCode, models.UnitTypeReleases, models.UnitTypeWiki, models.UnitTypeProjects: + cfg := &models.UnitConfig{} + repoUnit.Config = cfg + if fixed, err := fixUnitConfig16961(bs, cfg); !fixed { + return false, err + } + case models.UnitTypeExternalWiki: + cfg := &models.ExternalWikiConfig{} + repoUnit.Config = cfg + + if fixed, err := fixExternalWikiConfig16961(bs, cfg); !fixed { + return false, err + } + case models.UnitTypeExternalTracker: + cfg := &models.ExternalTrackerConfig{} + repoUnit.Config = cfg + if fixed, err := fixExternalTrackerConfig16961(bs, cfg); !fixed { + return false, err + } + case models.UnitTypePullRequests: + cfg := &models.PullRequestsConfig{} + repoUnit.Config = cfg + + if fixed, err := fixPullRequestsConfig16961(bs, cfg); !fixed { + return false, err + } + case models.UnitTypeIssues: + cfg := &models.IssuesConfig{} + repoUnit.Config = cfg + if fixed, err := fixIssuesConfig16961(bs, cfg); !fixed { + return false, err + } + default: + panic(fmt.Sprintf("unrecognized repo unit type: %v", repoUnit.Type)) + } + return true, nil +} + +func fixBrokenRepoUnits16961(logger log.Logger, autofix bool) error { + // RepoUnit describes all units of a repository + type RepoUnit struct { + ID int64 + RepoID int64 + Type models.UnitType + Config []byte + CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"` + } + + count := 0 + + err := db.Iterate( + db.DefaultContext, + new(RepoUnit), + builder.Gt{ + "id": 0, + }, + func(idx int, bean interface{}) error { + unit := bean.(*RepoUnit) + + bs := unit.Config + repoUnit := &models.RepoUnit{ + ID: unit.ID, + RepoID: unit.RepoID, + Type: unit.Type, + CreatedUnix: unit.CreatedUnix, + } + + if fixed, err := fixBrokenRepoUnit16961(repoUnit, bs); !fixed { + return err + } + + count++ + if !autofix { + return nil + } + + return models.UpdateRepoUnit(repoUnit) + }, + ) + + if err != nil { + logger.Critical("Unable to iterate acrosss repounits to fix the broken units: Error %v", err) + return err + } + + if !autofix { + logger.Warn("Found %d broken repo_units", count) + return nil + } + logger.Info("Fixed %d broken repo_units", count) + + return nil +} + +func init() { + Register(&Check{ + Title: "Check for incorrectly dumped repo_units (See #16961)", + Name: "fix-broken-repo-units", + IsDefault: false, + Run: fixBrokenRepoUnits16961, + Priority: 7, + }) +} diff --git a/modules/doctor/fix16961_test.go b/modules/doctor/fix16961_test.go new file mode 100644 index 0000000000000..017f585335c5f --- /dev/null +++ b/modules/doctor/fix16961_test.go @@ -0,0 +1,271 @@ +// Copyright 2021 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 doctor + +import ( + "testing" + + "code.gitea.io/gitea/models" + "github.com/stretchr/testify/assert" +) + +func Test_fixUnitConfig_16961(t *testing.T) { + tests := []struct { + name string + bs string + wantFixed bool + wantErr bool + }{ + { + name: "empty", + bs: "", + wantFixed: true, + wantErr: false, + }, + { + name: "normal: {}", + bs: "{}", + wantFixed: false, + wantErr: false, + }, + { + name: "broken but fixable: &{}", + bs: "&{}", + wantFixed: true, + wantErr: false, + }, + { + name: "broken but unfixable: &{asdasd}", + bs: "&{asdasd}", + wantFixed: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotFixed, err := fixUnitConfig16961([]byte(tt.bs), &models.UnitConfig{}) + if (err != nil) != tt.wantErr { + t.Errorf("fixUnitConfig_16961() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotFixed != tt.wantFixed { + t.Errorf("fixUnitConfig_16961() = %v, want %v", gotFixed, tt.wantFixed) + } + }) + } +} + +func Test_fixExternalWikiConfig_16961(t *testing.T) { + tests := []struct { + name string + bs string + expected string + wantFixed bool + wantErr bool + }{ + { + name: "normal: {\"ExternalWikiURL\":\"http://someurl\"}", + bs: "{\"ExternalWikiURL\":\"http://someurl\"}", + expected: "http://someurl", + wantFixed: false, + wantErr: false, + }, + { + name: "broken: &{http://someurl}", + bs: "&{http://someurl}", + expected: "http://someurl", + wantFixed: true, + wantErr: false, + }, + { + name: "broken but unfixable: http://someurl", + bs: "http://someurl", + wantFixed: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := &models.ExternalWikiConfig{} + gotFixed, err := fixExternalWikiConfig16961([]byte(tt.bs), cfg) + if (err != nil) != tt.wantErr { + t.Errorf("fixExternalWikiConfig_16961() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotFixed != tt.wantFixed { + t.Errorf("fixExternalWikiConfig_16961() = %v, want %v", gotFixed, tt.wantFixed) + } + if cfg.ExternalWikiURL != tt.expected { + t.Errorf("fixExternalWikiConfig_16961().ExternalWikiURL = %v, want %v", cfg.ExternalWikiURL, tt.expected) + } + }) + } +} + +func Test_fixExternalTrackerConfig_16961(t *testing.T) { + tests := []struct { + name string + bs string + expected models.ExternalTrackerConfig + wantFixed bool + wantErr bool + }{ + { + name: "normal", + bs: `{"ExternalTrackerURL":"a","ExternalTrackerFormat":"b","ExternalTrackerStyle":"c"}`, + expected: models.ExternalTrackerConfig{ + ExternalTrackerURL: "a", + ExternalTrackerFormat: "b", + ExternalTrackerStyle: "c", + }, + wantFixed: false, + wantErr: false, + }, + { + name: "broken", + bs: "&{a b c}", + expected: models.ExternalTrackerConfig{ + ExternalTrackerURL: "a", + ExternalTrackerFormat: "b", + ExternalTrackerStyle: "c", + }, + wantFixed: true, + wantErr: false, + }, + { + name: "broken - too many fields", + bs: "&{a b c d}", + wantFixed: false, + wantErr: true, + }, + { + name: "broken - wrong format", + bs: "a b c d}", + wantFixed: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := &models.ExternalTrackerConfig{} + gotFixed, err := fixExternalTrackerConfig16961([]byte(tt.bs), cfg) + if (err != nil) != tt.wantErr { + t.Errorf("fixExternalTrackerConfig_16961() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotFixed != tt.wantFixed { + t.Errorf("fixExternalTrackerConfig_16961() = %v, want %v", gotFixed, tt.wantFixed) + } + if cfg.ExternalTrackerFormat != tt.expected.ExternalTrackerFormat { + t.Errorf("fixExternalTrackerConfig_16961().ExternalTrackerFormat = %v, want %v", tt.expected.ExternalTrackerFormat, cfg.ExternalTrackerFormat) + } + if cfg.ExternalTrackerStyle != tt.expected.ExternalTrackerStyle { + t.Errorf("fixExternalTrackerConfig_16961().ExternalTrackerStyle = %v, want %v", tt.expected.ExternalTrackerStyle, cfg.ExternalTrackerStyle) + } + if cfg.ExternalTrackerURL != tt.expected.ExternalTrackerURL { + t.Errorf("fixExternalTrackerConfig_16961().ExternalTrackerURL = %v, want %v", tt.expected.ExternalTrackerURL, cfg.ExternalTrackerURL) + } + }) + } +} + +func Test_fixPullRequestsConfig_16961(t *testing.T) { + tests := []struct { + name string + bs string + expected models.PullRequestsConfig + wantFixed bool + wantErr bool + }{ + { + name: "normal", + bs: `{"IgnoreWhitespaceConflicts":false,"AllowMerge":false,"AllowRebase":false,"AllowRebaseMerge":false,"AllowSquash":false,"AllowManualMerge":false,"AutodetectManualMerge":false,"DefaultDeleteBranchAfterMerge":false,"DefaultMergeStyle":""}`, + }, + { + name: "broken - 1.14", + bs: `&{%!s(bool=false) %!s(bool=true) %!s(bool=true) %!s(bool=true) %!s(bool=true) %!s(bool=false) %!s(bool=false)}`, + expected: models.PullRequestsConfig{ + IgnoreWhitespaceConflicts: false, + AllowMerge: true, + AllowRebase: true, + AllowRebaseMerge: true, + AllowSquash: true, + AllowManualMerge: false, + AutodetectManualMerge: false, + }, + wantFixed: true, + }, + { + name: "broken - 1.15", + bs: `&{%!s(bool=false) %!s(bool=true) %!s(bool=true) %!s(bool=true) %!s(bool=true) %!s(bool=false) %!s(bool=false) %!s(bool=false) merge}`, + expected: models.PullRequestsConfig{ + AllowMerge: true, + AllowRebase: true, + AllowRebaseMerge: true, + AllowSquash: true, + DefaultMergeStyle: models.MergeStyleMerge, + }, + wantFixed: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := &models.PullRequestsConfig{} + gotFixed, err := fixPullRequestsConfig16961([]byte(tt.bs), cfg) + if (err != nil) != tt.wantErr { + t.Errorf("fixPullRequestsConfig_16961() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotFixed != tt.wantFixed { + t.Errorf("fixPullRequestsConfig_16961() = %v, want %v", gotFixed, tt.wantFixed) + } + assert.EqualValues(t, &tt.expected, cfg) + }) + } +} + +func Test_fixIssuesConfig_16961(t *testing.T) { + tests := []struct { + name string + bs string + expected models.IssuesConfig + wantFixed bool + wantErr bool + }{ + { + name: "normal", + bs: `{"EnableTimetracker":true,"AllowOnlyContributorsToTrackTime":true,"EnableDependencies":true}`, + expected: models.IssuesConfig{ + EnableTimetracker: true, + AllowOnlyContributorsToTrackTime: true, + EnableDependencies: true, + }, + }, + { + name: "broken", + bs: `&{%!s(bool=true) %!s(bool=true) %!s(bool=true)}`, + expected: models.IssuesConfig{ + EnableTimetracker: true, + AllowOnlyContributorsToTrackTime: true, + EnableDependencies: true, + }, + wantFixed: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := &models.IssuesConfig{} + gotFixed, err := fixIssuesConfig16961([]byte(tt.bs), cfg) + if (err != nil) != tt.wantErr { + t.Errorf("fixIssuesConfig_16961() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotFixed != tt.wantFixed { + t.Errorf("fixIssuesConfig_16961() = %v, want %v", gotFixed, tt.wantFixed) + } + assert.EqualValues(t, &tt.expected, cfg) + }) + } +} diff --git a/modules/doctor/mergebase.go b/modules/doctor/mergebase.go index 7e9a065b77d9b..c959da8d7f5fd 100644 --- a/modules/doctor/mergebase.go +++ b/modules/doctor/mergebase.go @@ -9,14 +9,16 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" + "xorm.io/builder" ) func iteratePRs(repo *models.Repository, each func(*models.Repository, *models.PullRequest) error) error { - return models.Iterate( - models.DefaultDBContext(), + return db.Iterate( + db.DefaultContext, new(models.PullRequest), builder.Eq{"base_repo_id": repo.ID}, func(idx int, bean interface{}) error { diff --git a/modules/doctor/misc.go b/modules/doctor/misc.go index 47fee8f7fd9fb..2f748bcb71870 100644 --- a/modules/doctor/misc.go +++ b/modules/doctor/misc.go @@ -12,19 +12,21 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" + lru "github.com/hashicorp/golang-lru" "xorm.io/builder" ) func iterateRepositories(each func(*models.Repository) error) error { - err := models.Iterate( - models.DefaultDBContext(), + err := db.Iterate( + db.DefaultContext, new(models.Repository), builder.Gt{"id": 0}, func(idx int, bean interface{}) error { diff --git a/modules/doctor/paths.go b/modules/doctor/paths.go index 53409b5fa4dfd..88172d3150967 100644 --- a/modules/doctor/paths.go +++ b/modules/doctor/paths.go @@ -6,7 +6,6 @@ package doctor import ( "fmt" - "io/ioutil" "os" "code.gitea.io/gitea/modules/log" @@ -102,7 +101,7 @@ func isWritableDir(path string) error { // There's no platform-independent way of checking if a directory is writable // https://stackoverflow.com/questions/20026320/how-to-tell-if-folder-exists-and-is-writable - tmpFile, err := ioutil.TempFile(path, "doctors-order") + tmpFile, err := os.CreateTemp(path, "doctors-order") if err != nil { return err } diff --git a/modules/git/batch_reader.go b/modules/git/batch_reader.go index 164e643812894..8e3c23251b635 100644 --- a/modules/git/batch_reader.go +++ b/modules/git/batch_reader.go @@ -8,8 +8,10 @@ import ( "bufio" "bytes" "context" + "fmt" "io" "math" + "runtime" "strconv" "strings" @@ -40,9 +42,14 @@ func CatFileBatchCheck(repoPath string) (WriteCloserError, *bufio.Reader, func() <-closed } + _, filename, line, _ := runtime.Caller(2) + filename = strings.TrimPrefix(filename, callerPrefix) + go func() { stderr := strings.Builder{} - err := NewCommandContext(ctx, "cat-file", "--batch-check").RunInDirFullPipeline(repoPath, batchStdoutWriter, &stderr, batchStdinReader) + err := NewCommandContext(ctx, "cat-file", "--batch-check"). + SetDescription(fmt.Sprintf("%s cat-file --batch-check [repo_path: %s] (%s:%d)", GitExecutable, repoPath, filename, line)). + RunInDirFullPipeline(repoPath, batchStdoutWriter, &stderr, batchStdinReader) if err != nil { _ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String())) _ = batchStdinReader.CloseWithError(ConcatenateError(err, (&stderr).String())) @@ -76,9 +83,14 @@ func CatFileBatch(repoPath string) (WriteCloserError, *bufio.Reader, func()) { <-closed } + _, filename, line, _ := runtime.Caller(2) + filename = strings.TrimPrefix(filename, callerPrefix) + go func() { stderr := strings.Builder{} - err := NewCommandContext(ctx, "cat-file", "--batch").RunInDirFullPipeline(repoPath, batchStdoutWriter, &stderr, batchStdinReader) + err := NewCommandContext(ctx, "cat-file", "--batch"). + SetDescription(fmt.Sprintf("%s cat-file --batch [repo_path: %s] (%s:%d)", GitExecutable, repoPath, filename, line)). + RunInDirFullPipeline(repoPath, batchStdoutWriter, &stderr, batchStdinReader) if err != nil { _ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String())) _ = batchStdinReader.CloseWithError(ConcatenateError(err, (&stderr).String())) @@ -292,3 +304,10 @@ func ParseTreeLine(rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fn sha = shaBuf return } + +var callerPrefix string + +func init() { + _, filename, _, _ := runtime.Caller(0) + callerPrefix = strings.TrimSuffix(filename, "modules/git/batch_reader.go") +} diff --git a/modules/git/blame_test.go b/modules/git/blame_test.go index 734d63ee1410a..4bee8cd27a962 100644 --- a/modules/git/blame_test.go +++ b/modules/git/blame_test.go @@ -6,7 +6,7 @@ package git import ( "context" - "io/ioutil" + "os" "testing" "github.com/stretchr/testify/assert" @@ -84,7 +84,7 @@ e2aa991e10ffd924a828ec149951f2f20eecead2 7 7 ` func TestReadingBlameOutput(t *testing.T) { - tempFile, err := ioutil.TempFile("", ".txt") + tempFile, err := os.CreateTemp("", ".txt") if err != nil { panic(err) } diff --git a/modules/git/blob.go b/modules/git/blob.go index 5831bc3735aae..c7919f3c41336 100644 --- a/modules/git/blob.go +++ b/modules/git/blob.go @@ -9,7 +9,6 @@ import ( "bytes" "encoding/base64" "io" - "io/ioutil" "code.gitea.io/gitea/modules/typesniffer" ) @@ -83,7 +82,7 @@ func (b *Blob) GetBlobContentBase64() (string, error) { } }() - out, err := ioutil.ReadAll(pr) + out, err := io.ReadAll(pr) if err != nil { return "", err } diff --git a/modules/git/blob_nogogit.go b/modules/git/blob_nogogit.go index 3391bc39311b1..65a73ebc522fa 100644 --- a/modules/git/blob_nogogit.go +++ b/modules/git/blob_nogogit.go @@ -11,7 +11,6 @@ import ( "bufio" "bytes" "io" - "io/ioutil" "math" "code.gitea.io/gitea/modules/log" @@ -46,13 +45,13 @@ func (b *Blob) DataAsync() (io.ReadCloser, error) { b.size = size if size < 4096 { - bs, err := ioutil.ReadAll(io.LimitReader(rd, size)) + bs, err := io.ReadAll(io.LimitReader(rd, size)) defer cancel() if err != nil { return nil, err } _, err = rd.Discard(1) - return ioutil.NopCloser(bytes.NewReader(bs)), err + return io.NopCloser(bytes.NewReader(bs)), err } return &blobReader{ diff --git a/modules/git/blob_test.go b/modules/git/blob_test.go index 2ceda6c4ef17d..34d8054d1e753 100644 --- a/modules/git/blob_test.go +++ b/modules/git/blob_test.go @@ -6,7 +6,7 @@ package git import ( - "io/ioutil" + "io" "path/filepath" "testing" @@ -30,7 +30,7 @@ func TestBlob_Data(t *testing.T) { assert.NoError(t, err) require.NotNil(t, r) - data, err := ioutil.ReadAll(r) + data, err := io.ReadAll(r) assert.NoError(t, r.Close()) assert.NoError(t, err) @@ -55,7 +55,7 @@ func Benchmark_Blob_Data(b *testing.B) { if err != nil { b.Fatal(err) } - ioutil.ReadAll(r) + io.ReadAll(r) _ = r.Close() } } diff --git a/modules/git/commit_info_gogit.go b/modules/git/commit_info_gogit.go index 8b82f3f66cbae..ccf90fc8c7ef7 100644 --- a/modules/git/commit_info_gogit.go +++ b/modules/git/commit_info_gogit.go @@ -44,20 +44,17 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath return nil, nil, err } if len(unHitPaths) > 0 { - revs2, err := GetLastCommitForPaths(ctx, c, treePath, unHitPaths) + revs2, err := GetLastCommitForPaths(ctx, cache, c, treePath, unHitPaths) if err != nil { return nil, nil, err } for k, v := range revs2 { - if err := cache.Put(commit.ID.String(), path.Join(treePath, k), v.ID().String()); err != nil { - return nil, nil, err - } revs[k] = v } } } else { - revs, err = GetLastCommitForPaths(ctx, c, treePath, entryPaths) + revs, err = GetLastCommitForPaths(ctx, nil, c, treePath, entryPaths) } if err != nil { return nil, nil, err @@ -70,25 +67,29 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath commitsInfo[i] = CommitInfo{ Entry: entry, } + + // Check if we have found a commit for this entry in time if rev, ok := revs[entry.Name()]; ok { entryCommit := convertCommit(rev) commitsInfo[i].Commit = entryCommit - if entry.IsSubModule() { - subModuleURL := "" - var fullPath string - if len(treePath) > 0 { - fullPath = treePath + "/" + entry.Name() - } else { - fullPath = entry.Name() - } - if subModule, err := commit.GetSubModule(fullPath); err != nil { - return nil, nil, err - } else if subModule != nil { - subModuleURL = subModule.URL - } - subModuleFile := NewSubModuleFile(entryCommit, subModuleURL, entry.ID.String()) - commitsInfo[i].SubModuleFile = subModuleFile + } + + // If the entry if a submodule add a submodule file for this + if entry.IsSubModule() { + subModuleURL := "" + var fullPath string + if len(treePath) > 0 { + fullPath = treePath + "/" + entry.Name() + } else { + fullPath = entry.Name() } + if subModule, err := commit.GetSubModule(fullPath); err != nil { + return nil, nil, err + } else if subModule != nil { + subModuleURL = subModule.URL + } + subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, subModuleURL, entry.ID.String()) + commitsInfo[i].SubModuleFile = subModuleFile } } @@ -175,7 +176,9 @@ func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cac } // GetLastCommitForPaths returns last commit information -func GetLastCommitForPaths(ctx context.Context, c cgobject.CommitNode, treePath string, paths []string) (map[string]*object.Commit, error) { +func GetLastCommitForPaths(ctx context.Context, cache *LastCommitCache, c cgobject.CommitNode, treePath string, paths []string) (map[string]*object.Commit, error) { + refSha := c.ID().String() + // We do a tree traversal with nodes sorted by commit time heap := binaryheap.NewWith(func(a, b interface{}) int { if a.(*commitAndPaths).commit.CommitTime().Before(b.(*commitAndPaths).commit.CommitTime()) { @@ -192,10 +195,13 @@ func GetLastCommitForPaths(ctx context.Context, c cgobject.CommitNode, treePath // Start search from the root commit and with full set of paths heap.Push(&commitAndPaths{c, paths, initialHashes}) - +heaploop: for { select { case <-ctx.Done(): + if ctx.Err() == context.DeadlineExceeded { + break heaploop + } return nil, ctx.Err() default: } @@ -233,14 +239,14 @@ func GetLastCommitForPaths(ctx context.Context, c cgobject.CommitNode, treePath } var remainingPaths []string - for i, path := range current.paths { + for i, pth := range current.paths { // The results could already contain some newer change for the same path, // so don't override that and bail out on the file early. - if resultNodes[path] == nil { + if resultNodes[pth] == nil { if pathUnchanged[i] { // The path existed with the same hash in at least one parent so it could // not have been changed in this commit directly. - remainingPaths = append(remainingPaths, path) + remainingPaths = append(remainingPaths, pth) } else { // There are few possible cases how can we get here: // - The path didn't exist in any parent, so it must have been created by @@ -250,7 +256,10 @@ func GetLastCommitForPaths(ctx context.Context, c cgobject.CommitNode, treePath // - We are looking at a merge commit and the hash of the file doesn't // match any of the hashes being merged. This is more common for directories, // but it can also happen if a file is changed through conflict resolution. - resultNodes[path] = current.commit + resultNodes[pth] = current.commit + if err := cache.Put(refSha, path.Join(treePath, pth), current.commit.ID().String()); err != nil { + return nil, err + } } } } diff --git a/modules/git/commit_info_nogogit.go b/modules/git/commit_info_nogogit.go index f57355d16ed40..261252dab1b51 100644 --- a/modules/git/commit_info_nogogit.go +++ b/modules/git/commit_info_nogogit.go @@ -37,21 +37,18 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath } if len(unHitPaths) > 0 { sort.Strings(unHitPaths) - commits, err := GetLastCommitForPaths(ctx, commit, treePath, unHitPaths) + commits, err := GetLastCommitForPaths(ctx, cache, commit, treePath, unHitPaths) if err != nil { return nil, nil, err } for pth, found := range commits { - if err := cache.Put(commit.ID.String(), path.Join(treePath, pth), found.ID.String()); err != nil { - return nil, nil, err - } revs[pth] = found } } } else { sort.Strings(entryPaths) - revs, err = GetLastCommitForPaths(ctx, commit, treePath, entryPaths) + revs, err = GetLastCommitForPaths(ctx, nil, commit, treePath, entryPaths) } if err != nil { return nil, nil, err @@ -62,27 +59,31 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath commitsInfo[i] = CommitInfo{ Entry: entry, } + + // Check if we have found a commit for this entry in time if entryCommit, ok := revs[entry.Name()]; ok { commitsInfo[i].Commit = entryCommit - if entry.IsSubModule() { - subModuleURL := "" - var fullPath string - if len(treePath) > 0 { - fullPath = treePath + "/" + entry.Name() - } else { - fullPath = entry.Name() - } - if subModule, err := commit.GetSubModule(fullPath); err != nil { - return nil, nil, err - } else if subModule != nil { - subModuleURL = subModule.URL - } - subModuleFile := NewSubModuleFile(entryCommit, subModuleURL, entry.ID.String()) - commitsInfo[i].SubModuleFile = subModuleFile - } } else { log.Debug("missing commit for %s", entry.Name()) } + + // If the entry if a submodule add a submodule file for this + if entry.IsSubModule() { + subModuleURL := "" + var fullPath string + if len(treePath) > 0 { + fullPath = treePath + "/" + entry.Name() + } else { + fullPath = entry.Name() + } + if subModule, err := commit.GetSubModule(fullPath); err != nil { + return nil, nil, err + } else if subModule != nil { + subModuleURL = subModule.URL + } + subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, subModuleURL, entry.ID.String()) + commitsInfo[i].SubModuleFile = subModuleFile + } } // Retrieve the commit for the treePath itself (see above). We basically @@ -121,9 +122,9 @@ func getLastCommitForPathsByCache(ctx context.Context, commitID, treePath string } // GetLastCommitForPaths returns last commit information -func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) { +func GetLastCommitForPaths(ctx context.Context, cache *LastCommitCache, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) { // We read backwards from the commit to obtain all of the commits - revs, err := WalkGitLog(ctx, commit.repo, commit, treePath, paths...) + revs, err := WalkGitLog(ctx, cache, commit.repo, commit, treePath, paths...) if err != nil { return nil, err } diff --git a/modules/git/diff.go b/modules/git/diff.go index 20f25c1bee3f5..b473dc73f60e8 100644 --- a/modules/git/diff.go +++ b/modules/git/diff.go @@ -10,6 +10,7 @@ import ( "context" "fmt" "io" + "os" "os/exec" "regexp" "strconv" @@ -273,3 +274,46 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi oldBegin, oldNumOfLines, newBegin, newNumOfLines) return strings.Join(newHunk, "\n"), nil } + +// GetAffectedFiles returns the affected files between two commits +func GetAffectedFiles(oldCommitID, newCommitID string, env []string, repo *Repository) ([]string, error) { + stdoutReader, stdoutWriter, err := os.Pipe() + if err != nil { + log.Error("Unable to create os.Pipe for %s", repo.Path) + return nil, err + } + defer func() { + _ = stdoutReader.Close() + _ = stdoutWriter.Close() + }() + + affectedFiles := make([]string, 0, 32) + + // Run `git diff --name-only` to get the names of the changed files + err = NewCommand("diff", "--name-only", oldCommitID, newCommitID). + RunInDirTimeoutEnvFullPipelineFunc(env, -1, repo.Path, + stdoutWriter, nil, nil, + func(ctx context.Context, cancel context.CancelFunc) error { + // Close the writer end of the pipe to begin processing + _ = stdoutWriter.Close() + defer func() { + // Close the reader on return to terminate the git command if necessary + _ = stdoutReader.Close() + }() + // Now scan the output from the command + scanner := bufio.NewScanner(stdoutReader) + for scanner.Scan() { + path := strings.TrimSpace(scanner.Text()) + if len(path) == 0 { + continue + } + affectedFiles = append(affectedFiles, path) + } + return scanner.Err() + }) + if err != nil { + log.Error("Unable to get affected files for commits from %s to %s in %s: %v", oldCommitID, newCommitID, repo.Path, err) + } + + return affectedFiles, err +} diff --git a/modules/git/hook.go b/modules/git/hook.go index 7007d23be22ec..ecd2db3cc3a1d 100644 --- a/modules/git/hook.go +++ b/modules/git/hook.go @@ -7,7 +7,6 @@ package git import ( "errors" - "io/ioutil" "os" "path" "path/filepath" @@ -59,14 +58,14 @@ func GetHook(repoPath, name string) (*Hook, error) { } samplePath := filepath.Join(repoPath, "hooks", name+".sample") if isFile(h.path) { - data, err := ioutil.ReadFile(h.path) + data, err := os.ReadFile(h.path) if err != nil { return nil, err } h.IsActive = true h.Content = string(data) } else if isFile(samplePath) { - data, err := ioutil.ReadFile(samplePath) + data, err := os.ReadFile(samplePath) if err != nil { return nil, err } @@ -97,7 +96,7 @@ func (h *Hook) Update() error { return err } - err := ioutil.WriteFile(h.path, []byte(strings.ReplaceAll(h.Content, "\r", "")), os.ModePerm) + err := os.WriteFile(h.path, []byte(strings.ReplaceAll(h.Content, "\r", "")), os.ModePerm) if err != nil { return err } @@ -143,5 +142,5 @@ func SetUpdateHook(repoPath, content string) (err error) { if err != nil { return err } - return ioutil.WriteFile(hookPath, []byte(content), 0777) + return os.WriteFile(hookPath, []byte(content), 0777) } diff --git a/modules/git/last_commit_cache.go b/modules/git/last_commit_cache.go index e2d296641f3ba..d4ec517b51025 100644 --- a/modules/git/last_commit_cache.go +++ b/modules/git/last_commit_cache.go @@ -26,6 +26,9 @@ func (c *LastCommitCache) getCacheKey(repoPath, ref, entryPath string) string { // Put put the last commit id with commit and entry path func (c *LastCommitCache) Put(ref, entryPath, commitID string) error { + if c == nil || c.cache == nil { + return nil + } log.Debug("LastCommitCache save: [%s:%s:%s]", ref, entryPath, commitID) return c.cache.Put(c.getCacheKey(c.repoPath, ref, entryPath), commitID, c.ttl()) } diff --git a/modules/git/last_commit_cache_gogit.go b/modules/git/last_commit_cache_gogit.go index fb09af6f2a762..b57e9ad11ff0d 100644 --- a/modules/git/last_commit_cache_gogit.go +++ b/modules/git/last_commit_cache_gogit.go @@ -9,7 +9,6 @@ package git import ( "context" - "path" "code.gitea.io/gitea/modules/log" @@ -93,15 +92,12 @@ func (c *LastCommitCache) recursiveCache(ctx context.Context, index cgobject.Com entryMap[entry.Name()] = entry } - commits, err := GetLastCommitForPaths(ctx, index, treePath, entryPaths) + commits, err := GetLastCommitForPaths(ctx, c, index, treePath, entryPaths) if err != nil { return err } - for entry, cm := range commits { - if err := c.Put(index.ID().String(), path.Join(treePath, entry), cm.ID().String()); err != nil { - return err - } + for entry := range commits { if entryMap[entry].IsDir() { subTree, err := tree.SubTree(entry) if err != nil { diff --git a/modules/git/last_commit_cache_nogogit.go b/modules/git/last_commit_cache_nogogit.go index f71e7350a1fec..5315c0a152d4d 100644 --- a/modules/git/last_commit_cache_nogogit.go +++ b/modules/git/last_commit_cache_nogogit.go @@ -10,7 +10,6 @@ package git import ( "bufio" "context" - "path" "code.gitea.io/gitea/modules/log" ) @@ -80,28 +79,23 @@ func (c *LastCommitCache) recursiveCache(ctx context.Context, commit *Commit, tr } entryPaths := make([]string, len(entries)) - entryMap := make(map[string]*TreeEntry) for i, entry := range entries { entryPaths[i] = entry.Name() - entryMap[entry.Name()] = entry } - commits, err := GetLastCommitForPaths(ctx, commit, treePath, entryPaths) + _, err = WalkGitLog(ctx, c, commit.repo, commit, treePath, entryPaths...) if err != nil { return err } - for entry, entryCommit := range commits { - if err := c.Put(commit.ID.String(), path.Join(treePath, entry), entryCommit.ID.String()); err != nil { - return err - } + for _, treeEntry := range entries { // entryMap won't contain "" therefore skip this. - if treeEntry := entryMap[entry]; treeEntry != nil && treeEntry.IsDir() { - subTree, err := tree.SubTree(entry) + if treeEntry.IsDir() { + subTree, err := tree.SubTree(treeEntry.Name()) if err != nil { return err } - if err := c.recursiveCache(ctx, commit, subTree, entry, level-1); err != nil { + if err := c.recursiveCache(ctx, commit, subTree, treeEntry.Name(), level-1); err != nil { return err } } diff --git a/modules/git/log_name_status.go b/modules/git/log_name_status.go index 7d83ac7270c04..e792b02a5dc58 100644 --- a/modules/git/log_name_status.go +++ b/modules/git/log_name_status.go @@ -275,7 +275,9 @@ func (g *LogNameStatusRepoParser) Close() { } // WalkGitLog walks the git log --name-status for the head commit in the provided treepath and files -func WalkGitLog(ctx context.Context, repo *Repository, head *Commit, treepath string, paths ...string) (map[string]string, error) { +func WalkGitLog(ctx context.Context, cache *LastCommitCache, repo *Repository, head *Commit, treepath string, paths ...string) (map[string]string, error) { + headRef := head.ID.String() + tree, err := head.SubTree(treepath) if err != nil { return nil, err @@ -339,6 +341,9 @@ heaploop: for { select { case <-ctx.Done(): + if ctx.Err() == context.DeadlineExceeded { + break heaploop + } g.Close() return nil, ctx.Err() default: @@ -360,10 +365,16 @@ heaploop: changed[i] = false if results[i] == "" { results[i] = current.CommitID + if err := cache.Put(headRef, path.Join(treepath, paths[i]), current.CommitID); err != nil { + return nil, err + } delete(path2idx, paths[i]) remaining-- if results[0] == "" { results[0] = current.CommitID + if err := cache.Put(headRef, treepath, current.CommitID); err != nil { + return nil, err + } delete(path2idx, "") remaining-- } diff --git a/modules/git/notes_gogit.go b/modules/git/notes_gogit.go index 84f66d86bf67b..6cb719ce92a58 100644 --- a/modules/git/notes_gogit.go +++ b/modules/git/notes_gogit.go @@ -9,7 +9,7 @@ package git import ( "context" - "io/ioutil" + "io" "code.gitea.io/gitea/modules/log" @@ -17,6 +17,7 @@ import ( ) // GetNote retrieves the git-notes data for a given commit. +// FIXME: Add LastCommitCache support func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) error { log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path) notes, err := repo.GetCommit(NotesRef) @@ -58,7 +59,7 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) } defer dataRc.Close() - d, err := ioutil.ReadAll(dataRc) + d, err := io.ReadAll(dataRc) if err != nil { log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err) return err @@ -75,7 +76,7 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) return err } - lastCommits, err := GetLastCommitForPaths(ctx, commitNode, "", []string{path}) + lastCommits, err := GetLastCommitForPaths(ctx, nil, commitNode, "", []string{path}) if err != nil { log.Error("Unable to get the commit for the path %q. Error: %v", path, err) return err diff --git a/modules/git/notes_nogogit.go b/modules/git/notes_nogogit.go index 9a54b4810638e..13b4b7b36ab02 100644 --- a/modules/git/notes_nogogit.go +++ b/modules/git/notes_nogogit.go @@ -9,13 +9,14 @@ package git import ( "context" - "io/ioutil" + "io" "strings" "code.gitea.io/gitea/modules/log" ) // GetNote retrieves the git-notes data for a given commit. +// FIXME: Add LastCommitCache support func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) error { log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path) notes, err := repo.GetCommit(NotesRef) @@ -60,7 +61,7 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) _ = dataRc.Close() } }() - d, err := ioutil.ReadAll(dataRc) + d, err := io.ReadAll(dataRc) if err != nil { log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err) return err @@ -75,7 +76,7 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) path = path[idx+1:] } - lastCommits, err := GetLastCommitForPaths(ctx, notes, treePath, []string{path}) + lastCommits, err := GetLastCommitForPaths(ctx, nil, notes, treePath, []string{path}) if err != nil { log.Error("Unable to get the commit for the path %q. Error: %v", treePath, err) return err diff --git a/modules/git/repo.go b/modules/git/repo.go index e7d42dacb165d..89af7aa9e1dad 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -425,14 +425,24 @@ func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io. } defer os.RemoveAll(tmp) - tmpFile := filepath.Join(tmp, "bundle") - args := []string{ - "bundle", - "create", - tmpFile, - commit, + env := append(os.Environ(), "GIT_OBJECT_DIRECTORY="+filepath.Join(repo.Path, "objects")) + _, err = NewCommandContext(ctx, "init", "--bare").RunInDirWithEnv(tmp, env) + if err != nil { + return err + } + + _, err = NewCommandContext(ctx, "reset", "--soft", commit).RunInDirWithEnv(tmp, env) + if err != nil { + return err } - _, err = NewCommandContext(ctx, args...).RunInDir(repo.Path) + + _, err = NewCommandContext(ctx, "branch", "-m", "bundle").RunInDirWithEnv(tmp, env) + if err != nil { + return err + } + + tmpFile := filepath.Join(tmp, "bundle") + _, err = NewCommandContext(ctx, "bundle", "create", tmpFile, "bundle", "HEAD").RunInDirWithEnv(tmp, env) if err != nil { return err } diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go index 0bd7d7e49c947..aace64425388a 100644 --- a/modules/git/repo_attribute.go +++ b/modules/git/repo_attribute.go @@ -12,6 +12,8 @@ import ( "os" "strconv" "strings" + + "code.gitea.io/gitea/modules/log" ) // CheckAttributeOpts represents the possible options to CheckAttribute @@ -112,42 +114,48 @@ func (c *CheckAttributeReader) Init(ctx context.Context) error { if len(c.IndexFile) > 0 && CheckGitVersionAtLeast("1.7.8") == nil { cmdArgs = append(cmdArgs, "--cached") - c.env = []string{"GIT_INDEX_FILE=" + c.IndexFile} + c.env = append(c.env, "GIT_INDEX_FILE="+c.IndexFile) } if len(c.WorkTree) > 0 && CheckGitVersionAtLeast("1.7.8") == nil { - c.env = []string{"GIT_WORK_TREE=" + c.WorkTree} + c.env = append(c.env, "GIT_WORK_TREE="+c.WorkTree) } - if len(c.Attributes) > 0 { - cmdArgs = append(cmdArgs, c.Attributes...) - cmdArgs = append(cmdArgs, "--") - } else { + c.env = append(c.env, "GIT_FLUSH=1") + + if len(c.Attributes) == 0 { lw := new(nulSeparatedAttributeWriter) lw.attributes = make(chan attributeTriple) + lw.closed = make(chan struct{}) c.stdOut = lw c.stdOut.Close() return fmt.Errorf("no provided Attributes to check") } + cmdArgs = append(cmdArgs, c.Attributes...) + cmdArgs = append(cmdArgs, "--") + c.ctx, c.cancel = context.WithCancel(ctx) c.cmd = NewCommandContext(c.ctx, cmdArgs...) + var err error + c.stdinReader, c.stdinWriter, err = os.Pipe() if err != nil { + c.cancel() return err } if CheckGitVersionAtLeast("1.8.5") == nil { lw := new(nulSeparatedAttributeWriter) lw.attributes = make(chan attributeTriple, 5) - + lw.closed = make(chan struct{}) c.stdOut = lw } else { lw := new(lineSeparatedAttributeWriter) lw.attributes = make(chan attributeTriple, 5) - + lw.closed = make(chan struct{}) c.stdOut = lw } return nil @@ -155,13 +163,14 @@ func (c *CheckAttributeReader) Init(ctx context.Context) error { // Run run cmd func (c *CheckAttributeReader) Run() error { + defer func() { + _ = c.Close() + }() stdErr := new(bytes.Buffer) err := c.cmd.RunInDirTimeoutEnvFullPipelineFunc(c.env, -1, c.Repo.Path, c.stdOut, stdErr, c.stdinReader, func(_ context.Context, _ context.CancelFunc) error { close(c.running) return nil }) - defer c.cancel() - _ = c.stdOut.Close() if err != nil && c.ctx.Err() != nil && err.Error() != "signal: killed" { return fmt.Errorf("failed to run attr-check. Error: %w\nStderr: %s", err, stdErr.String()) } @@ -170,27 +179,31 @@ func (c *CheckAttributeReader) Run() error { } // CheckPath check attr for given path -func (c *CheckAttributeReader) CheckPath(path string) (map[string]string, error) { +func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err error) { + defer func() { + if err != nil { + log.Error("CheckPath returns error: %v", err) + } + }() + select { case <-c.ctx.Done(): return nil, c.ctx.Err() case <-c.running: } - if _, err := c.stdinWriter.Write([]byte(path + "\x00")); err != nil { - defer c.cancel() + if _, err = c.stdinWriter.Write([]byte(path + "\x00")); err != nil { + defer c.Close() return nil, err } - if err := c.stdinWriter.Sync(); err != nil { - defer c.cancel() - return nil, err - } - - rs := make(map[string]string) + rs = make(map[string]string) for range c.Attributes { select { - case attr := <-c.stdOut.ReadAttribute(): + case attr, ok := <-c.stdOut.ReadAttribute(): + if !ok { + return nil, c.ctx.Err() + } rs[attr.Attribute] = attr.Value case <-c.ctx.Done(): return nil, c.ctx.Err() @@ -201,13 +214,16 @@ func (c *CheckAttributeReader) CheckPath(path string) (map[string]string, error) // Close close pip after use func (c *CheckAttributeReader) Close() error { + err := c.stdinWriter.Close() + _ = c.stdinReader.Close() + _ = c.stdOut.Close() + c.cancel() select { case <-c.running: default: close(c.running) } - defer c.cancel() - return c.stdinWriter.Close() + return err } type attributeWriter interface { @@ -224,6 +240,7 @@ type attributeTriple struct { type nulSeparatedAttributeWriter struct { tmp []byte attributes chan attributeTriple + closed chan struct{} working attributeTriple pos int } @@ -267,13 +284,20 @@ func (wr *nulSeparatedAttributeWriter) ReadAttribute() <-chan attributeTriple { } func (wr *nulSeparatedAttributeWriter) Close() error { + select { + case <-wr.closed: + return nil + default: + } close(wr.attributes) + close(wr.closed) return nil } type lineSeparatedAttributeWriter struct { tmp []byte attributes chan attributeTriple + closed chan struct{} } func (wr *lineSeparatedAttributeWriter) Write(p []byte) (n int, err error) { @@ -356,6 +380,12 @@ func (wr *lineSeparatedAttributeWriter) ReadAttribute() <-chan attributeTriple { } func (wr *lineSeparatedAttributeWriter) Close() error { + select { + case <-wr.closed: + return nil + default: + } close(wr.attributes) + close(wr.closed) return nil } diff --git a/modules/git/repo_blob_test.go b/modules/git/repo_blob_test.go index ccf418b3059f8..132a3fa50c9bc 100644 --- a/modules/git/repo_blob_test.go +++ b/modules/git/repo_blob_test.go @@ -6,7 +6,7 @@ package git import ( "fmt" - "io/ioutil" + "io" "path/filepath" "testing" @@ -34,7 +34,7 @@ func TestRepository_GetBlob_Found(t *testing.T) { dataReader, err := blob.DataAsync() assert.NoError(t, err) - data, err := ioutil.ReadAll(dataReader) + data, err := io.ReadAll(dataReader) assert.NoError(t, dataReader.Close()) assert.NoError(t, err) assert.Equal(t, testCase.Data, data) diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index e456f04e8773a..25060f56da627 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -8,7 +8,6 @@ package git import ( "bytes" "io" - "io/ioutil" "strconv" "strings" @@ -222,7 +221,7 @@ func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) ( }() if skip > 0 { - _, err := io.CopyN(ioutil.Discard, stdoutReader, int64(skip*41)) + _, err := io.CopyN(io.Discard, stdoutReader, int64(skip*41)) if err != nil { if err == io.EOF { return []*Commit{}, nil @@ -232,7 +231,7 @@ func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) ( } } - stdout, err := ioutil.ReadAll(stdoutReader) + stdout, err := io.ReadAll(stdoutReader) if err != nil { return nil, err } diff --git a/modules/git/repo_commit_nogogit.go b/modules/git/repo_commit_nogogit.go index b7e49a6501b17..8bfc953759d53 100644 --- a/modules/git/repo_commit_nogogit.go +++ b/modules/git/repo_commit_nogogit.go @@ -11,7 +11,6 @@ import ( "bufio" "errors" "io" - "io/ioutil" "strings" "code.gitea.io/gitea/modules/log" @@ -77,7 +76,7 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id SHA1) (*Co case "tag": // then we need to parse the tag // and load the commit - data, err := ioutil.ReadAll(io.LimitReader(rd, size)) + data, err := io.ReadAll(io.LimitReader(rd, size)) if err != nil { return nil, err } diff --git a/modules/git/repo_compare.go b/modules/git/repo_compare.go index 5d1208aab18fe..50e9005511131 100644 --- a/modules/git/repo_compare.go +++ b/modules/git/repo_compare.go @@ -46,7 +46,7 @@ func (repo *Repository) GetMergeBase(tmpRemote string, base, head string) (strin } // GetCompareInfo generates and returns compare information between base and head branches of repositories. -func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string) (_ *CompareInfo, err error) { +func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string, directComparison bool) (_ *CompareInfo, err error) { var ( remoteBranch string tmpRemote string @@ -79,8 +79,15 @@ func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string) if err != nil { compareInfo.BaseCommitID = remoteBranch } + separator := "..." + baseCommitID := compareInfo.MergeBase + if directComparison { + separator = ".." + baseCommitID = compareInfo.BaseCommitID + } + // We have a common base - therefore we know that ... should work - logs, err := NewCommand("log", compareInfo.MergeBase+"..."+headBranch, prettyLogFormat).RunInDirBytes(repo.Path) + logs, err := NewCommand("log", baseCommitID+separator+headBranch, prettyLogFormat).RunInDirBytes(repo.Path) if err != nil { return nil, err } @@ -100,7 +107,7 @@ func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string) // Count number of changed files. // This probably should be removed as we need to use shortstat elsewhere // Now there is git diff --shortstat but this appears to be slower than simply iterating with --nameonly - compareInfo.NumFiles, err = repo.GetDiffNumChangedFiles(remoteBranch, headBranch) + compareInfo.NumFiles, err = repo.GetDiffNumChangedFiles(remoteBranch, headBranch, directComparison) if err != nil { return nil, err } @@ -120,12 +127,17 @@ func (l *lineCountWriter) Write(p []byte) (n int, err error) { // GetDiffNumChangedFiles counts the number of changed files // This is substantially quicker than shortstat but... -func (repo *Repository) GetDiffNumChangedFiles(base, head string) (int, error) { +func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparison bool) (int, error) { // Now there is git diff --shortstat but this appears to be slower than simply iterating with --nameonly w := &lineCountWriter{} stderr := new(bytes.Buffer) - if err := NewCommand("diff", "-z", "--name-only", base+"..."+head). + separator := "..." + if directComparison { + separator = ".." + } + + if err := NewCommand("diff", "-z", "--name-only", base+separator+head). RunInDirPipeline(repo.Path, w, stderr); err != nil { if strings.Contains(stderr.String(), "no merge base") { // git >= 2.28 now returns an error if base and head have become unrelated. @@ -203,20 +215,29 @@ func parseDiffStat(stdout string) (numFiles, totalAdditions, totalDeletions int, } // GetDiffOrPatch generates either diff or formatted patch data between given revisions -func (repo *Repository) GetDiffOrPatch(base, head string, w io.Writer, formatted bool) error { - if formatted { +func (repo *Repository) GetDiffOrPatch(base, head string, w io.Writer, patch, binary bool) error { + if patch { return repo.GetPatch(base, head, w) } + if binary { + return repo.GetDiffBinary(base, head, w) + } return repo.GetDiff(base, head, w) } -// GetDiff generates and returns patch data between given revisions. +// GetDiff generates and returns patch data between given revisions, optimized for human readability func (repo *Repository) GetDiff(base, head string, w io.Writer) error { + return NewCommand("diff", "-p", base, head). + RunInDirPipeline(repo.Path, w, nil) +} + +// GetDiffBinary generates and returns patch data between given revisions, including binary diffs. +func (repo *Repository) GetDiffBinary(base, head string, w io.Writer) error { return NewCommand("diff", "-p", "--binary", base, head). RunInDirPipeline(repo.Path, w, nil) } -// GetPatch generates and returns format-patch data between given revisions. +// GetPatch generates and returns format-patch data between given revisions, able to be used with `git apply` func (repo *Repository) GetPatch(base, head string, w io.Writer) error { stderr := new(bytes.Buffer) err := NewCommand("format-patch", "--binary", "--stdout", base+"..."+head). @@ -234,8 +255,7 @@ func (repo *Repository) GetDiffFromMergeBase(base, head string, w io.Writer) err err := NewCommand("diff", "-p", "--binary", base+"..."+head). RunInDirPipeline(repo.Path, w, stderr) if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) { - return NewCommand("diff", "-p", "--binary", base, head). - RunInDirPipeline(repo.Path, w, nil) + return repo.GetDiffBinary(base, head, w) } return err } diff --git a/modules/git/repo_compare_test.go b/modules/git/repo_compare_test.go index f0e20838f4b0f..3a6cda955c5eb 100644 --- a/modules/git/repo_compare_test.go +++ b/modules/git/repo_compare_test.go @@ -6,7 +6,7 @@ package git import ( "bytes" - "io/ioutil" + "io" "path/filepath" "testing" @@ -25,7 +25,7 @@ func TestGetFormatPatch(t *testing.T) { rd := &bytes.Buffer{} err = repo.GetPatch("8d92fc95^", "8d92fc95", rd) assert.NoError(t, err) - patchb, err := ioutil.ReadAll(rd) + patchb, err := io.ReadAll(rd) assert.NoError(t, err) patch := string(patchb) assert.Regexp(t, "^From 8d92fc95", patch) diff --git a/modules/git/repo_index.go b/modules/git/repo_index.go index b301ff2437b54..27cb7fbebe191 100644 --- a/modules/git/repo_index.go +++ b/modules/git/repo_index.go @@ -7,7 +7,6 @@ package git import ( "bytes" "context" - "io/ioutil" "os" "strings" @@ -47,7 +46,7 @@ func (repo *Repository) readTreeToIndex(id SHA1, indexFilename ...string) error // ReadTreeToTemporaryIndex reads a treeish to a temporary index file func (repo *Repository) ReadTreeToTemporaryIndex(treeish string) (filename string, cancel context.CancelFunc, err error) { - tmpIndex, err := ioutil.TempFile("", "index") + tmpIndex, err := os.CreateTemp("", "index") if err != nil { return } diff --git a/modules/git/repo_language_stats_gogit.go b/modules/git/repo_language_stats_gogit.go index 3abce1f0773de..d37827c3de6fc 100644 --- a/modules/git/repo_language_stats_gogit.go +++ b/modules/git/repo_language_stats_gogit.go @@ -11,10 +11,11 @@ import ( "bytes" "context" "io" - "io/ioutil" + "os" "code.gitea.io/gitea/modules/analyze" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" "github.com/go-enry/go-enry/v2" "github.com/go-git/go-git/v5" @@ -50,25 +51,32 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err indexFilename, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID) if err == nil { defer deleteTemporaryFile() - - checker = &CheckAttributeReader{ - Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language"}, - Repo: repo, - IndexFile: indexFilename, - } - ctx, cancel := context.WithCancel(DefaultContext) - if err := checker.Init(ctx); err != nil { - log.Error("Unable to open checker for %s. Error: %v", commitID, err) - } else { - go func() { - err = checker.Run() - if err != nil { - log.Error("Unable to open checker for %s. Error: %v", commitID, err) - cancel() - } + tmpWorkTree, err := os.MkdirTemp("", "empty-work-dir") + if err == nil { + defer func() { + _ = util.RemoveAll(tmpWorkTree) }() + + checker = &CheckAttributeReader{ + Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language"}, + Repo: repo, + IndexFile: indexFilename, + WorkTree: tmpWorkTree, + } + ctx, cancel := context.WithCancel(DefaultContext) + if err := checker.Init(ctx); err != nil { + log.Error("Unable to open checker for %s. Error: %v", commitID, err) + } else { + go func() { + err = checker.Run() + if err != nil { + log.Error("Unable to open checker for %s. Error: %v", commitID, err) + cancel() + } + }() + } + defer cancel() } - defer cancel() } } @@ -99,7 +107,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err if language, has := attrs["linguist-language"]; has && language != "unspecified" && language != "" { // group languages, such as Pug -> HTML; SCSS -> CSS group := enry.GetLanguageGroup(language) - if len(group) == 0 { + if len(group) != 0 { language = group } @@ -166,7 +174,7 @@ func readFile(f *object.File, limit int64) ([]byte, error) { defer r.Close() if limit <= 0 { - return ioutil.ReadAll(r) + return io.ReadAll(r) } size := f.Size diff --git a/modules/git/repo_language_stats_nogogit.go b/modules/git/repo_language_stats_nogogit.go index c3b96ea841e06..06269a466c72c 100644 --- a/modules/git/repo_language_stats_nogogit.go +++ b/modules/git/repo_language_stats_nogogit.go @@ -13,9 +13,11 @@ import ( "context" "io" "math" + "os" "code.gitea.io/gitea/modules/analyze" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" "github.com/go-enry/go-enry/v2" ) @@ -69,25 +71,32 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err indexFilename, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID) if err == nil { defer deleteTemporaryFile() - - checker = &CheckAttributeReader{ - Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language"}, - Repo: repo, - IndexFile: indexFilename, - } - ctx, cancel := context.WithCancel(DefaultContext) - if err := checker.Init(ctx); err != nil { - log.Error("Unable to open checker for %s. Error: %v", commitID, err) - } else { - go func() { - err = checker.Run() - if err != nil { - log.Error("Unable to open checker for %s. Error: %v", commitID, err) - cancel() - } + tmpWorkTree, err := os.MkdirTemp("", "empty-work-dir") + if err == nil { + defer func() { + _ = util.RemoveAll(tmpWorkTree) }() + + checker = &CheckAttributeReader{ + Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language"}, + Repo: repo, + IndexFile: indexFilename, + WorkTree: tmpWorkTree, + } + ctx, cancel := context.WithCancel(DefaultContext) + if err := checker.Init(ctx); err != nil { + log.Error("Unable to open checker for %s. Error: %v", commitID, err) + } else { + go func() { + err = checker.Run() + if err != nil { + log.Error("Unable to open checker for %s. Error: %v", commitID, err) + cancel() + } + }() + } + defer cancel() } - defer cancel() } } @@ -123,12 +132,11 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err if language, has := attrs["linguist-language"]; has && language != "unspecified" && language != "" { // group languages, such as Pug -> HTML; SCSS -> CSS group := enry.GetLanguageGroup(language) - if len(group) == 0 { + if len(group) != 0 { language = group } sizes[language] += f.Size() - continue } } @@ -186,7 +194,6 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err } sizes[language] += f.Size() - continue } diff --git a/modules/git/repo_language_stats_test.go b/modules/git/repo_language_stats_test.go new file mode 100644 index 0000000000000..a77266413aa64 --- /dev/null +++ b/modules/git/repo_language_stats_test.go @@ -0,0 +1,34 @@ +// Copyright 2020 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 !gogit +// +build !gogit + +package git + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRepository_GetLanguageStats(t *testing.T) { + repoPath := filepath.Join(testReposDir, "language_stats_repo") + gitRepo, err := OpenRepository(repoPath) + if !assert.NoError(t, err) { + t.Fatal() + } + defer gitRepo.Close() + + stats, err := gitRepo.GetLanguageStats("8fee858da5796dfb37704761701bb8e800ad9ef3") + if !assert.NoError(t, err) { + t.Fatal() + } + + assert.EqualValues(t, map[string]int64{ + "Python": 134, + "Java": 112, + }, stats) +} diff --git a/modules/git/repo_tag_gogit.go b/modules/git/repo_tag_gogit.go index 3022fe96f7772..ff8a6d53eee6e 100644 --- a/modules/git/repo_tag_gogit.go +++ b/modules/git/repo_tag_gogit.go @@ -21,7 +21,8 @@ func (repo *Repository) IsTagExist(name string) bool { } // GetTags returns all tags of the repository. -func (repo *Repository) GetTags() ([]string, error) { +// returning at most limit tags, or all if limit is 0. +func (repo *Repository) GetTags(skip, limit int) ([]string, error) { var tagNames []string tags, err := repo.gogitRepo.Tags() @@ -40,5 +41,15 @@ func (repo *Repository) GetTags() ([]string, error) { tagNames[i], tagNames[j] = tagNames[j], tagNames[i] } + // since we have to reverse order we can paginate only afterwards + if len(tagNames) < skip { + tagNames = []string{} + } else { + tagNames = tagNames[skip:] + } + if limit != 0 && len(tagNames) > limit { + tagNames = tagNames[:limit] + } + return tagNames, nil } diff --git a/modules/git/repo_tag_nogogit.go b/modules/git/repo_tag_nogogit.go index 0170f0cc76e7d..172b6fd66cbb0 100644 --- a/modules/git/repo_tag_nogogit.go +++ b/modules/git/repo_tag_nogogit.go @@ -18,7 +18,8 @@ func (repo *Repository) IsTagExist(name string) bool { } // GetTags returns all tags of the repository. -func (repo *Repository) GetTags() (tags []string, err error) { - tags, _, err = callShowRef(repo.Path, TagPrefix, "--tags", 0, 0) +// returning at most limit tags, or all if limit is 0. +func (repo *Repository) GetTags(skip, limit int) (tags []string, err error) { + tags, _, err = callShowRef(repo.Path, TagPrefix, "--tags", skip, limit) return } diff --git a/modules/git/repo_tree_nogogit.go b/modules/git/repo_tree_nogogit.go index 9d4268b13a286..b27abb6e02a6f 100644 --- a/modules/git/repo_tree_nogogit.go +++ b/modules/git/repo_tree_nogogit.go @@ -9,7 +9,6 @@ package git import ( "io" - "io/ioutil" ) func (repo *Repository) getTree(id SHA1) (*Tree, error) { @@ -27,7 +26,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) { switch typ { case "tag": resolvedID := id - data, err := ioutil.ReadAll(io.LimitReader(rd, size)) + data, err := io.ReadAll(io.LimitReader(rd, size)) if err != nil { return nil, err } diff --git a/modules/git/tests/repos/language_stats_repo/COMMIT_EDITMSG b/modules/git/tests/repos/language_stats_repo/COMMIT_EDITMSG new file mode 100644 index 0000000000000..ec4d890919877 --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/COMMIT_EDITMSG @@ -0,0 +1,3 @@ +Add some test files for GetLanguageStats + +Signed-off-by: Andrew Thornton diff --git a/modules/git/tests/repos/language_stats_repo/HEAD b/modules/git/tests/repos/language_stats_repo/HEAD new file mode 100644 index 0000000000000..cb089cd89a7d7 --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/modules/git/tests/repos/language_stats_repo/config b/modules/git/tests/repos/language_stats_repo/config new file mode 100644 index 0000000000000..515f4836297fd --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/config @@ -0,0 +1,5 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true diff --git a/modules/git/tests/repos/language_stats_repo/description b/modules/git/tests/repos/language_stats_repo/description new file mode 100644 index 0000000000000..498b267a8c781 --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/modules/git/tests/repos/language_stats_repo/hooks/applypatch-msg.sample b/modules/git/tests/repos/language_stats_repo/hooks/applypatch-msg.sample new file mode 100755 index 0000000000000..a5d7b84a67345 --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/modules/git/tests/repos/language_stats_repo/hooks/commit-msg.sample b/modules/git/tests/repos/language_stats_repo/hooks/commit-msg.sample new file mode 100755 index 0000000000000..b58d1184a9d43 --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/modules/git/tests/repos/language_stats_repo/hooks/fsmonitor-watchman.sample b/modules/git/tests/repos/language_stats_repo/hooks/fsmonitor-watchman.sample new file mode 100755 index 0000000000000..14ed0aa42de0f --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/hooks/fsmonitor-watchman.sample @@ -0,0 +1,173 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use IPC::Open2; + +# An example hook script to integrate Watchman +# (https://facebook.github.io/watchman/) with git to speed up detecting +# new and modified files. +# +# The hook is passed a version (currently 2) and last update token +# formatted as a string and outputs to stdout a new update token and +# all files that have been modified since the update token. Paths must +# be relative to the root of the working tree and separated by a single NUL. +# +# To enable this hook, rename this file to "query-watchman" and set +# 'git config core.fsmonitor .git/hooks/query-watchman' +# +my ($version, $last_update_token) = @ARGV; + +# Uncomment for debugging +# print STDERR "$0 $version $last_update_token\n"; + +# Check the hook interface version +if ($version ne 2) { + die "Unsupported query-fsmonitor hook version '$version'.\n" . + "Falling back to scanning...\n"; +} + +my $git_work_tree = get_working_dir(); + +my $retry = 1; + +my $json_pkg; +eval { + require JSON::XS; + $json_pkg = "JSON::XS"; + 1; +} or do { + require JSON::PP; + $json_pkg = "JSON::PP"; +}; + +launch_watchman(); + +sub launch_watchman { + my $o = watchman_query(); + if (is_work_tree_watched($o)) { + output_result($o->{clock}, @{$o->{files}}); + } +} + +sub output_result { + my ($clockid, @files) = @_; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # binmode $fh, ":utf8"; + # print $fh "$clockid\n@files\n"; + # close $fh; + + binmode STDOUT, ":utf8"; + print $clockid; + print "\0"; + local $, = "\0"; + print @files; +} + +sub watchman_clock { + my $response = qx/watchman clock "$git_work_tree"/; + die "Failed to get clock id on '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + + return $json_pkg->new->utf8->decode($response); +} + +sub watchman_query { + my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') + or die "open2() failed: $!\n" . + "Falling back to scanning...\n"; + + # In the query expression below we're asking for names of files that + # changed since $last_update_token but not from the .git folder. + # + # To accomplish this, we're using the "since" generator to use the + # recency index to select candidate nodes and "fields" to limit the + # output to file names only. Then we're using the "expression" term to + # further constrain the results. + if (substr($last_update_token, 0, 1) eq "c") { + $last_update_token = "\"$last_update_token\""; + } + my $query = <<" END"; + ["query", "$git_work_tree", { + "since": $last_update_token, + "fields": ["name"], + "expression": ["not", ["dirname", ".git"]] + }] + END + + # Uncomment for debugging the watchman query + # open (my $fh, ">", ".git/watchman-query.json"); + # print $fh $query; + # close $fh; + + print CHLD_IN $query; + close CHLD_IN; + my $response = do {local $/; }; + + # Uncomment for debugging the watch response + # open ($fh, ">", ".git/watchman-response.json"); + # print $fh $response; + # close $fh; + + die "Watchman: command returned no output.\n" . + "Falling back to scanning...\n" if $response eq ""; + die "Watchman: command returned invalid output: $response\n" . + "Falling back to scanning...\n" unless $response =~ /^\{/; + + return $json_pkg->new->utf8->decode($response); +} + +sub is_work_tree_watched { + my ($output) = @_; + my $error = $output->{error}; + if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { + $retry--; + my $response = qx/watchman watch "$git_work_tree"/; + die "Failed to make watchman watch '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + $output = $json_pkg->new->utf8->decode($response); + $error = $output->{error}; + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # close $fh; + + # Watchman will always return all files on the first query so + # return the fast "everything is dirty" flag to git and do the + # Watchman query just to get it over with now so we won't pay + # the cost in git to look up each individual file. + my $o = watchman_clock(); + $error = $output->{error}; + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + output_result($o->{clock}, ("/")); + $last_update_token = $o->{clock}; + + eval { launch_watchman() }; + return 0; + } + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + return 1; +} + +sub get_working_dir { + my $working_dir; + if ($^O =~ 'msys' || $^O =~ 'cygwin') { + $working_dir = Win32::GetCwd(); + $working_dir =~ tr/\\/\//; + } else { + require Cwd; + $working_dir = Cwd::cwd(); + } + + return $working_dir; +} diff --git a/modules/git/tests/repos/language_stats_repo/hooks/post-update.sample b/modules/git/tests/repos/language_stats_repo/hooks/post-update.sample new file mode 100755 index 0000000000000..ec17ec1939b7c --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/modules/git/tests/repos/language_stats_repo/hooks/pre-applypatch.sample b/modules/git/tests/repos/language_stats_repo/hooks/pre-applypatch.sample new file mode 100755 index 0000000000000..4142082bcb939 --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/modules/git/tests/repos/language_stats_repo/hooks/pre-commit.sample b/modules/git/tests/repos/language_stats_repo/hooks/pre-commit.sample new file mode 100755 index 0000000000000..e144712c85c05 --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=$(git hash-object -t tree /dev/null) +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --type=bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/modules/git/tests/repos/language_stats_repo/hooks/pre-merge-commit.sample b/modules/git/tests/repos/language_stats_repo/hooks/pre-merge-commit.sample new file mode 100755 index 0000000000000..399eab1924e39 --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/hooks/pre-merge-commit.sample @@ -0,0 +1,13 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git merge" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message to +# stderr if it wants to stop the merge commit. +# +# To enable this hook, rename this file to "pre-merge-commit". + +. git-sh-setup +test -x "$GIT_DIR/hooks/pre-commit" && + exec "$GIT_DIR/hooks/pre-commit" +: diff --git a/modules/git/tests/repos/language_stats_repo/hooks/pre-push.sample b/modules/git/tests/repos/language_stats_repo/hooks/pre-push.sample new file mode 100755 index 0000000000000..4ce688d32b753 --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/modules/git/tests/repos/language_stats_repo/hooks/pre-rebase.sample b/modules/git/tests/repos/language_stats_repo/hooks/pre-rebase.sample new file mode 100755 index 0000000000000..6cbef5c370d8c --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up to date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +<<\DOC_END + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/modules/git/tests/repos/language_stats_repo/hooks/pre-receive.sample b/modules/git/tests/repos/language_stats_repo/hooks/pre-receive.sample new file mode 100755 index 0000000000000..a1fd29ec14823 --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/hooks/pre-receive.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to make use of push options. +# The example simply echoes all push options that start with 'echoback=' +# and rejects all pushes when the "reject" push option is used. +# +# To enable this hook, rename this file to "pre-receive". + +if test -n "$GIT_PUSH_OPTION_COUNT" +then + i=0 + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" + do + eval "value=\$GIT_PUSH_OPTION_$i" + case "$value" in + echoback=*) + echo "echo from the pre-receive-hook: ${value#*=}" >&2 + ;; + reject) + exit 1 + esac + i=$((i + 1)) + done +fi diff --git a/modules/git/tests/repos/language_stats_repo/hooks/prepare-commit-msg.sample b/modules/git/tests/repos/language_stats_repo/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000000000..10fa14c5ab013 --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/hooks/prepare-commit-msg.sample @@ -0,0 +1,42 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first one removes the +# "# Please enter the commit message..." help message. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +COMMIT_MSG_FILE=$1 +COMMIT_SOURCE=$2 +SHA1=$3 + +/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" + +# case "$COMMIT_SOURCE,$SHA1" in +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; +# *) ;; +# esac + +# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" +# if test -z "$COMMIT_SOURCE" +# then +# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" +# fi diff --git a/modules/git/tests/repos/language_stats_repo/hooks/push-to-checkout.sample b/modules/git/tests/repos/language_stats_repo/hooks/push-to-checkout.sample new file mode 100755 index 0000000000000..af5a0c0018b5e --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/hooks/push-to-checkout.sample @@ -0,0 +1,78 @@ +#!/bin/sh + +# An example hook script to update a checked-out tree on a git push. +# +# This hook is invoked by git-receive-pack(1) when it reacts to git +# push and updates reference(s) in its repository, and when the push +# tries to update the branch that is currently checked out and the +# receive.denyCurrentBranch configuration variable is set to +# updateInstead. +# +# By default, such a push is refused if the working tree and the index +# of the remote repository has any difference from the currently +# checked out commit; when both the working tree and the index match +# the current commit, they are updated to match the newly pushed tip +# of the branch. This hook is to be used to override the default +# behaviour; however the code below reimplements the default behaviour +# as a starting point for convenient modification. +# +# The hook receives the commit with which the tip of the current +# branch is going to be updated: +commit=$1 + +# It can exit with a non-zero status to refuse the push (when it does +# so, it must not modify the index or the working tree). +die () { + echo >&2 "$*" + exit 1 +} + +# Or it can make any necessary changes to the working tree and to the +# index to bring them to the desired state when the tip of the current +# branch is updated to the new commit, and exit with a zero status. +# +# For example, the hook can simply run git read-tree -u -m HEAD "$1" +# in order to emulate git fetch that is run in the reverse direction +# with git push, as the two-tree form of git read-tree -u -m is +# essentially the same as git switch or git checkout that switches +# branches while keeping the local changes in the working tree that do +# not interfere with the difference between the branches. + +# The below is a more-or-less exact translation to shell of the C code +# for the default behaviour for git's push-to-checkout hook defined in +# the push_to_deploy() function in builtin/receive-pack.c. +# +# Note that the hook will be executed from the repository directory, +# not from the working tree, so if you want to perform operations on +# the working tree, you will have to adapt your code accordingly, e.g. +# by adding "cd .." or using relative paths. + +if ! git update-index -q --ignore-submodules --refresh +then + die "Up-to-date check failed" +fi + +if ! git diff-files --quiet --ignore-submodules -- +then + die "Working directory has unstaged changes" +fi + +# This is a rough translation of: +# +# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX +if git cat-file -e HEAD 2>/dev/null +then + head=HEAD +else + head=$(git hash-object -t tree --stdin &2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --type=bool hooks.allowunannotated) +allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) +denycreatebranch=$(git config --type=bool hooks.denycreatebranch) +allowdeletetag=$(git config --type=bool hooks.allowdeletetag) +allowmodifytag=$(git config --type=bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero=$(git hash-object --stdin &2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/modules/git/tests/repos/language_stats_repo/index b/modules/git/tests/repos/language_stats_repo/index new file mode 100644 index 0000000000000..e6c02231716df Binary files /dev/null and b/modules/git/tests/repos/language_stats_repo/index differ diff --git a/modules/git/tests/repos/language_stats_repo/info/exclude b/modules/git/tests/repos/language_stats_repo/info/exclude new file mode 100644 index 0000000000000..a5196d1be8fb5 --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/modules/git/tests/repos/language_stats_repo/logs/HEAD b/modules/git/tests/repos/language_stats_repo/logs/HEAD new file mode 100644 index 0000000000000..ce489502bb796 --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 8fee858da5796dfb37704761701bb8e800ad9ef3 Andrew Thornton 1632140318 +0100 commit (initial): Add some test files for GetLanguageStats diff --git a/modules/git/tests/repos/language_stats_repo/logs/refs/heads/master b/modules/git/tests/repos/language_stats_repo/logs/refs/heads/master new file mode 100644 index 0000000000000..ce489502bb796 --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 8fee858da5796dfb37704761701bb8e800ad9ef3 Andrew Thornton 1632140318 +0100 commit (initial): Add some test files for GetLanguageStats diff --git a/modules/git/tests/repos/language_stats_repo/objects/42/25ecfaf6bafbcfa31ea5cbd8121c36d9457085 b/modules/git/tests/repos/language_stats_repo/objects/42/25ecfaf6bafbcfa31ea5cbd8121c36d9457085 new file mode 100644 index 0000000000000..ff3b642734150 Binary files /dev/null and b/modules/git/tests/repos/language_stats_repo/objects/42/25ecfaf6bafbcfa31ea5cbd8121c36d9457085 differ diff --git a/modules/git/tests/repos/language_stats_repo/objects/4a/c803638e4b8995146e329a05e096fa2c77a03d b/modules/git/tests/repos/language_stats_repo/objects/4a/c803638e4b8995146e329a05e096fa2c77a03d new file mode 100644 index 0000000000000..b71abc120c1c2 Binary files /dev/null and b/modules/git/tests/repos/language_stats_repo/objects/4a/c803638e4b8995146e329a05e096fa2c77a03d differ diff --git a/modules/git/tests/repos/language_stats_repo/objects/64/4c37ad7fe64ac012df7e59d27a92e3137c640e b/modules/git/tests/repos/language_stats_repo/objects/64/4c37ad7fe64ac012df7e59d27a92e3137c640e new file mode 100644 index 0000000000000..5c2485d82f9ea Binary files /dev/null and b/modules/git/tests/repos/language_stats_repo/objects/64/4c37ad7fe64ac012df7e59d27a92e3137c640e differ diff --git a/modules/git/tests/repos/language_stats_repo/objects/6c/633a0067b463e459ae952716b17ae36aa30adc b/modules/git/tests/repos/language_stats_repo/objects/6c/633a0067b463e459ae952716b17ae36aa30adc new file mode 100644 index 0000000000000..873cb7187dfe5 Binary files /dev/null and b/modules/git/tests/repos/language_stats_repo/objects/6c/633a0067b463e459ae952716b17ae36aa30adc differ diff --git a/modules/git/tests/repos/language_stats_repo/objects/8e/b563dc106e3dfd3ad0fa81f7a0c5e2604f80cd b/modules/git/tests/repos/language_stats_repo/objects/8e/b563dc106e3dfd3ad0fa81f7a0c5e2604f80cd new file mode 100644 index 0000000000000..f89ecb7d607f5 Binary files /dev/null and b/modules/git/tests/repos/language_stats_repo/objects/8e/b563dc106e3dfd3ad0fa81f7a0c5e2604f80cd differ diff --git a/modules/git/tests/repos/language_stats_repo/objects/8f/ee858da5796dfb37704761701bb8e800ad9ef3 b/modules/git/tests/repos/language_stats_repo/objects/8f/ee858da5796dfb37704761701bb8e800ad9ef3 new file mode 100644 index 0000000000000..0219c2d565cdf Binary files /dev/null and b/modules/git/tests/repos/language_stats_repo/objects/8f/ee858da5796dfb37704761701bb8e800ad9ef3 differ diff --git a/modules/git/tests/repos/language_stats_repo/objects/aa/a21bf84c8b2304608d3fc83b747840f2456299 b/modules/git/tests/repos/language_stats_repo/objects/aa/a21bf84c8b2304608d3fc83b747840f2456299 new file mode 100644 index 0000000000000..adc50f2bce7d1 Binary files /dev/null and b/modules/git/tests/repos/language_stats_repo/objects/aa/a21bf84c8b2304608d3fc83b747840f2456299 differ diff --git a/modules/git/tests/repos/language_stats_repo/objects/da/a5abe3c5f42cae598e362e8a8db6284565d6bb b/modules/git/tests/repos/language_stats_repo/objects/da/a5abe3c5f42cae598e362e8a8db6284565d6bb new file mode 100644 index 0000000000000..9d4d4b1a04fe1 Binary files /dev/null and b/modules/git/tests/repos/language_stats_repo/objects/da/a5abe3c5f42cae598e362e8a8db6284565d6bb differ diff --git a/modules/git/tests/repos/language_stats_repo/refs/heads/master b/modules/git/tests/repos/language_stats_repo/refs/heads/master new file mode 100644 index 0000000000000..679afcc5063b8 --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/refs/heads/master @@ -0,0 +1 @@ +8fee858da5796dfb37704761701bb8e800ad9ef3 diff --git a/modules/gitgraph/graph_models.go b/modules/gitgraph/graph_models.go index ec47f0ad84aca..86bd8cb237bf8 100644 --- a/modules/gitgraph/graph_models.go +++ b/modules/gitgraph/graph_models.go @@ -10,6 +10,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" ) @@ -114,7 +115,7 @@ func (graph *Graph) LoadAndProcessCommits(repository *models.Repository, gitRepo _ = models.CalculateTrustStatus(c.Verification, repository, &keyMap) - statuses, err := models.GetLatestCommitStatus(repository.ID, c.Commit.ID.String(), models.ListOptions{}) + statuses, err := models.GetLatestCommitStatus(repository.ID, c.Commit.ID.String(), db.ListOptions{}) if err != nil { log.Error("GetLatestCommitStatus: %v", err) } else { diff --git a/modules/graceful/server.go b/modules/graceful/server.go index 6b7d4a1a970c1..23eb821c8c94c 100644 --- a/modules/graceful/server.go +++ b/modules/graceful/server.go @@ -7,7 +7,6 @@ package graceful import ( "crypto/tls" - "io/ioutil" "net" "os" "strings" @@ -111,13 +110,13 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string, serve ServeFuncti config.Certificates = make([]tls.Certificate, 1) - certPEMBlock, err := ioutil.ReadFile(certFile) + certPEMBlock, err := os.ReadFile(certFile) if err != nil { log.Error("Failed to load https cert file %s for %s:%s: %v", certFile, srv.network, srv.address, err) return err } - keyPEMBlock, err := ioutil.ReadFile(keyFile) + keyPEMBlock, err := os.ReadFile(keyFile) if err != nil { log.Error("Failed to load https key file %s for %s:%s: %v", keyFile, srv.network, srv.address, err) return err diff --git a/modules/highlight/highlight.go b/modules/highlight/highlight.go index 079f7a44bd111..6684fbe842910 100644 --- a/modules/highlight/highlight.go +++ b/modules/highlight/highlight.go @@ -66,17 +66,6 @@ func Code(fileName, code string) string { if len(code) > sizeLimit { return code } - formatter := html.New(html.WithClasses(true), - html.WithLineNumbers(false), - html.PreventSurroundingPre(true), - ) - if formatter == nil { - log.Error("Couldn't create chroma formatter") - return code - } - - htmlbuf := bytes.Buffer{} - htmlw := bufio.NewWriter(&htmlbuf) var lexer chroma.Lexer if val, ok := highlightMapping[filepath.Ext(fileName)]; ok { @@ -97,6 +86,18 @@ func Code(fileName, code string) string { } cache.Add(fileName, lexer) } + return CodeFromLexer(lexer, code) +} + +// CodeFromLexer returns a HTML version of code string with chroma syntax highlighting classes +func CodeFromLexer(lexer chroma.Lexer, code string) string { + formatter := html.New(html.WithClasses(true), + html.WithLineNumbers(false), + html.PreventSurroundingPre(true), + ) + + htmlbuf := bytes.Buffer{} + htmlw := bufio.NewWriter(&htmlbuf) iterator, err := lexer.Tokenise(nil, string(code)) if err != nil { diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go index f5e3906be65c4..35d4e6dfd82e8 100644 --- a/modules/httpcache/httpcache.go +++ b/modules/httpcache/httpcache.go @@ -16,12 +16,17 @@ import ( "code.gitea.io/gitea/modules/setting" ) -// GetCacheControl returns a suitable "Cache-Control" header value -func GetCacheControl() string { - if !setting.IsProd() { - return "no-store" +// AddCacheControlToHeader adds suitable cache-control headers to response +func AddCacheControlToHeader(h http.Header, d time.Duration) { + if setting.IsProd() { + h.Set("Cache-Control", "private, max-age="+strconv.Itoa(int(d.Seconds()))) + } else { + h.Set("Cache-Control", "no-store") + // to remind users they are using non-prod setting. + // some users may be confused by "Cache-Control: no-store" in their setup if they did wrong to `RUN_MODE` in `app.ini`. + h.Add("X-Gitea-Debug", "RUN_MODE="+setting.RunMode) + h.Add("X-Gitea-Debug", "CacheControl=no-store") } - return "private, max-age=" + strconv.FormatInt(int64(setting.StaticCacheTime.Seconds()), 10) } // generateETag generates an ETag based on size, filename and file modification time @@ -32,7 +37,7 @@ func generateETag(fi os.FileInfo) string { // HandleTimeCache handles time-based caching for a HTTP request func HandleTimeCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (handled bool) { - w.Header().Set("Cache-Control", GetCacheControl()) + AddCacheControlToHeader(w.Header(), setting.StaticCacheTime) ifModifiedSince := req.Header.Get("If-Modified-Since") if ifModifiedSince != "" { @@ -63,7 +68,7 @@ func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag strin return true } } - w.Header().Set("Cache-Control", GetCacheControl()) + AddCacheControlToHeader(w.Header(), setting.StaticCacheTime) return false } diff --git a/modules/httpcache/httpcache_test.go b/modules/httpcache/httpcache_test.go index fe5ca179560ff..68ac892c91e7f 100644 --- a/modules/httpcache/httpcache_test.go +++ b/modules/httpcache/httpcache_test.go @@ -8,6 +8,7 @@ import ( "net/http" "net/http/httptest" "os" + "strings" "testing" "time" @@ -24,6 +25,17 @@ func (m mockFileInfo) ModTime() time.Time { return time.Time{} } func (m mockFileInfo) IsDir() bool { return false } func (m mockFileInfo) Sys() interface{} { return nil } +func countFormalHeaders(h http.Header) (c int) { + for k := range h { + // ignore our headers for internal usage + if strings.HasPrefix(k, "X-Gitea-") { + continue + } + c++ + } + return c +} + func TestHandleFileETagCache(t *testing.T) { fi := mockFileInfo{} etag := `"MTBnaXRlYS50ZXN0TW9uLCAwMSBKYW4gMDAwMSAwMDowMDowMCBHTVQ="` @@ -35,7 +47,7 @@ func TestHandleFileETagCache(t *testing.T) { handled := HandleFileETagCache(req, w, fi) assert.False(t, handled) - assert.Len(t, w.Header(), 2) + assert.Equal(t, 2, countFormalHeaders(w.Header())) assert.Contains(t, w.Header(), "Cache-Control") assert.Contains(t, w.Header(), "Etag") assert.Equal(t, etag, w.Header().Get("Etag")) @@ -49,7 +61,7 @@ func TestHandleFileETagCache(t *testing.T) { handled := HandleFileETagCache(req, w, fi) assert.False(t, handled) - assert.Len(t, w.Header(), 2) + assert.Equal(t, 2, countFormalHeaders(w.Header())) assert.Contains(t, w.Header(), "Cache-Control") assert.Contains(t, w.Header(), "Etag") assert.Equal(t, etag, w.Header().Get("Etag")) @@ -63,7 +75,7 @@ func TestHandleFileETagCache(t *testing.T) { handled := HandleFileETagCache(req, w, fi) assert.True(t, handled) - assert.Len(t, w.Header(), 1) + assert.Equal(t, 1, countFormalHeaders(w.Header())) assert.Contains(t, w.Header(), "Etag") assert.Equal(t, etag, w.Header().Get("Etag")) assert.Equal(t, http.StatusNotModified, w.Code) @@ -80,7 +92,7 @@ func TestHandleGenericETagCache(t *testing.T) { handled := HandleGenericETagCache(req, w, etag) assert.False(t, handled) - assert.Len(t, w.Header(), 2) + assert.Equal(t, 2, countFormalHeaders(w.Header())) assert.Contains(t, w.Header(), "Cache-Control") assert.Contains(t, w.Header(), "Etag") assert.Equal(t, etag, w.Header().Get("Etag")) @@ -94,7 +106,7 @@ func TestHandleGenericETagCache(t *testing.T) { handled := HandleGenericETagCache(req, w, etag) assert.False(t, handled) - assert.Len(t, w.Header(), 2) + assert.Equal(t, 2, countFormalHeaders(w.Header())) assert.Contains(t, w.Header(), "Cache-Control") assert.Contains(t, w.Header(), "Etag") assert.Equal(t, etag, w.Header().Get("Etag")) @@ -108,7 +120,7 @@ func TestHandleGenericETagCache(t *testing.T) { handled := HandleGenericETagCache(req, w, etag) assert.True(t, handled) - assert.Len(t, w.Header(), 1) + assert.Equal(t, 1, countFormalHeaders(w.Header())) assert.Contains(t, w.Header(), "Etag") assert.Equal(t, etag, w.Header().Get("Etag")) assert.Equal(t, http.StatusNotModified, w.Code) @@ -122,7 +134,7 @@ func TestHandleGenericETagCache(t *testing.T) { handled := HandleGenericETagCache(req, w, etag) assert.False(t, handled) - assert.Len(t, w.Header(), 2) + assert.Equal(t, 2, countFormalHeaders(w.Header())) assert.Contains(t, w.Header(), "Cache-Control") assert.Contains(t, w.Header(), "Etag") assert.Equal(t, etag, w.Header().Get("Etag")) @@ -136,7 +148,7 @@ func TestHandleGenericETagCache(t *testing.T) { handled := HandleGenericETagCache(req, w, etag) assert.True(t, handled) - assert.Len(t, w.Header(), 1) + assert.Equal(t, 1, countFormalHeaders(w.Header())) assert.Contains(t, w.Header(), "Etag") assert.Equal(t, etag, w.Header().Get("Etag")) assert.Equal(t, http.StatusNotModified, w.Code) diff --git a/modules/httplib/httplib.go b/modules/httplib/httplib.go index aecdd4346cdbb..ad3b7462e98b7 100644 --- a/modules/httplib/httplib.go +++ b/modules/httplib/httplib.go @@ -11,7 +11,6 @@ import ( "crypto/tls" "encoding/xml" "io" - "io/ioutil" "log" "mime/multipart" "net" @@ -243,11 +242,11 @@ func (r *Request) Body(data interface{}) *Request { switch t := data.(type) { case string: bf := bytes.NewBufferString(t) - r.req.Body = ioutil.NopCloser(bf) + r.req.Body = io.NopCloser(bf) r.req.ContentLength = int64(len(t)) case []byte: bf := bytes.NewBuffer(t) - r.req.Body = ioutil.NopCloser(bf) + r.req.Body = io.NopCloser(bf) r.req.ContentLength = int64(len(t)) } return r @@ -307,7 +306,7 @@ func (r *Request) getResponse() (*http.Response, error) { _ = pw.Close() }() r.Header("Content-Type", bodyWriter.FormDataContentType()) - r.req.Body = ioutil.NopCloser(pr) + r.req.Body = io.NopCloser(pr) } else if len(paramBody) > 0 { r.Header("Content-Type", "application/x-www-form-urlencoded") r.Body(paramBody) @@ -407,7 +406,7 @@ func (r *Request) Bytes() ([]byte, error) { return nil, nil } defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) + data, err := io.ReadAll(resp.Body) if err != nil { return nil, err } diff --git a/modules/indexer/code/bleve.go b/modules/indexer/code/bleve.go index fc5c602dbef08..8e5df34e60be7 100644 --- a/modules/indexer/code/bleve.go +++ b/modules/indexer/code/bleve.go @@ -8,7 +8,6 @@ import ( "bufio" "fmt" "io" - "io/ioutil" "os" "strconv" "strings" @@ -210,7 +209,7 @@ func (b *BleveIndexer) addUpdate(batchWriter git.WriteCloserError, batchReader * return err } - fileContents, err := ioutil.ReadAll(io.LimitReader(batchReader, size)) + fileContents, err := io.ReadAll(io.LimitReader(batchReader, size)) if err != nil { return err } else if !typesniffer.DetectContentType(fileContents).IsText() { diff --git a/modules/indexer/code/bleve_test.go b/modules/indexer/code/bleve_test.go index f79957220ff13..3d97e312f9aa0 100644 --- a/modules/indexer/code/bleve_test.go +++ b/modules/indexer/code/bleve_test.go @@ -5,19 +5,19 @@ package code import ( - "io/ioutil" + "os" "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) func TestBleveIndexAndSearch(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) - dir, err := ioutil.TempDir("", "bleve.index") + dir, err := os.MkdirTemp("", "bleve.index") assert.NoError(t, err) if err != nil { assert.Fail(t, "Unable to create temporary directory") diff --git a/modules/indexer/code/elastic_search.go b/modules/indexer/code/elastic_search.go index a7a243e24e13e..49633c31916df 100644 --- a/modules/indexer/code/elastic_search.go +++ b/modules/indexer/code/elastic_search.go @@ -9,7 +9,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "strconv" "strings" "time" @@ -207,7 +206,7 @@ func (b *ElasticSearchIndexer) addUpdate(batchWriter git.WriteCloserError, batch return nil, err } - fileContents, err := ioutil.ReadAll(io.LimitReader(batchReader, size)) + fileContents, err := io.ReadAll(io.LimitReader(batchReader, size)) if err != nil { return nil, err } else if !typesniffer.DetectContentType(fileContents).IsText() { diff --git a/modules/indexer/code/elastic_search_test.go b/modules/indexer/code/elastic_search_test.go index 7cf62e0c5f4eb..c9d2c297bba4f 100644 --- a/modules/indexer/code/elastic_search_test.go +++ b/modules/indexer/code/elastic_search_test.go @@ -8,13 +8,12 @@ import ( "os" "testing" - "code.gitea.io/gitea/models" - + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestESIndexAndSearch(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) u := os.Getenv("TEST_INDEXER_CODE_ES_URL") if u == "" { diff --git a/modules/indexer/code/indexer.go b/modules/indexer/code/indexer.go index 67fa43eda89dc..46b5905059936 100644 --- a/modules/indexer/code/indexer.go +++ b/modules/indexer/code/indexer.go @@ -12,6 +12,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/queue" @@ -285,7 +286,7 @@ func UpdateRepoIndexer(repo *models.Repository) { func populateRepoIndexer(ctx context.Context) { log.Info("Populating the repo indexer with existing repositories") - exist, err := models.IsTableNotEmpty("repository") + exist, err := db.IsTableNotEmpty("repository") if err != nil { log.Fatal("System error: %v", err) } else if !exist { @@ -295,12 +296,12 @@ func populateRepoIndexer(ctx context.Context) { // if there is any existing repo indexer metadata in the DB, delete it // since we are starting afresh. Also, xorm requires deletes to have a // condition, and we want to delete everything, thus 1=1. - if err := models.DeleteAllRecords("repo_indexer_status"); err != nil { + if err := db.DeleteAllRecords("repo_indexer_status"); err != nil { log.Fatal("System error: %v", err) } var maxRepoID int64 - if maxRepoID, err = models.GetMaxID("repository"); err != nil { + if maxRepoID, err = db.GetMaxID("repository"); err != nil { log.Fatal("System error: %v", err) } diff --git a/modules/indexer/code/indexer_test.go b/modules/indexer/code/indexer_test.go index 01717bd288a75..34930a84caffe 100644 --- a/modules/indexer/code/indexer_test.go +++ b/modules/indexer/code/indexer_test.go @@ -8,13 +8,12 @@ import ( "path/filepath" "testing" - "code.gitea.io/gitea/models" - + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..", "..")) + db.MainTest(m, filepath.Join("..", "..", "..")) } func testIndexer(name string, t *testing.T, indexer Indexer) { diff --git a/modules/indexer/issues/bleve_test.go b/modules/indexer/issues/bleve_test.go index 2a8ed15d820b8..70a9582e1d77e 100644 --- a/modules/indexer/issues/bleve_test.go +++ b/modules/indexer/issues/bleve_test.go @@ -5,7 +5,7 @@ package issues import ( - "io/ioutil" + "os" "testing" "code.gitea.io/gitea/modules/util" @@ -13,7 +13,7 @@ import ( ) func TestBleveIndexAndSearch(t *testing.T) { - dir, err := ioutil.TempDir("", "bleve.index") + dir, err := os.MkdirTemp("", "bleve.index") assert.NoError(t, err) if err != nil { assert.Fail(t, "Unable to create temporary directory") diff --git a/modules/indexer/issues/indexer.go b/modules/indexer/issues/indexer.go index 676b6686ea5b2..4e133b4dd3931 100644 --- a/modules/indexer/issues/indexer.go +++ b/modules/indexer/issues/indexer.go @@ -12,6 +12,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/queue" @@ -241,7 +242,7 @@ func populateIssueIndexer(ctx context.Context) { default: } repos, _, err := models.SearchRepositoryByName(&models.SearchRepoOptions{ - ListOptions: models.ListOptions{Page: page, PageSize: models.RepositoryListDefaultPageSize}, + ListOptions: db.ListOptions{Page: page, PageSize: models.RepositoryListDefaultPageSize}, OrderBy: models.SearchOrderByID, Private: true, Collaborate: util.OptionalBoolFalse, diff --git a/modules/indexer/issues/indexer_test.go b/modules/indexer/issues/indexer_test.go index 8c163f78d15dd..561f357f4da71 100644 --- a/modules/indexer/issues/indexer_test.go +++ b/modules/indexer/issues/indexer_test.go @@ -5,13 +5,13 @@ package issues import ( - "io/ioutil" + "os" "path" "path/filepath" "testing" "time" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -21,14 +21,14 @@ import ( ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..", "..")) + db.MainTest(m, filepath.Join("..", "..", "..")) } func TestBleveSearchIssues(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) setting.Cfg = ini.Empty() - tmpIndexerDir, err := ioutil.TempDir("", "issues-indexer") + tmpIndexerDir, err := os.MkdirTemp("", "issues-indexer") if err != nil { assert.Fail(t, "Unable to create temporary directory: %v", err) return @@ -74,7 +74,7 @@ func TestBleveSearchIssues(t *testing.T) { } func TestDBSearchIssues(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) setting.Indexer.IssueType = "db" InitIssueIndexer(true) diff --git a/modules/indexer/stats/db.go b/modules/indexer/stats/db.go index 976bf2d632d69..87e8677a289fc 100644 --- a/modules/indexer/stats/db.go +++ b/modules/indexer/stats/db.go @@ -38,11 +38,11 @@ func (db *DBIndexer) Index(id int64) error { // Get latest commit for default branch commitID, err := gitRepo.GetBranchCommitID(repo.DefaultBranch) if err != nil { - if git.IsErrBranchNotExist(err) || git.IsErrNotExist((err)) { - log.Debug("Unable to get commit ID for defaultbranch %s in %s ... skipping this repository", repo.DefaultBranch, repo.RepoPath()) + if git.IsErrBranchNotExist(err) || git.IsErrNotExist(err) { + log.Debug("Unable to get commit ID for default branch %s in %s ... skipping this repository", repo.DefaultBranch, repo.RepoPath()) return nil } - log.Error("Unable to get commit ID for defaultbranch %s in %s. Error: %v", repo.DefaultBranch, repo.RepoPath(), err) + log.Error("Unable to get commit ID for default branch %s in %s. Error: %v", repo.DefaultBranch, repo.RepoPath(), err) return err } @@ -54,10 +54,17 @@ func (db *DBIndexer) Index(id int64) error { // Calculate and save language statistics to database stats, err := gitRepo.GetLanguageStats(commitID) if err != nil { - log.Error("Unable to get language stats for ID %s for defaultbranch %s in %s. Error: %v", commitID, repo.DefaultBranch, repo.RepoPath(), err) + log.Error("Unable to get language stats for ID %s for default branch %s in %s. Error: %v", commitID, repo.DefaultBranch, repo.RepoPath(), err) return err } - return repo.UpdateLanguageStats(commitID, stats) + err = repo.UpdateLanguageStats(commitID, stats) + if err != nil { + log.Error("Unable to update language stats for ID %s for default branch %s in %s. Error: %v", commitID, repo.DefaultBranch, repo.RepoPath(), err) + return err + } + + log.Debug("DBIndexer completed language stats for ID %s for default branch %s in %s. stats count: %d", commitID, repo.DefaultBranch, repo.RepoPath(), len(stats)) + return nil } // Close dummy function diff --git a/modules/indexer/stats/indexer.go b/modules/indexer/stats/indexer.go index 4d8a174ff9af4..fe87a2268bac5 100644 --- a/modules/indexer/stats/indexer.go +++ b/modules/indexer/stats/indexer.go @@ -6,6 +6,7 @@ package stats import ( "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" ) @@ -39,7 +40,7 @@ func populateRepoIndexer() { isShutdown := graceful.GetManager().IsShutdown() - exist, err := models.IsTableNotEmpty("repository") + exist, err := db.IsTableNotEmpty("repository") if err != nil { log.Fatal("System error: %v", err) } else if !exist { @@ -47,7 +48,7 @@ func populateRepoIndexer() { } var maxRepoID int64 - if maxRepoID, err = models.GetMaxID("repository"); err != nil { + if maxRepoID, err = db.GetMaxID("repository"); err != nil { log.Fatal("System error: %v", err) } diff --git a/modules/indexer/stats/indexer_test.go b/modules/indexer/stats/indexer_test.go index 4bcbaa9423772..a4645b2083d9a 100644 --- a/modules/indexer/stats/indexer_test.go +++ b/modules/indexer/stats/indexer_test.go @@ -10,6 +10,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "gopkg.in/ini.v1" @@ -18,11 +19,11 @@ import ( ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..", "..")) + db.MainTest(m, filepath.Join("..", "..", "..")) } func TestRepoStatsIndex(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) setting.Cfg = ini.Empty() setting.NewQueueService() diff --git a/modules/lfs/http_client_test.go b/modules/lfs/http_client_test.go index 589773e45bf9c..5b514a1230d7b 100644 --- a/modules/lfs/http_client_test.go +++ b/modules/lfs/http_client_test.go @@ -8,7 +8,6 @@ import ( "bytes" "context" "io" - "io/ioutil" "net/http" "strings" "testing" @@ -32,7 +31,7 @@ func (a *DummyTransferAdapter) Name() string { } func (a *DummyTransferAdapter) Download(ctx context.Context, l *Link) (io.ReadCloser, error) { - return ioutil.NopCloser(bytes.NewBufferString("dummy")), nil + return io.NopCloser(bytes.NewBufferString("dummy")), nil } func (a *DummyTransferAdapter) Upload(ctx context.Context, l *Link, p Pointer, r io.Reader) error { @@ -50,7 +49,7 @@ func lfsTestRoundtripHandler(req *http.Request) *http.Response { if strings.Contains(url, "status-not-ok") { return &http.Response{StatusCode: http.StatusBadRequest} } else if strings.Contains(url, "invalid-json-response") { - return &http.Response{StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewBufferString("invalid json"))} + return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewBufferString("invalid json"))} } else if strings.Contains(url, "valid-batch-request-download") { batchResponse = &BatchResponse{ Transfer: "dummy", @@ -149,7 +148,7 @@ func lfsTestRoundtripHandler(req *http.Request) *http.Response { payload := new(bytes.Buffer) json.NewEncoder(payload).Encode(batchResponse) - return &http.Response{StatusCode: http.StatusOK, Body: ioutil.NopCloser(payload)} + return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(payload)} } func TestHTTPClientDownload(t *testing.T) { @@ -350,7 +349,7 @@ func TestHTTPClientUpload(t *testing.T) { client.transfers["dummy"] = dummy err := client.Upload(context.Background(), []Pointer{p}, func(p Pointer, objectError error) (io.ReadCloser, error) { - return ioutil.NopCloser(new(bytes.Buffer)), objectError + return io.NopCloser(new(bytes.Buffer)), objectError }) if len(c.expectederror) > 0 { assert.True(t, strings.Contains(err.Error(), c.expectederror), "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) diff --git a/modules/lfs/transferadapter_test.go b/modules/lfs/transferadapter_test.go index 9192b486ed1f9..b26d516c683dd 100644 --- a/modules/lfs/transferadapter_test.go +++ b/modules/lfs/transferadapter_test.go @@ -8,7 +8,6 @@ import ( "bytes" "context" "io" - "io/ioutil" "net/http" "strings" "testing" @@ -35,7 +34,7 @@ func TestBasicTransferAdapter(t *testing.T) { if strings.Contains(url, "download-request") { assert.Equal(t, "GET", req.Method) - return &http.Response{StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewBufferString("dummy"))} + return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewBufferString("dummy"))} } else if strings.Contains(url, "upload-request") { assert.Equal(t, "PUT", req.Method) assert.Equal(t, "application/octet-stream", req.Header.Get("Content-Type")) @@ -63,7 +62,7 @@ func TestBasicTransferAdapter(t *testing.T) { payload := new(bytes.Buffer) json.NewEncoder(payload).Encode(er) - return &http.Response{StatusCode: http.StatusNotFound, Body: ioutil.NopCloser(payload)} + return &http.Response{StatusCode: http.StatusNotFound, Body: io.NopCloser(payload)} } else { t.Errorf("Unknown test case: %s", url) return nil diff --git a/modules/log/conn_test.go b/modules/log/conn_test.go index dc5de732f4117..158a8ca6cee6a 100644 --- a/modules/log/conn_test.go +++ b/modules/log/conn_test.go @@ -6,7 +6,7 @@ package log import ( "fmt" - "io/ioutil" + "io" "net" "strings" "sync" @@ -20,7 +20,7 @@ func listenReadAndClose(t *testing.T, l net.Listener, expected string) { conn, err := l.Accept() assert.NoError(t, err) defer conn.Close() - written, err := ioutil.ReadAll(conn) + written, err := io.ReadAll(conn) assert.NoError(t, err) assert.Equal(t, expected, string(written)) diff --git a/modules/log/file_test.go b/modules/log/file_test.go index 7bc5f90037a76..39d2467a1ff8a 100644 --- a/modules/log/file_test.go +++ b/modules/log/file_test.go @@ -7,7 +7,7 @@ package log import ( "compress/gzip" "fmt" - "io/ioutil" + "io" "os" "path/filepath" "strings" @@ -19,7 +19,7 @@ import ( ) func TestFileLoggerFails(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "TestFileLogger") + tmpDir, err := os.MkdirTemp("", "TestFileLogger") assert.NoError(t, err) defer util.RemoveAll(tmpDir) @@ -47,7 +47,7 @@ func TestFileLoggerFails(t *testing.T) { } func TestFileLogger(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "TestFileLogger") + tmpDir, err := os.MkdirTemp("", "TestFileLogger") assert.NoError(t, err) defer util.RemoveAll(tmpDir) @@ -85,21 +85,21 @@ func TestFileLogger(t *testing.T) { fileLogger.LogEvent(&event) fileLogger.Flush() - logData, err := ioutil.ReadFile(filename) + logData, err := os.ReadFile(filename) assert.NoError(t, err) assert.Equal(t, expected, string(logData)) event.level = DEBUG fileLogger.LogEvent(&event) fileLogger.Flush() - logData, err = ioutil.ReadFile(filename) + logData, err = os.ReadFile(filename) assert.NoError(t, err) assert.Equal(t, expected, string(logData)) event.level = TRACE fileLogger.LogEvent(&event) fileLogger.Flush() - logData, err = ioutil.ReadFile(filename) + logData, err = os.ReadFile(filename) assert.NoError(t, err) assert.Equal(t, expected, string(logData)) @@ -107,18 +107,18 @@ func TestFileLogger(t *testing.T) { expected += fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) fileLogger.LogEvent(&event) fileLogger.Flush() - logData, err = ioutil.ReadFile(filename) + logData, err = os.ReadFile(filename) assert.NoError(t, err) assert.Equal(t, expected, string(logData)) // Should rotate fileLogger.LogEvent(&event) fileLogger.Flush() - logData, err = ioutil.ReadFile(filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1)) + logData, err = os.ReadFile(filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1)) assert.NoError(t, err) assert.Equal(t, expected, string(logData)) - logData, err = ioutil.ReadFile(filename) + logData, err = os.ReadFile(filename) assert.NoError(t, err) expected = fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) assert.Equal(t, expected, string(logData)) @@ -134,7 +134,7 @@ func TestFileLogger(t *testing.T) { expected += fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) fileLogger.LogEvent(&event) fileLogger.Flush() - logData, err = ioutil.ReadFile(filename) + logData, err = os.ReadFile(filename) assert.NoError(t, err) assert.Equal(t, expected, string(logData)) @@ -142,7 +142,7 @@ func TestFileLogger(t *testing.T) { expected += fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) fileLogger.LogEvent(&event) fileLogger.Flush() - logData, err = ioutil.ReadFile(filename) + logData, err = os.ReadFile(filename) assert.NoError(t, err) assert.Equal(t, expected, string(logData)) @@ -150,7 +150,7 @@ func TestFileLogger(t *testing.T) { } func TestCompressFileLogger(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "TestFileLogger") + tmpDir, err := os.MkdirTemp("", "TestFileLogger") assert.NoError(t, err) defer util.RemoveAll(tmpDir) @@ -184,7 +184,7 @@ func TestCompressFileLogger(t *testing.T) { fileLogger.LogEvent(&event) fileLogger.Flush() - logData, err := ioutil.ReadFile(filename) + logData, err := os.ReadFile(filename) assert.NoError(t, err) assert.Equal(t, expected, string(logData)) @@ -192,7 +192,7 @@ func TestCompressFileLogger(t *testing.T) { expected += fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) fileLogger.LogEvent(&event) fileLogger.Flush() - logData, err = ioutil.ReadFile(filename) + logData, err = os.ReadFile(filename) assert.NoError(t, err) assert.Equal(t, expected, string(logData)) @@ -210,7 +210,7 @@ func TestCompressFileLogger(t *testing.T) { } func TestCompressOldFile(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "TestFileLogger") + tmpDir, err := os.MkdirTemp("", "TestFileLogger") assert.NoError(t, err) defer util.RemoveAll(tmpDir) fname := filepath.Join(tmpDir, "test") @@ -238,9 +238,9 @@ func TestCompressOldFile(t *testing.T) { assert.NoError(t, err) zr, err := gzip.NewReader(f) assert.NoError(t, err) - data, err := ioutil.ReadAll(zr) + data, err := io.ReadAll(zr) assert.NoError(t, err) - original, err := ioutil.ReadFile(nonGzip) + original, err := os.ReadFile(nonGzip) assert.NoError(t, err) assert.Equal(t, original, data) } diff --git a/modules/log/stack.go b/modules/log/stack.go index 568c10cd00d02..8fc3f35421fed 100644 --- a/modules/log/stack.go +++ b/modules/log/stack.go @@ -7,7 +7,7 @@ package log import ( "bytes" "fmt" - "io/ioutil" + "os" "runtime" ) @@ -38,7 +38,7 @@ func Stack(skip int) string { fmt.Fprintf(buf, "%s:%d (0x%x)\n", filename, lineNumber, programCounter) // Now try to print the offending line if filename != lastFilename { - data, err := ioutil.ReadFile(filename) + data, err := os.ReadFile(filename) if err != nil { // can't read this sourcefile // likely we don't have the sourcecode available diff --git a/modules/markup/csv/csv.go b/modules/markup/csv/csv.go index 8a4df8951154a..e4b423d4f00b8 100644 --- a/modules/markup/csv/csv.go +++ b/modules/markup/csv/csv.go @@ -9,7 +9,6 @@ import ( "bytes" "html" "io" - "io/ioutil" "regexp" "strconv" @@ -87,7 +86,7 @@ func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Wri var tmpBlock = bufio.NewWriter(output) // FIXME: don't read all to memory - rawBytes, err := ioutil.ReadAll(input) + rawBytes, err := io.ReadAll(input) if err != nil { return err } diff --git a/modules/markup/external/external.go b/modules/markup/external/external.go index f7be06dbe91d3..36cbd69f92c98 100644 --- a/modules/markup/external/external.go +++ b/modules/markup/external/external.go @@ -8,7 +8,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "os" "os/exec" "runtime" @@ -75,7 +74,7 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io. if p.IsInputFile { // write to temp file - f, err := ioutil.TempFile("", "gitea_input") + f, err := os.CreateTemp("", "gitea_input") if err != nil { return fmt.Errorf("%s create temp file when rendering %s failed: %v", p.Name(), p.Command, err) } diff --git a/modules/markup/html.go b/modules/markup/html.go index f279e23bff2a1..d5ae237b5b526 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -7,7 +7,6 @@ package markup import ( "bytes" "io" - "io/ioutil" "net/url" "path" "path/filepath" @@ -290,7 +289,7 @@ var nulCleaner = strings.NewReplacer("\000", "") func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output io.Writer) error { defer ctx.Cancel() // FIXME: don't read all content to memory - rawHTML, err := ioutil.ReadAll(input) + rawHTML, err := io.ReadAll(input) if err != nil { return err } @@ -830,7 +829,7 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { reftext := node.Data[ref.RefLocation.Start:ref.RefLocation.End] if exttrack && !ref.IsPull { ctx.Metas["index"] = ref.Issue - link = createLink(com.Expand(ctx.Metas["format"], ctx.Metas), reftext, "ref-issue") + link = createLink(com.Expand(ctx.Metas["format"], ctx.Metas), reftext, "ref-issue ref-external-issue") } else { // Path determines the type of link that will be rendered. It's unknown at this point whether // the linked item is actually a PR or an issue. Luckily it's of no real consequence because diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index 330750a47a7b0..dbad350de2c56 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -96,12 +96,14 @@ func TestRender_IssueIndexPattern2(t *testing.T) { // numeric: render inputs with valid mentions test := func(s, expectedFmt, marker string, indices ...int) { var path, prefix string + isExternal := false if marker == "!" { path = "pulls" prefix = "http://localhost:3000/someUser/someRepo/pulls/" } else { path = "issues" prefix = "https://someurl.com/someUser/someRepo/" + isExternal = true } links := make([]interface{}, len(indices)) @@ -111,8 +113,13 @@ func TestRender_IssueIndexPattern2(t *testing.T) { expectedNil := fmt.Sprintf(expectedFmt, links...) testRenderIssueIndexPattern(t, s, expectedNil, &RenderContext{Metas: localMetas}) + class := "ref-issue" + if isExternal { + class += " ref-external-issue" + } + for i, index := range indices { - links[i] = numericIssueLink(prefix, "ref-issue", index, marker) + links[i] = numericIssueLink(prefix, class, index, marker) } expectedNum := fmt.Sprintf(expectedFmt, links...) testRenderIssueIndexPattern(t, s, expectedNum, &RenderContext{Metas: numericMetas}) @@ -178,7 +185,7 @@ func TestRender_IssueIndexPattern4(t *testing.T) { test := func(s, expectedFmt string, names ...string) { links := make([]interface{}, len(names)) for i, name := range names { - links[i] = alphanumIssueLink("https://someurl.com/someUser/someRepo/", "ref-issue", name) + links[i] = alphanumIssueLink("https://someurl.com/someUser/someRepo/", "ref-issue ref-external-issue", name) } expected := fmt.Sprintf(expectedFmt, links...) testRenderIssueIndexPattern(t, s, expected, &RenderContext{Metas: alphanumericMetas}) diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index ab026dd1b8503..554ee0d4be8cf 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -8,7 +8,6 @@ package markdown import ( "fmt" "io" - "io/ioutil" "strings" "sync" @@ -189,7 +188,7 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer) // FIXME: Don't read all to memory, but goldmark doesn't support pc := newParserContext(ctx) - buf, err := ioutil.ReadAll(input) + buf, err := io.ReadAll(input) if err != nil { log.Error("Unable to ReadAll: %v", err) return diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go index 7e9f1f45c5a7a..b035e04a1fcca 100644 --- a/modules/markup/orgmode/orgmode.go +++ b/modules/markup/orgmode/orgmode.go @@ -12,6 +12,7 @@ import ( "strings" "code.gitea.io/gitea/modules/highlight" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -51,6 +52,12 @@ func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule { func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { htmlWriter := org.NewHTMLWriter() htmlWriter.HighlightCodeBlock = func(source, lang string, inline bool) string { + defer func() { + if err := recover(); err != nil { + log.Error("Panic in HighlightCodeBlock: %v\n%s", err, log.Stack(2)) + panic(err) + } + }() var w strings.Builder if _, err := w.WriteString(`
`); err != nil {
 			return ""
@@ -80,7 +87,7 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
 			}
 			lexer = chroma.Coalesce(lexer)
 
-			if _, err := w.WriteString(highlight.Code(lexer.Config().Filenames[0], source)); err != nil {
+			if _, err := w.WriteString(highlight.CodeFromLexer(lexer, source)); err != nil {
 				return ""
 			}
 		}
diff --git a/modules/markup/orgmode/orgmode_test.go b/modules/markup/orgmode/orgmode_test.go
index da89326e9e13e..81d0d66a76c5b 100644
--- a/modules/markup/orgmode/orgmode_test.go
+++ b/modules/markup/orgmode/orgmode_test.go
@@ -57,3 +57,29 @@ func TestRender_Images(t *testing.T) {
 	test("[[file:"+url+"]]",
 		"

\""+result+"\"

") } + +func TestRender_Source(t *testing.T) { + setting.AppURL = AppURL + setting.AppSubURL = AppSubURL + + test := func(input, expected string) { + buffer, err := RenderString(&markup.RenderContext{ + URLPrefix: setting.AppSubURL, + }, input) + assert.NoError(t, err) + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) + } + + test(`#+begin_src go +// HelloWorld prints "Hello World" +func HelloWorld() { + fmt.Println("Hello World") +} +#+end_src +`, `
+
// HelloWorld prints "Hello World"
+func HelloWorld() {
+	fmt.Println("Hello World")
+}
+
`) +} diff --git a/modules/markup/sanitizer.go b/modules/markup/sanitizer.go index 9342d65de581d..c8f9de33b5fb7 100644 --- a/modules/markup/sanitizer.go +++ b/modules/markup/sanitizer.go @@ -66,7 +66,7 @@ func createDefaultPolicy() *bluemonday.Policy { } // Allow classes for anchors - policy.AllowAttrs("class").Matching(regexp.MustCompile(`ref-issue`)).OnElements("a") + policy.AllowAttrs("class").Matching(regexp.MustCompile(`ref-issue( ref-external-issue)?`)).OnElements("a") // Allow classes for task lists policy.AllowAttrs("class").Matching(regexp.MustCompile(`task-list-item`)).OnElements("li") diff --git a/modules/metrics/collector.go b/modules/metrics/collector.go index f906b58f7da82..527202e0a6d6f 100755 --- a/modules/metrics/collector.go +++ b/modules/metrics/collector.go @@ -15,34 +15,39 @@ const namespace = "gitea_" // Collector implements the prometheus.Collector interface and // exposes gitea metrics for prometheus type Collector struct { - Accesses *prometheus.Desc - Actions *prometheus.Desc - Attachments *prometheus.Desc - Comments *prometheus.Desc - Follows *prometheus.Desc - HookTasks *prometheus.Desc - Issues *prometheus.Desc - IssuesOpen *prometheus.Desc - IssuesClosed *prometheus.Desc - Labels *prometheus.Desc - LoginSources *prometheus.Desc - Milestones *prometheus.Desc - Mirrors *prometheus.Desc - Oauths *prometheus.Desc - Organizations *prometheus.Desc - PublicKeys *prometheus.Desc - Releases *prometheus.Desc - Repositories *prometheus.Desc - Stars *prometheus.Desc - Teams *prometheus.Desc - UpdateTasks *prometheus.Desc - Users *prometheus.Desc - Watches *prometheus.Desc - Webhooks *prometheus.Desc + Accesses *prometheus.Desc + Actions *prometheus.Desc + Attachments *prometheus.Desc + Comments *prometheus.Desc + Follows *prometheus.Desc + HookTasks *prometheus.Desc + Issues *prometheus.Desc + IssuesOpen *prometheus.Desc + IssuesClosed *prometheus.Desc + IssuesByLabel *prometheus.Desc + IssuesByRepository *prometheus.Desc + Labels *prometheus.Desc + LoginSources *prometheus.Desc + Milestones *prometheus.Desc + Mirrors *prometheus.Desc + Oauths *prometheus.Desc + Organizations *prometheus.Desc + Projects *prometheus.Desc + ProjectBoards *prometheus.Desc + PublicKeys *prometheus.Desc + Releases *prometheus.Desc + Repositories *prometheus.Desc + Stars *prometheus.Desc + Teams *prometheus.Desc + UpdateTasks *prometheus.Desc + Users *prometheus.Desc + Watches *prometheus.Desc + Webhooks *prometheus.Desc } // NewCollector returns a new Collector with all prometheus.Desc initialized func NewCollector() Collector { + return Collector{ Accesses: prometheus.NewDesc( namespace+"accesses", @@ -79,6 +84,16 @@ func NewCollector() Collector { "Number of Issues", nil, nil, ), + IssuesByLabel: prometheus.NewDesc( + namespace+"issues_by_label", + "Number of Issues", + []string{"label"}, nil, + ), + IssuesByRepository: prometheus.NewDesc( + namespace+"issues_by_repository", + "Number of Issues", + []string{"repository"}, nil, + ), IssuesOpen: prometheus.NewDesc( namespace+"issues_open", "Number of open Issues", @@ -119,6 +134,16 @@ func NewCollector() Collector { "Number of Organizations", nil, nil, ), + Projects: prometheus.NewDesc( + namespace+"projects", + "Number of projects", + nil, nil, + ), + ProjectBoards: prometheus.NewDesc( + namespace+"projects_boards", + "Number of project boards", + nil, nil, + ), PublicKeys: prometheus.NewDesc( namespace+"publickeys", "Number of PublicKeys", @@ -165,7 +190,6 @@ func NewCollector() Collector { nil, nil, ), } - } // Describe returns all possible prometheus.Desc @@ -177,6 +201,8 @@ func (c Collector) Describe(ch chan<- *prometheus.Desc) { ch <- c.Follows ch <- c.HookTasks ch <- c.Issues + ch <- c.IssuesByLabel + ch <- c.IssuesByRepository ch <- c.IssuesOpen ch <- c.IssuesClosed ch <- c.Labels @@ -185,6 +211,8 @@ func (c Collector) Describe(ch chan<- *prometheus.Desc) { ch <- c.Mirrors ch <- c.Oauths ch <- c.Organizations + ch <- c.Projects + ch <- c.ProjectBoards ch <- c.PublicKeys ch <- c.Releases ch <- c.Repositories @@ -235,6 +263,22 @@ func (c Collector) Collect(ch chan<- prometheus.Metric) { prometheus.GaugeValue, float64(stats.Counter.Issue), ) + for _, il := range stats.Counter.IssueByLabel { + ch <- prometheus.MustNewConstMetric( + c.IssuesByLabel, + prometheus.GaugeValue, + float64(il.Count), + il.Label, + ) + } + for _, ir := range stats.Counter.IssueByRepository { + ch <- prometheus.MustNewConstMetric( + c.IssuesByRepository, + prometheus.GaugeValue, + float64(ir.Count), + ir.OwnerName+"/"+ir.Repository, + ) + } ch <- prometheus.MustNewConstMetric( c.IssuesClosed, prometheus.GaugeValue, @@ -275,6 +319,16 @@ func (c Collector) Collect(ch chan<- prometheus.Metric) { prometheus.GaugeValue, float64(stats.Counter.Org), ) + ch <- prometheus.MustNewConstMetric( + c.Projects, + prometheus.GaugeValue, + float64(stats.Counter.Project), + ) + ch <- prometheus.MustNewConstMetric( + c.ProjectBoards, + prometheus.GaugeValue, + float64(stats.Counter.ProjectBoard), + ) ch <- prometheus.MustNewConstMetric( c.PublicKeys, prometheus.GaugeValue, diff --git a/modules/migrations/base/pullrequest.go b/modules/migrations/base/pullrequest.go index 84b302d18fada..b51a14e47c52f 100644 --- a/modules/migrations/base/pullrequest.go +++ b/modules/migrations/base/pullrequest.go @@ -41,6 +41,11 @@ func (p *PullRequest) IsForkPullRequest() bool { return p.Head.RepoPath() != p.Base.RepoPath() } +// GetGitRefName returns pull request relative path to head +func (p PullRequest) GetGitRefName() string { + return fmt.Sprintf("refs/pull/%d/head", p.Number) +} + // PullRequestBranch represents a pull request branch type PullRequestBranch struct { CloneURL string `yaml:"clone_url"` diff --git a/modules/migrations/gitea_uploader.go b/modules/migrations/gitea_uploader.go index f8fb52fa024d6..62e8924e5447e 100644 --- a/modules/migrations/gitea_uploader.go +++ b/modules/migrations/gitea_uploader.go @@ -16,6 +16,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/migrations/base" @@ -69,17 +70,17 @@ func NewGiteaLocalUploader(ctx context.Context, doer *models.User, repoOwner, re func (g *GiteaLocalUploader) MaxBatchInsertSize(tp string) int { switch tp { case "issue": - return models.MaxBatchInsertSize(new(models.Issue)) + return db.MaxBatchInsertSize(new(models.Issue)) case "comment": - return models.MaxBatchInsertSize(new(models.Comment)) + return db.MaxBatchInsertSize(new(models.Comment)) case "milestone": - return models.MaxBatchInsertSize(new(models.Milestone)) + return db.MaxBatchInsertSize(new(models.Milestone)) case "label": - return models.MaxBatchInsertSize(new(models.Label)) + return db.MaxBatchInsertSize(new(models.Label)) case "release": - return models.MaxBatchInsertSize(new(models.Release)) + return db.MaxBatchInsertSize(new(models.Release)) case "pullrequest": - return models.MaxBatchInsertSize(new(models.PullRequest)) + return db.MaxBatchInsertSize(new(models.PullRequest)) } return 10 } @@ -689,6 +690,23 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR } } else { head = pr.Head.Ref + // Ensure the closed PR SHA still points to an existing ref + _, err = git.NewCommand("rev-list", "--quiet", "-1", pr.Head.SHA).RunInDir(g.repo.RepoPath()) + if err != nil { + if pr.Head.SHA != "" { + // Git update-ref remove bad references with a relative path + log.Warn("Deprecated local head, removing : %v", pr.Head.SHA) + relPath := pr.GetGitRefName() + _, err = git.NewCommand("update-ref", "--no-deref", "-d", relPath).RunInDir(g.repo.RepoPath()) + } else { + // The SHA is empty, remove the head file + log.Warn("Empty reference, removing : %v", pullHead) + err = os.Remove(filepath.Join(pullHead, "head")) + } + if err != nil { + log.Error("Cannot remove local head ref, %v", err) + } + } } if pr.Created.IsZero() { diff --git a/modules/migrations/gitea_uploader_test.go b/modules/migrations/gitea_uploader_test.go index 032393032b5ee..b8b947961f4b7 100644 --- a/modules/migrations/gitea_uploader_test.go +++ b/modules/migrations/gitea_uploader_test.go @@ -11,6 +11,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/migrations/base" "code.gitea.io/gitea/modules/structs" @@ -23,9 +24,9 @@ func TestGiteaUploadRepo(t *testing.T) { // FIXME: Since no accesskey or user/password will trigger rate limit of github, just skip t.Skip() - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) var ( downloader = NewGithubDownloaderV3(context.Background(), "https://github.com", "", "", "", "go-xorm", "builder") @@ -50,7 +51,7 @@ func TestGiteaUploadRepo(t *testing.T) { }, nil) assert.NoError(t, err) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{OwnerID: user.ID, Name: repoName}).(*models.Repository) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{OwnerID: user.ID, Name: repoName}).(*models.Repository) assert.True(t, repo.HasWiki()) assert.EqualValues(t, models.RepositoryReady, repo.Status) @@ -68,12 +69,12 @@ func TestGiteaUploadRepo(t *testing.T) { assert.NoError(t, err) assert.Empty(t, milestones) - labels, err := models.GetLabelsByRepoID(repo.ID, "", models.ListOptions{}) + labels, err := models.GetLabelsByRepoID(repo.ID, "", db.ListOptions{}) assert.NoError(t, err) assert.Len(t, labels, 12) releases, err := models.GetReleasesByRepoID(repo.ID, models.FindReleasesOptions{ - ListOptions: models.ListOptions{ + ListOptions: db.ListOptions{ PageSize: 10, Page: 0, }, @@ -83,7 +84,7 @@ func TestGiteaUploadRepo(t *testing.T) { assert.Len(t, releases, 8) releases, err = models.GetReleasesByRepoID(repo.ID, models.FindReleasesOptions{ - ListOptions: models.ListOptions{ + ListOptions: db.ListOptions{ PageSize: 10, Page: 0, }, diff --git a/modules/migrations/github.go b/modules/migrations/github.go index 54af10d116aa9..97e1b672accdc 100644 --- a/modules/migrations/github.go +++ b/modules/migrations/github.go @@ -521,6 +521,9 @@ func (g *GithubDownloaderV3) GetAllComments(page, perPage int) ([]*base.Comment, created = "created" asc = "asc" ) + if perPage > g.maxPerPage { + perPage = g.maxPerPage + } opt := &github.IssueListCommentsOptions{ Sort: &created, Direction: &asc, @@ -535,7 +538,9 @@ func (g *GithubDownloaderV3) GetAllComments(page, perPage int) ([]*base.Comment, if err != nil { return nil, false, fmt.Errorf("error while listing repos: %v", err) } - log.Trace("Request get comments %d/%d, but in fact get %d", perPage, page, len(comments)) + var isEnd = resp.NextPage == 0 + + log.Trace("Request get comments %d/%d, but in fact get %d, next page is %d", perPage, page, len(comments), resp.NextPage) g.rate = &resp.Rate for _, comment := range comments { // get reactions @@ -575,7 +580,7 @@ func (g *GithubDownloaderV3) GetAllComments(page, perPage int) ([]*base.Comment, }) } - return allComments, len(allComments) < perPage, nil + return allComments, isEnd, nil } // GetPullRequests returns pull requests according page and perPage diff --git a/modules/migrations/main_test.go b/modules/migrations/main_test.go index 86aee4e86bc16..5b29230659aa7 100644 --- a/modules/migrations/main_test.go +++ b/modules/migrations/main_test.go @@ -10,14 +10,14 @@ import ( "testing" "time" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/migrations/base" "github.com/stretchr/testify/assert" ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..")) + db.MainTest(m, filepath.Join("..", "..")) } func timePtr(t time.Time) *time.Time { diff --git a/modules/migrations/migrate_test.go b/modules/migrations/migrate_test.go index 98ee2dfc4a3ec..c050a9abc0869 100644 --- a/modules/migrations/migrate_test.go +++ b/modules/migrations/migrate_test.go @@ -9,16 +9,17 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" ) func TestMigrateWhiteBlocklist(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - adminUser := models.AssertExistsAndLoadBean(t, &models.User{Name: "user1"}).(*models.User) - nonAdminUser := models.AssertExistsAndLoadBean(t, &models.User{Name: "user2"}).(*models.User) + adminUser := db.AssertExistsAndLoadBean(t, &models.User{Name: "user1"}).(*models.User) + nonAdminUser := db.AssertExistsAndLoadBean(t, &models.User{Name: "user2"}).(*models.User) setting.Migrations.AllowedDomains = []string{"github.com"} assert.NoError(t, Init()) diff --git a/modules/migrations/restore.go b/modules/migrations/restore.go index 6287d601c2a0a..5fddf7b50579d 100644 --- a/modules/migrations/restore.go +++ b/modules/migrations/restore.go @@ -7,7 +7,6 @@ package migrations import ( "context" "fmt" - "io/ioutil" "os" "path/filepath" "strconv" @@ -55,7 +54,7 @@ func (r *RepositoryRestorer) SetContext(ctx context.Context) { func (r *RepositoryRestorer) getRepoOptions() (map[string]string, error) { p := filepath.Join(r.baseDir, "repo.yml") - bs, err := ioutil.ReadFile(p) + bs, err := os.ReadFile(p) if err != nil { return nil, err } @@ -96,7 +95,7 @@ func (r *RepositoryRestorer) GetTopics() ([]string, error) { Topics []string `yaml:"topics"` }{} - bs, err := ioutil.ReadFile(p) + bs, err := os.ReadFile(p) if err != nil { return nil, err } @@ -120,7 +119,7 @@ func (r *RepositoryRestorer) GetMilestones() ([]*base.Milestone, error) { return nil, err } - bs, err := ioutil.ReadFile(p) + bs, err := os.ReadFile(p) if err != nil { return nil, err } @@ -144,7 +143,7 @@ func (r *RepositoryRestorer) GetReleases() ([]*base.Release, error) { return nil, err } - bs, err := ioutil.ReadFile(p) + bs, err := os.ReadFile(p) if err != nil { return nil, err } @@ -175,7 +174,7 @@ func (r *RepositoryRestorer) GetLabels() ([]*base.Label, error) { return nil, err } - bs, err := ioutil.ReadFile(p) + bs, err := os.ReadFile(p) if err != nil { return nil, err } @@ -199,7 +198,7 @@ func (r *RepositoryRestorer) GetIssues(page, perPage int) ([]*base.Issue, bool, return nil, false, err } - bs, err := ioutil.ReadFile(p) + bs, err := os.ReadFile(p) if err != nil { return nil, false, err } @@ -226,7 +225,7 @@ func (r *RepositoryRestorer) GetComments(opts base.GetCommentOptions) ([]*base.C return nil, false, err } - bs, err := ioutil.ReadFile(p) + bs, err := os.ReadFile(p) if err != nil { return nil, false, err } @@ -250,7 +249,7 @@ func (r *RepositoryRestorer) GetPullRequests(page, perPage int) ([]*base.PullReq return nil, false, err } - bs, err := ioutil.ReadFile(p) + bs, err := os.ReadFile(p) if err != nil { return nil, false, err } @@ -278,7 +277,7 @@ func (r *RepositoryRestorer) GetReviews(context base.IssueContext) ([]*base.Revi return nil, err } - bs, err := ioutil.ReadFile(p) + bs, err := os.ReadFile(p) if err != nil { return nil, err } diff --git a/modules/notification/action/action_test.go b/modules/notification/action/action_test.go index cdfe4bd663d83..705a4fb2f49ad 100644 --- a/modules/notification/action/action_test.go +++ b/modules/notification/action/action_test.go @@ -10,18 +10,19 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..", "..")) + db.MainTest(m, filepath.Join("..", "..", "..")) } func TestRenameRepoAction(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{OwnerID: user.ID}).(*models.Repository) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{OwnerID: user.ID}).(*models.Repository) repo.Owner = user oldRepoName := repo.Name @@ -38,10 +39,10 @@ func TestRenameRepoAction(t *testing.T) { IsPrivate: repo.IsPrivate, Content: oldRepoName, } - models.AssertNotExistsBean(t, actionBean) + db.AssertNotExistsBean(t, actionBean) NewNotifier().NotifyRenameRepository(user, repo, oldRepoName) - models.AssertExistsAndLoadBean(t, actionBean) + db.AssertExistsAndLoadBean(t, actionBean) models.CheckConsistencyFor(t, &models.Action{}) } diff --git a/modules/notification/webhook/webhook.go b/modules/notification/webhook/webhook.go index acdb91efe373e..c767acf134bb2 100644 --- a/modules/notification/webhook/webhook.go +++ b/modules/notification/webhook/webhook.go @@ -51,7 +51,7 @@ func (m *webhookNotifier) NotifyIssueClearLabels(doer *models.User, issue *model err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventPullRequestLabel, &api.PullRequestPayload{ Action: api.HookIssueLabelCleared, Index: issue.Index, - PullRequest: convert.ToAPIPullRequest(issue.PullRequest), + PullRequest: convert.ToAPIPullRequest(issue.PullRequest, nil), Repository: convert.ToRepo(issue.Repo, mode), Sender: convert.ToUser(doer, nil), }) @@ -145,7 +145,7 @@ func (m *webhookNotifier) NotifyIssueChangeAssignee(doer *models.User, issue *mo issue.PullRequest.Issue = issue apiPullRequest := &api.PullRequestPayload{ Index: issue.Index, - PullRequest: convert.ToAPIPullRequest(issue.PullRequest), + PullRequest: convert.ToAPIPullRequest(issue.PullRequest, nil), Repository: convert.ToRepo(issue.Repo, mode), Sender: convert.ToUser(doer, nil), } @@ -197,7 +197,7 @@ func (m *webhookNotifier) NotifyIssueChangeTitle(doer *models.User, issue *model From: oldTitle, }, }, - PullRequest: convert.ToAPIPullRequest(issue.PullRequest), + PullRequest: convert.ToAPIPullRequest(issue.PullRequest, nil), Repository: convert.ToRepo(issue.Repo, mode), Sender: convert.ToUser(doer, nil), }) @@ -232,7 +232,7 @@ func (m *webhookNotifier) NotifyIssueChangeStatus(doer *models.User, issue *mode // Merge pull request calls issue.changeStatus so we need to handle separately. apiPullRequest := &api.PullRequestPayload{ Index: issue.Index, - PullRequest: convert.ToAPIPullRequest(issue.PullRequest), + PullRequest: convert.ToAPIPullRequest(issue.PullRequest, nil), Repository: convert.ToRepo(issue.Repo, mode), Sender: convert.ToUser(doer, nil), } @@ -301,7 +301,7 @@ func (m *webhookNotifier) NotifyNewPullRequest(pull *models.PullRequest, mention if err := webhook_services.PrepareWebhooks(pull.Issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{ Action: api.HookIssueOpened, Index: pull.Issue.Index, - PullRequest: convert.ToAPIPullRequest(pull), + PullRequest: convert.ToAPIPullRequest(pull, nil), Repository: convert.ToRepo(pull.Issue.Repo, mode), Sender: convert.ToUser(pull.Issue.Poster, nil), }); err != nil { @@ -322,7 +322,7 @@ func (m *webhookNotifier) NotifyIssueChangeContent(doer *models.User, issue *mod From: oldContent, }, }, - PullRequest: convert.ToAPIPullRequest(issue.PullRequest), + PullRequest: convert.ToAPIPullRequest(issue.PullRequest, nil), Repository: convert.ToRepo(issue.Repo, mode), Sender: convert.ToUser(doer, nil), }) @@ -500,7 +500,7 @@ func (m *webhookNotifier) NotifyIssueChangeLabels(doer *models.User, issue *mode err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventPullRequestLabel, &api.PullRequestPayload{ Action: api.HookIssueLabelUpdated, Index: issue.Index, - PullRequest: convert.ToAPIPullRequest(issue.PullRequest), + PullRequest: convert.ToAPIPullRequest(issue.PullRequest, nil), Repository: convert.ToRepo(issue.Repo, models.AccessModeNone), Sender: convert.ToUser(doer, nil), }) @@ -542,7 +542,7 @@ func (m *webhookNotifier) NotifyIssueChangeMilestone(doer *models.User, issue *m err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventPullRequestMilestone, &api.PullRequestPayload{ Action: hookAction, Index: issue.Index, - PullRequest: convert.ToAPIPullRequest(issue.PullRequest), + PullRequest: convert.ToAPIPullRequest(issue.PullRequest, nil), Repository: convert.ToRepo(issue.Repo, mode), Sender: convert.ToUser(doer, nil), }) @@ -609,7 +609,7 @@ func (*webhookNotifier) NotifyMergePullRequest(pr *models.PullRequest, doer *mod // Merge pull request calls issue.changeStatus so we need to handle separately. apiPullRequest := &api.PullRequestPayload{ Index: pr.Issue.Index, - PullRequest: convert.ToAPIPullRequest(pr), + PullRequest: convert.ToAPIPullRequest(pr, nil), Repository: convert.ToRepo(pr.Issue.Repo, mode), Sender: convert.ToUser(doer, nil), Action: api.HookIssueClosed, @@ -642,7 +642,7 @@ func (m *webhookNotifier) NotifyPullRequestChangeTargetBranch(doer *models.User, From: oldBranch, }, }, - PullRequest: convert.ToAPIPullRequest(issue.PullRequest), + PullRequest: convert.ToAPIPullRequest(issue.PullRequest, nil), Repository: convert.ToRepo(issue.Repo, mode), Sender: convert.ToUser(doer, nil), }) @@ -681,7 +681,7 @@ func (m *webhookNotifier) NotifyPullRequestReview(pr *models.PullRequest, review if err := webhook_services.PrepareWebhooks(review.Issue.Repo, reviewHookType, &api.PullRequestPayload{ Action: api.HookIssueReviewed, Index: review.Issue.Index, - PullRequest: convert.ToAPIPullRequest(pr), + PullRequest: convert.ToAPIPullRequest(pr, nil), Repository: convert.ToRepo(review.Issue.Repo, mode), Sender: convert.ToUser(review.Reviewer, nil), Review: &api.ReviewPayload{ @@ -736,7 +736,7 @@ func (m *webhookNotifier) NotifyPullRequestSynchronized(doer *models.User, pr *m if err := webhook_services.PrepareWebhooks(pr.Issue.Repo, models.HookEventPullRequestSync, &api.PullRequestPayload{ Action: api.HookIssueSynchronized, Index: pr.Issue.Index, - PullRequest: convert.ToAPIPullRequest(pr), + PullRequest: convert.ToAPIPullRequest(pr, nil), Repository: convert.ToRepo(pr.Issue.Repo, models.AccessModeNone), Sender: convert.ToUser(doer, nil), }); err != nil { diff --git a/modules/options/dynamic.go b/modules/options/dynamic.go index 13fa5d6aa7ebd..78b0450d9b45e 100644 --- a/modules/options/dynamic.go +++ b/modules/options/dynamic.go @@ -9,7 +9,7 @@ package options import ( "fmt" - "io/ioutil" + "os" "path" "code.gitea.io/gitea/modules/log" @@ -100,7 +100,7 @@ func fileFromDir(name string) ([]byte, error) { log.Error("Unable to check if %s is a file. Error: %v", customPath, err) } if isFile { - return ioutil.ReadFile(customPath) + return os.ReadFile(customPath) } staticPath := path.Join(setting.StaticRootPath, "options", name) @@ -110,7 +110,7 @@ func fileFromDir(name string) ([]byte, error) { log.Error("Unable to check if %s is a file. Error: %v", staticPath, err) } if isFile { - return ioutil.ReadFile(staticPath) + return os.ReadFile(staticPath) } return []byte{}, fmt.Errorf("Asset file does not exist: %s", name) diff --git a/modules/options/static.go b/modules/options/static.go index 49e8445cd5950..afe32216d33f7 100644 --- a/modules/options/static.go +++ b/modules/options/static.go @@ -9,7 +9,8 @@ package options import ( "fmt" - "io/ioutil" + "io" + "os" "path" "code.gitea.io/gitea/modules/log" @@ -109,7 +110,7 @@ func fileFromDir(name string) ([]byte, error) { log.Error("Unable to check if %s is a file. Error: %v", customPath, err) } if isFile { - return ioutil.ReadFile(customPath) + return os.ReadFile(customPath) } f, err := Assets.Open(name) @@ -118,7 +119,7 @@ func fileFromDir(name string) ([]byte, error) { } defer f.Close() - return ioutil.ReadAll(f) + return io.ReadAll(f) } func Asset(name string) ([]byte, error) { @@ -127,7 +128,7 @@ func Asset(name string) ([]byte, error) { return nil, err } defer f.Close() - return ioutil.ReadAll(f) + return io.ReadAll(f) } func AssetNames() []string { diff --git a/modules/pprof/pprof.go b/modules/pprof/pprof.go index 80ad67be3a81b..f080728766788 100644 --- a/modules/pprof/pprof.go +++ b/modules/pprof/pprof.go @@ -6,7 +6,7 @@ package pprof import ( "fmt" - "io/ioutil" + "os" "runtime" "runtime/pprof" @@ -15,7 +15,7 @@ import ( // DumpMemProfileForUsername dumps a memory profile at pprofDataPath as memprofile__ func DumpMemProfileForUsername(pprofDataPath, username string) error { - f, err := ioutil.TempFile(pprofDataPath, fmt.Sprintf("memprofile_%s_", username)) + f, err := os.CreateTemp(pprofDataPath, fmt.Sprintf("memprofile_%s_", username)) if err != nil { return err } @@ -27,7 +27,7 @@ func DumpMemProfileForUsername(pprofDataPath, username string) error { // DumpCPUProfileForUsername dumps a CPU profile at pprofDataPath as cpuprofile__ // it returns the stop function which stops, writes and closes the CPU profile file func DumpCPUProfileForUsername(pprofDataPath, username string) (func(), error) { - f, err := ioutil.TempFile(pprofDataPath, fmt.Sprintf("cpuprofile_%s_", username)) + f, err := os.CreateTemp(pprofDataPath, fmt.Sprintf("cpuprofile_%s_", username)) if err != nil { return nil, err } diff --git a/modules/private/key.go b/modules/private/key.go index d0b11a96e7ac2..8dfd82a6d628f 100644 --- a/modules/private/key.go +++ b/modules/private/key.go @@ -7,7 +7,7 @@ package private import ( "context" "fmt" - "io/ioutil" + "io" "net/http" "code.gitea.io/gitea/modules/setting" @@ -49,7 +49,7 @@ func AuthorizedPublicKeyByContent(ctx context.Context, content string) (string, if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("Failed to update public key: %s", decodeJSONError(resp).Err) } - bs, err := ioutil.ReadAll(resp.Body) + bs, err := io.ReadAll(resp.Body) return string(bs), err } diff --git a/modules/private/mail.go b/modules/private/mail.go index 7e229396e31ee..bcf601b029763 100644 --- a/modules/private/mail.go +++ b/modules/private/mail.go @@ -7,7 +7,7 @@ package private import ( "context" "fmt" - "io/ioutil" + "io" "net/http" "code.gitea.io/gitea/modules/json" @@ -45,7 +45,7 @@ func SendEmail(ctx context.Context, subject, message string, to []string) (int, } defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return http.StatusInternalServerError, fmt.Sprintf("Response body error: %v", err.Error()) } diff --git a/modules/private/restore_repo.go b/modules/private/restore_repo.go index b5592278ab47d..6ebf4c63d5cda 100644 --- a/modules/private/restore_repo.go +++ b/modules/private/restore_repo.go @@ -7,7 +7,7 @@ package private import ( "context" "fmt" - "io/ioutil" + "io" "net/http" "time" @@ -47,7 +47,7 @@ func RestoreRepo(ctx context.Context, repoDir, ownerName, repoName string, units var ret = struct { Err string `json:"err"` }{} - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return http.StatusInternalServerError, fmt.Sprintf("Response body error: %v", err.Error()) } diff --git a/modules/public/static.go b/modules/public/static.go index 6994ed6508574..32ba0fe258aa0 100644 --- a/modules/public/static.go +++ b/modules/public/static.go @@ -11,7 +11,6 @@ import ( "bytes" "compress/gzip" "io" - "io/ioutil" "mime" "net/http" "os" @@ -31,7 +30,7 @@ func Asset(name string) ([]byte, error) { return nil, err } defer f.Close() - return ioutil.ReadAll(f) + return io.ReadAll(f) } func AssetNames() []string { diff --git a/modules/queue/queue_disk_channel_test.go b/modules/queue/queue_disk_channel_test.go index 06cb7a7a96b19..99fb65934eb8b 100644 --- a/modules/queue/queue_disk_channel_test.go +++ b/modules/queue/queue_disk_channel_test.go @@ -5,7 +5,7 @@ package queue import ( - "io/ioutil" + "os" "sync" "testing" @@ -26,7 +26,7 @@ func TestPersistableChannelQueue(t *testing.T) { queueShutdown := []func(){} queueTerminate := []func(){} - tmpDir, err := ioutil.TempDir("", "persistable-channel-queue-test-data") + tmpDir, err := os.MkdirTemp("", "persistable-channel-queue-test-data") assert.NoError(t, err) defer util.RemoveAll(tmpDir) diff --git a/modules/queue/queue_disk_test.go b/modules/queue/queue_disk_test.go index 1f884d4f8d76d..549a04104ebd7 100644 --- a/modules/queue/queue_disk_test.go +++ b/modules/queue/queue_disk_test.go @@ -5,7 +5,7 @@ package queue import ( - "io/ioutil" + "os" "sync" "testing" "time" @@ -28,7 +28,7 @@ func TestLevelQueue(t *testing.T) { queueShutdown := []func(){} queueTerminate := []func(){} - tmpDir, err := ioutil.TempDir("", "level-queue-test-data") + tmpDir, err := os.MkdirTemp("", "level-queue-test-data") assert.NoError(t, err) defer util.RemoveAll(tmpDir) diff --git a/modules/recaptcha/recaptcha.go b/modules/recaptcha/recaptcha.go index e0f5ee2e0708e..c6798f71177a7 100644 --- a/modules/recaptcha/recaptcha.go +++ b/modules/recaptcha/recaptcha.go @@ -7,7 +7,7 @@ package recaptcha import ( "context" "fmt" - "io/ioutil" + "io" "net/http" "net/url" "strings" @@ -46,7 +46,7 @@ func Verify(ctx context.Context, response string) (bool, error) { return false, fmt.Errorf("Failed to send CAPTCHA response: %s", err) } defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return false, fmt.Errorf("Failed to read CAPTCHA response: %s", err) } diff --git a/modules/repofiles/action_test.go b/modules/repofiles/action_test.go index 97632df68fb89..c29cfd686d420 100644 --- a/modules/repofiles/action_test.go +++ b/modules/repofiles/action_test.go @@ -8,6 +8,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" @@ -15,7 +16,7 @@ import ( ) func TestUpdateIssuesCommit(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) pushCommits := []*repository.PushCommit{ { Sha1: "abcdef1", @@ -43,8 +44,8 @@ func TestUpdateIssuesCommit(t *testing.T) { }, } - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) repo.Owner = user commentBean := &models.Comment{ @@ -55,11 +56,11 @@ func TestUpdateIssuesCommit(t *testing.T) { } issueBean := &models.Issue{RepoID: repo.ID, Index: 4} - models.AssertNotExistsBean(t, commentBean) - models.AssertNotExistsBean(t, &models.Issue{RepoID: repo.ID, Index: 2}, "is_closed=1") + db.AssertNotExistsBean(t, commentBean) + db.AssertNotExistsBean(t, &models.Issue{RepoID: repo.ID, Index: 2}, "is_closed=1") assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, repo.DefaultBranch)) - models.AssertExistsAndLoadBean(t, commentBean) - models.AssertExistsAndLoadBean(t, issueBean, "is_closed=1") + db.AssertExistsAndLoadBean(t, commentBean) + db.AssertExistsAndLoadBean(t, issueBean, "is_closed=1") models.CheckConsistencyFor(t, &models.Action{}) // Test that push to a non-default branch closes no issue. @@ -73,7 +74,7 @@ func TestUpdateIssuesCommit(t *testing.T) { Message: "close #1", }, } - repo = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) + repo = db.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) commentBean = &models.Comment{ Type: models.CommentTypeCommitRef, CommitSHA: "abcdef1", @@ -82,11 +83,11 @@ func TestUpdateIssuesCommit(t *testing.T) { } issueBean = &models.Issue{RepoID: repo.ID, Index: 1} - models.AssertNotExistsBean(t, commentBean) - models.AssertNotExistsBean(t, &models.Issue{RepoID: repo.ID, Index: 1}, "is_closed=1") + db.AssertNotExistsBean(t, commentBean) + db.AssertNotExistsBean(t, &models.Issue{RepoID: repo.ID, Index: 1}, "is_closed=1") assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, "non-existing-branch")) - models.AssertExistsAndLoadBean(t, commentBean) - models.AssertNotExistsBean(t, issueBean, "is_closed=1") + db.AssertExistsAndLoadBean(t, commentBean) + db.AssertNotExistsBean(t, issueBean, "is_closed=1") models.CheckConsistencyFor(t, &models.Action{}) pushCommits = []*repository.PushCommit{ @@ -99,7 +100,7 @@ func TestUpdateIssuesCommit(t *testing.T) { Message: "close " + setting.AppURL + repo.FullName() + "/pulls/1", }, } - repo = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) + repo = db.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) commentBean = &models.Comment{ Type: models.CommentTypeCommitRef, CommitSHA: "abcdef3", @@ -108,16 +109,16 @@ func TestUpdateIssuesCommit(t *testing.T) { } issueBean = &models.Issue{RepoID: repo.ID, Index: 1} - models.AssertNotExistsBean(t, commentBean) - models.AssertNotExistsBean(t, &models.Issue{RepoID: repo.ID, Index: 1}, "is_closed=1") + db.AssertNotExistsBean(t, commentBean) + db.AssertNotExistsBean(t, &models.Issue{RepoID: repo.ID, Index: 1}, "is_closed=1") assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, repo.DefaultBranch)) - models.AssertExistsAndLoadBean(t, commentBean) - models.AssertExistsAndLoadBean(t, issueBean, "is_closed=1") + db.AssertExistsAndLoadBean(t, commentBean) + db.AssertExistsAndLoadBean(t, issueBean, "is_closed=1") models.CheckConsistencyFor(t, &models.Action{}) } func TestUpdateIssuesCommit_Colon(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) pushCommits := []*repository.PushCommit{ { Sha1: "abcdef2", @@ -129,21 +130,21 @@ func TestUpdateIssuesCommit_Colon(t *testing.T) { }, } - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) repo.Owner = user issueBean := &models.Issue{RepoID: repo.ID, Index: 4} - models.AssertNotExistsBean(t, &models.Issue{RepoID: repo.ID, Index: 2}, "is_closed=1") + db.AssertNotExistsBean(t, &models.Issue{RepoID: repo.ID, Index: 2}, "is_closed=1") assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, repo.DefaultBranch)) - models.AssertExistsAndLoadBean(t, issueBean, "is_closed=1") + db.AssertExistsAndLoadBean(t, issueBean, "is_closed=1") models.CheckConsistencyFor(t, &models.Action{}) } func TestUpdateIssuesCommit_Issue5957(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + assert.NoError(t, db.PrepareTestDatabase()) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // Test that push to a non-default branch closes an issue. pushCommits := []*repository.PushCommit{ @@ -157,7 +158,7 @@ func TestUpdateIssuesCommit_Issue5957(t *testing.T) { }, } - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) commentBean := &models.Comment{ Type: models.CommentTypeCommitRef, CommitSHA: "abcdef1", @@ -167,17 +168,17 @@ func TestUpdateIssuesCommit_Issue5957(t *testing.T) { issueBean := &models.Issue{RepoID: repo.ID, Index: 2, ID: 7} - models.AssertNotExistsBean(t, commentBean) - models.AssertNotExistsBean(t, issueBean, "is_closed=1") + db.AssertNotExistsBean(t, commentBean) + db.AssertNotExistsBean(t, issueBean, "is_closed=1") assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, "non-existing-branch")) - models.AssertExistsAndLoadBean(t, commentBean) - models.AssertExistsAndLoadBean(t, issueBean, "is_closed=1") + db.AssertExistsAndLoadBean(t, commentBean) + db.AssertExistsAndLoadBean(t, issueBean, "is_closed=1") models.CheckConsistencyFor(t, &models.Action{}) } func TestUpdateIssuesCommit_AnotherRepo(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + assert.NoError(t, db.PrepareTestDatabase()) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // Test that a push to default branch closes issue in another repo // If the user also has push permissions to that repo @@ -192,7 +193,7 @@ func TestUpdateIssuesCommit_AnotherRepo(t *testing.T) { }, } - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) commentBean := &models.Comment{ Type: models.CommentTypeCommitRef, CommitSHA: "abcdef1", @@ -202,17 +203,17 @@ func TestUpdateIssuesCommit_AnotherRepo(t *testing.T) { issueBean := &models.Issue{RepoID: 1, Index: 1, ID: 1} - models.AssertNotExistsBean(t, commentBean) - models.AssertNotExistsBean(t, issueBean, "is_closed=1") + db.AssertNotExistsBean(t, commentBean) + db.AssertNotExistsBean(t, issueBean, "is_closed=1") assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, repo.DefaultBranch)) - models.AssertExistsAndLoadBean(t, commentBean) - models.AssertExistsAndLoadBean(t, issueBean, "is_closed=1") + db.AssertExistsAndLoadBean(t, commentBean) + db.AssertExistsAndLoadBean(t, issueBean, "is_closed=1") models.CheckConsistencyFor(t, &models.Action{}) } func TestUpdateIssuesCommit_AnotherRepo_FullAddress(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + assert.NoError(t, db.PrepareTestDatabase()) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // Test that a push to default branch closes issue in another repo // If the user also has push permissions to that repo @@ -227,7 +228,7 @@ func TestUpdateIssuesCommit_AnotherRepo_FullAddress(t *testing.T) { }, } - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) commentBean := &models.Comment{ Type: models.CommentTypeCommitRef, CommitSHA: "abcdef1", @@ -237,17 +238,17 @@ func TestUpdateIssuesCommit_AnotherRepo_FullAddress(t *testing.T) { issueBean := &models.Issue{RepoID: 1, Index: 1, ID: 1} - models.AssertNotExistsBean(t, commentBean) - models.AssertNotExistsBean(t, issueBean, "is_closed=1") + db.AssertNotExistsBean(t, commentBean) + db.AssertNotExistsBean(t, issueBean, "is_closed=1") assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, repo.DefaultBranch)) - models.AssertExistsAndLoadBean(t, commentBean) - models.AssertExistsAndLoadBean(t, issueBean, "is_closed=1") + db.AssertExistsAndLoadBean(t, commentBean) + db.AssertExistsAndLoadBean(t, issueBean, "is_closed=1") models.CheckConsistencyFor(t, &models.Action{}) } func TestUpdateIssuesCommit_AnotherRepoNoPermission(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 10}).(*models.User) + assert.NoError(t, db.PrepareTestDatabase()) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 10}).(*models.User) // Test that a push with close reference *can not* close issue // If the committer doesn't have push rights in that repo @@ -270,7 +271,7 @@ func TestUpdateIssuesCommit_AnotherRepoNoPermission(t *testing.T) { }, } - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 6}).(*models.Repository) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 6}).(*models.Repository) commentBean := &models.Comment{ Type: models.CommentTypeCommitRef, CommitSHA: "abcdef3", @@ -286,12 +287,12 @@ func TestUpdateIssuesCommit_AnotherRepoNoPermission(t *testing.T) { issueBean := &models.Issue{RepoID: 3, Index: 1, ID: 6} - models.AssertNotExistsBean(t, commentBean) - models.AssertNotExistsBean(t, commentBean2) - models.AssertNotExistsBean(t, issueBean, "is_closed=1") + db.AssertNotExistsBean(t, commentBean) + db.AssertNotExistsBean(t, commentBean2) + db.AssertNotExistsBean(t, issueBean, "is_closed=1") assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, repo.DefaultBranch)) - models.AssertNotExistsBean(t, commentBean) - models.AssertNotExistsBean(t, commentBean2) - models.AssertNotExistsBean(t, issueBean, "is_closed=1") + db.AssertNotExistsBean(t, commentBean) + db.AssertNotExistsBean(t, commentBean2) + db.AssertNotExistsBean(t, issueBean, "is_closed=1") models.CheckConsistencyFor(t, &models.Action{}) } diff --git a/modules/repofiles/blob_test.go b/modules/repofiles/blob_test.go index 8ba80347bf6f5..5238dd6e74f4b 100644 --- a/modules/repofiles/blob_test.go +++ b/modules/repofiles/blob_test.go @@ -7,7 +7,7 @@ package repofiles import ( "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" @@ -15,7 +15,7 @@ import ( ) func TestGetBlobBySHA(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1") test.LoadRepo(t, ctx, 1) test.LoadRepoCommit(t, ctx) diff --git a/modules/repofiles/content_test.go b/modules/repofiles/content_test.go index 253d434459294..f68460d7ec3f6 100644 --- a/modules/repofiles/content_test.go +++ b/modules/repofiles/content_test.go @@ -8,7 +8,7 @@ import ( "path/filepath" "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" @@ -16,7 +16,7 @@ import ( ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..")) + db.MainTest(m, filepath.Join("..", "..")) } func getExpectedReadmeContentsResponse() *api.ContentsResponse { @@ -49,7 +49,7 @@ func getExpectedReadmeContentsResponse() *api.ContentsResponse { } func TestGetContents(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1") ctx.SetParams(":id", "1") test.LoadRepo(t, ctx, 1) @@ -77,7 +77,7 @@ func TestGetContents(t *testing.T) { } func TestGetContentsOrListForDir(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1") ctx.SetParams(":id", "1") test.LoadRepo(t, ctx, 1) @@ -112,7 +112,7 @@ func TestGetContentsOrListForDir(t *testing.T) { } func TestGetContentsOrListForFile(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1") ctx.SetParams(":id", "1") test.LoadRepo(t, ctx, 1) @@ -140,7 +140,7 @@ func TestGetContentsOrListForFile(t *testing.T) { } func TestGetContentsErrors(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1") ctx.SetParams(":id", "1") test.LoadRepo(t, ctx, 1) @@ -171,7 +171,7 @@ func TestGetContentsErrors(t *testing.T) { } func TestGetContentsOrListErrors(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1") ctx.SetParams(":id", "1") test.LoadRepo(t, ctx, 1) @@ -202,7 +202,7 @@ func TestGetContentsOrListErrors(t *testing.T) { } func TestGetContentsOrListOfEmptyRepos(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo15") ctx.SetParams(":id", "15") test.LoadRepo(t, ctx, 15) diff --git a/modules/repofiles/delete.go b/modules/repofiles/delete.go index 2b8ddf3cc266f..5ae418b7f62a1 100644 --- a/modules/repofiles/delete.go +++ b/modules/repofiles/delete.go @@ -56,37 +56,8 @@ func DeleteRepoFile(repo *models.Repository, doer *models.User, opts *DeleteRepo BranchName: opts.NewBranch, } } - } else { - protectedBranch, err := repo.GetBranchProtection(opts.OldBranch) - if err != nil { - return nil, err - } - if protectedBranch != nil { - if !protectedBranch.CanUserPush(doer.ID) { - return nil, models.ErrUserCannotCommit{ - UserName: doer.LowerName, - } - } - if protectedBranch.RequireSignedCommits { - _, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch) - if err != nil { - if !models.IsErrWontSign(err) { - return nil, err - } - return nil, models.ErrUserCannotCommit{ - UserName: doer.LowerName, - } - } - } - patterns := protectedBranch.GetProtectedFilePatterns() - for _, pat := range patterns { - if pat.Match(strings.ToLower(opts.TreePath)) { - return nil, models.ErrFilePathProtected{ - Path: opts.TreePath, - } - } - } - } + } else if err := VerifyBranchProtection(repo, doer, opts.OldBranch, opts.TreePath); err != nil { + return nil, err } // Check that the path given in opts.treeName is valid (not a git path) diff --git a/modules/repofiles/diff_test.go b/modules/repofiles/diff_test.go index da50284150a28..aeb4f76a4576b 100644 --- a/modules/repofiles/diff_test.go +++ b/modules/repofiles/diff_test.go @@ -8,6 +8,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/services/gitdiff" @@ -15,7 +16,7 @@ import ( ) func TestGetDiffPreview(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1") ctx.SetParams(":id", "1") test.LoadRepo(t, ctx, 1) @@ -128,7 +129,7 @@ func TestGetDiffPreview(t *testing.T) { } func TestGetDiffPreviewErrors(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1") ctx.SetParams(":id", "1") test.LoadRepo(t, ctx, 1) diff --git a/modules/repofiles/file_test.go b/modules/repofiles/file_test.go index aafe816a166a8..2974056549e38 100644 --- a/modules/repofiles/file_test.go +++ b/modules/repofiles/file_test.go @@ -7,7 +7,7 @@ package repofiles import ( "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -81,7 +81,7 @@ func getExpectedFileResponse() *api.FileResponse { } func TestGetFileResponseFromCommit(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1") ctx.SetParams(":id", "1") test.LoadRepo(t, ctx, 1) diff --git a/modules/repofiles/tree_test.go b/modules/repofiles/tree_test.go index 274e89e96938e..9e2efa4216fad 100644 --- a/modules/repofiles/tree_test.go +++ b/modules/repofiles/tree_test.go @@ -7,7 +7,7 @@ package repofiles import ( "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" @@ -15,7 +15,7 @@ import ( ) func TestGetTreeBySHA(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1") test.LoadRepo(t, ctx, 1) test.LoadRepoCommit(t, ctx) diff --git a/modules/repofiles/update.go b/modules/repofiles/update.go index ad984c465ae86..dc2893cb1c350 100644 --- a/modules/repofiles/update.go +++ b/modules/repofiles/update.go @@ -148,37 +148,8 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up if err != nil && !git.IsErrBranchNotExist(err) { return nil, err } - } else { - protectedBranch, err := repo.GetBranchProtection(opts.OldBranch) - if err != nil { - return nil, err - } - if protectedBranch != nil { - if !protectedBranch.CanUserPush(doer.ID) { - return nil, models.ErrUserCannotCommit{ - UserName: doer.LowerName, - } - } - if protectedBranch.RequireSignedCommits { - _, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch) - if err != nil { - if !models.IsErrWontSign(err) { - return nil, err - } - return nil, models.ErrUserCannotCommit{ - UserName: doer.LowerName, - } - } - } - patterns := protectedBranch.GetProtectedFilePatterns() - for _, pat := range patterns { - if pat.Match(strings.ToLower(opts.TreePath)) { - return nil, models.ErrFilePathProtected{ - Path: opts.TreePath, - } - } - } - } + } else if err := VerifyBranchProtection(repo, doer, opts.OldBranch, opts.TreePath); err != nil { + return nil, err } // If FromTreePath is not set, set it to the opts.TreePath @@ -465,3 +436,43 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up } return file, nil } + +// VerifyBranchProtection verify the branch protection for modifying the given treePath on the given branch +func VerifyBranchProtection(repo *models.Repository, doer *models.User, branchName string, treePath string) error { + protectedBranch, err := repo.GetBranchProtection(branchName) + if err != nil { + return err + } + if protectedBranch != nil { + isUnprotectedFile := false + glob := protectedBranch.GetUnprotectedFilePatterns() + if len(glob) != 0 { + isUnprotectedFile = protectedBranch.IsUnprotectedFile(glob, treePath) + } + if !protectedBranch.CanUserPush(doer.ID) && !isUnprotectedFile { + return models.ErrUserCannotCommit{ + UserName: doer.LowerName, + } + } + if protectedBranch.RequireSignedCommits { + _, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), branchName) + if err != nil { + if !models.IsErrWontSign(err) { + return err + } + return models.ErrUserCannotCommit{ + UserName: doer.LowerName, + } + } + } + patterns := protectedBranch.GetProtectedFilePatterns() + for _, pat := range patterns { + if pat.Match(strings.ToLower(treePath)) { + return models.ErrFilePathProtected{ + Path: treePath, + } + } + } + } + return nil +} diff --git a/modules/repository/adopt.go b/modules/repository/adopt.go index 321e6ab7672cd..c5c059f4718b4 100644 --- a/modules/repository/adopt.go +++ b/modules/repository/adopt.go @@ -5,12 +5,14 @@ package repository import ( + "context" "fmt" "os" "path/filepath" "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -46,7 +48,7 @@ func AdoptRepository(doer, u *models.User, opts models.CreateRepoOptions) (*mode IsEmpty: !opts.AutoInit, } - if err := models.WithTx(func(ctx models.DBContext) error { + if err := db.WithTx(func(ctx context.Context) error { repoPath := models.RepoPath(u.Name, repo.Name) isExist, err := util.IsExist(repoPath) if err != nil { @@ -120,7 +122,7 @@ func DeleteUnadoptedRepository(doer, u *models.User, repoName string) error { } // ListUnadoptedRepositories lists all the unadopted repositories that match the provided query -func ListUnadoptedRepositories(query string, opts *models.ListOptions) ([]string, int, error) { +func ListUnadoptedRepositories(query string, opts *db.ListOptions) ([]string, int, error) { globUser, _ := glob.Compile("*") globRepo, _ := glob.Compile("*") @@ -163,10 +165,13 @@ func ListUnadoptedRepositories(query string, opts *models.ListOptions) ([]string // Clean up old repoNamesToCheck if len(repoNamesToCheck) > 0 { - repos, _, err := models.GetUserRepositories(&models.SearchRepoOptions{Actor: ctxUser, Private: true, ListOptions: models.ListOptions{ - Page: 1, - PageSize: opts.PageSize, - }, LowerNames: repoNamesToCheck}) + repos, _, err := models.GetUserRepositories(&models.SearchRepoOptions{ + Actor: ctxUser, + Private: true, + ListOptions: db.ListOptions{ + Page: 1, + PageSize: opts.PageSize, + }, LowerNames: repoNamesToCheck}) if err != nil { return err } @@ -217,10 +222,13 @@ func ListUnadoptedRepositories(query string, opts *models.ListOptions) ([]string if count < end { repoNamesToCheck = append(repoNamesToCheck, name) if len(repoNamesToCheck) >= opts.PageSize { - repos, _, err := models.GetUserRepositories(&models.SearchRepoOptions{Actor: ctxUser, Private: true, ListOptions: models.ListOptions{ - Page: 1, - PageSize: opts.PageSize, - }, LowerNames: repoNamesToCheck}) + repos, _, err := models.GetUserRepositories(&models.SearchRepoOptions{ + Actor: ctxUser, + Private: true, + ListOptions: db.ListOptions{ + Page: 1, + PageSize: opts.PageSize, + }, LowerNames: repoNamesToCheck}) if err != nil { return err } @@ -252,10 +260,13 @@ func ListUnadoptedRepositories(query string, opts *models.ListOptions) ([]string } if len(repoNamesToCheck) > 0 { - repos, _, err := models.GetUserRepositories(&models.SearchRepoOptions{Actor: ctxUser, Private: true, ListOptions: models.ListOptions{ - Page: 1, - PageSize: opts.PageSize, - }, LowerNames: repoNamesToCheck}) + repos, _, err := models.GetUserRepositories(&models.SearchRepoOptions{ + Actor: ctxUser, + Private: true, + ListOptions: db.ListOptions{ + Page: 1, + PageSize: opts.PageSize, + }, LowerNames: repoNamesToCheck}) if err != nil { return nil, 0, err } diff --git a/modules/repository/check.go b/modules/repository/check.go index 0485e9e83a50d..78e6f8a90135f 100644 --- a/modules/repository/check.go +++ b/modules/repository/check.go @@ -11,6 +11,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util" @@ -22,8 +23,8 @@ import ( func GitFsck(ctx context.Context, timeout time.Duration, args []string) error { log.Trace("Doing: GitFsck") - if err := models.Iterate( - models.DefaultDBContext(), + if err := db.Iterate( + db.DefaultContext, new(models.Repository), builder.Expr("id>0 AND is_fsck_enabled=?", true), func(idx int, bean interface{}) error { @@ -57,8 +58,8 @@ func GitGcRepos(ctx context.Context, timeout time.Duration, args ...string) erro log.Trace("Doing: GitGcRepos") args = append([]string{"gc"}, args...) - if err := models.Iterate( - models.DefaultDBContext(), + if err := db.Iterate( + db.DefaultContext, new(models.Repository), builder.Gt{"id": 0}, func(idx int, bean interface{}) error { @@ -93,7 +94,7 @@ func GitGcRepos(ctx context.Context, timeout time.Duration, args ...string) erro } // Now update the size of the repository - if err := repo.UpdateSize(models.DefaultDBContext()); err != nil { + if err := repo.UpdateSize(db.DefaultContext); err != nil { log.Error("Updating size as part of garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err) desc := fmt.Sprintf("Updating size as part of garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err) if err = models.CreateRepositoryNotice(desc); err != nil { @@ -114,8 +115,8 @@ func GitGcRepos(ctx context.Context, timeout time.Duration, args ...string) erro func gatherMissingRepoRecords(ctx context.Context) ([]*models.Repository, error) { repos := make([]*models.Repository, 0, 10) - if err := models.Iterate( - models.DefaultDBContext(), + if err := db.Iterate( + db.DefaultContext, new(models.Repository), builder.Gt{"id": 0}, func(idx int, bean interface{}) error { diff --git a/modules/repository/commits.go b/modules/repository/commits.go index 1558d8563905f..c86f0d570be4f 100644 --- a/modules/repository/commits.go +++ b/modules/repository/commits.go @@ -9,6 +9,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/avatars" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" api "code.gitea.io/gitea/modules/structs" @@ -139,14 +140,14 @@ func (pc *PushCommits) AvatarLink(email string) string { return avatar } - size := models.DefaultAvatarPixelSize * models.AvatarRenderedSizeFactor + size := avatars.DefaultAvatarPixelSize * avatars.AvatarRenderedSizeFactor u, ok := pc.emailUsers[email] if !ok { var err error u, err = models.GetUserByEmail(email) if err != nil { - pc.avatars[email] = models.SizedAvatarLink(email, size) + pc.avatars[email] = avatars.GenerateEmailAvatarFastLink(email, size) if !models.IsErrUserNotExist(err) { log.Error("GetUserByEmail: %v", err) return "" @@ -156,7 +157,7 @@ func (pc *PushCommits) AvatarLink(email string) string { } } if u != nil { - pc.avatars[email] = u.RealSizedAvatarLink(size) + pc.avatars[email] = u.AvatarLinkWithSize(size) } return pc.avatars[email] diff --git a/modules/repository/commits_test.go b/modules/repository/commits_test.go index 9f0852525898b..c62ad584496df 100644 --- a/modules/repository/commits_test.go +++ b/modules/repository/commits_test.go @@ -11,12 +11,13 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "github.com/stretchr/testify/assert" ) func TestPushCommits_ToAPIPayloadCommits(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) pushCommits := NewPushCommits() pushCommits.Commits = []*PushCommit{ @@ -47,7 +48,7 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) { } pushCommits.HeadCommit = &PushCommit{Sha1: "69554a6"} - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) payloadCommits, headCommit, err := pushCommits.ToAPIPayloadCommits(repo.RepoPath(), "/user2/repo16") assert.NoError(t, err) assert.Len(t, payloadCommits, 3) @@ -99,7 +100,7 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) { } func TestPushCommits_AvatarLink(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) pushCommits := NewPushCommits() pushCommits.Commits = []*PushCommit{ diff --git a/modules/repository/create.go b/modules/repository/create.go index 5eac03836e1bb..0e91a73b8359c 100644 --- a/modules/repository/create.go +++ b/modules/repository/create.go @@ -5,10 +5,12 @@ package repository import ( + "context" "fmt" "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -54,7 +56,7 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (*mod var rollbackRepo *models.Repository - if err := models.WithTx(func(ctx models.DBContext) error { + if err := db.WithTx(func(ctx context.Context) error { if err := models.CreateRepository(ctx, doer, u, repo, false); err != nil { return err } diff --git a/modules/repository/create_test.go b/modules/repository/create_test.go index 65ed7806a9b25..040c061d9209f 100644 --- a/modules/repository/create_test.go +++ b/modules/repository/create_test.go @@ -9,16 +9,17 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" ) func TestIncludesAllRepositoriesTeams(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) testTeamRepositories := func(teamID int64, repoIds []int64) { - team := models.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team) + team := db.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team) assert.NoError(t, team.GetRepositories(&models.SearchTeamOptions{}), "%s: GetRepositories", team.Name) assert.Len(t, team.Repos, team.NumRepos, "%s: len repo", team.Name) assert.Len(t, team.Repos, len(repoIds), "%s: repo count", team.Name) diff --git a/modules/repository/fork.go b/modules/repository/fork.go index 892c1494df7e0..59c07271a646f 100644 --- a/modules/repository/fork.go +++ b/modules/repository/fork.go @@ -5,14 +5,17 @@ package repository import ( + "context" "fmt" "strings" "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" ) // ForkRepository forks a repository @@ -45,38 +48,60 @@ func ForkRepository(doer, owner *models.User, opts models.ForkRepoOptions) (_ *m oldRepoPath := opts.BaseRepo.RepoPath() - err = models.WithTx(func(ctx models.DBContext) error { - if err = models.CreateRepository(ctx, doer, owner, repo, false); err != nil { - return err + needsRollback := false + rollbackFn := func() { + if !needsRollback { + return } - rollbackRemoveFn := func() { - if repo.ID == 0 { - return - } - if errDelete := models.DeleteRepository(doer, owner.ID, repo.ID); errDelete != nil { - log.Error("Rollback deleteRepository: %v", errDelete) - } + repoPath := models.RepoPath(owner.Name, repo.Name) + + if exists, _ := util.IsExist(repoPath); !exists { + return + } + + // As the transaction will be failed and hence database changes will be destroyed we only need + // to delete the related repository on the filesystem + if errDelete := util.RemoveAll(repoPath); errDelete != nil { + log.Error("Failed to remove fork repo") + } + } + + needsRollbackInPanic := true + defer func() { + panicErr := recover() + if panicErr == nil { + return + } + + if needsRollbackInPanic { + rollbackFn() + } + panic(panicErr) + }() + + err = db.WithTx(func(ctx context.Context) error { + if err = models.CreateRepository(ctx, doer, owner, repo, false); err != nil { + return err } if err = models.IncrementRepoForkNum(ctx, opts.BaseRepo.ID); err != nil { - rollbackRemoveFn() return err } // copy lfs files failure should not be ignored - if err := models.CopyLFS(ctx, repo, opts.BaseRepo); err != nil { - rollbackRemoveFn() + if err = models.CopyLFS(ctx, repo, opts.BaseRepo); err != nil { return err } + needsRollback = true + repoPath := models.RepoPath(owner.Name, repo.Name) if stdout, err := git.NewCommand( "clone", "--bare", oldRepoPath, repoPath). SetDescription(fmt.Sprintf("ForkRepository(git clone): %s to %s", opts.BaseRepo.FullName(), repo.FullName())). RunInDirTimeout(10*time.Minute, ""); err != nil { log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, opts.BaseRepo, stdout, err) - rollbackRemoveFn() return fmt.Errorf("git clone: %v", err) } @@ -84,23 +109,23 @@ func ForkRepository(doer, owner *models.User, opts models.ForkRepoOptions) (_ *m SetDescription(fmt.Sprintf("ForkRepository(git update-server-info): %s", repo.FullName())). RunInDir(repoPath); err != nil { log.Error("Fork Repository (git update-server-info) failed for %v:\nStdout: %s\nError: %v", repo, stdout, err) - rollbackRemoveFn() return fmt.Errorf("git update-server-info: %v", err) } if err = createDelegateHooks(repoPath); err != nil { - rollbackRemoveFn() return fmt.Errorf("createDelegateHooks: %v", err) } return nil }) + needsRollbackInPanic = false if err != nil { + rollbackFn() return nil, err } // even if below operations failed, it could be ignored. And they will be retried - ctx := models.DefaultDBContext() - if err = repo.UpdateSize(ctx); err != nil { + ctx := db.DefaultContext + if err := repo.UpdateSize(ctx); err != nil { log.Error("Failed to update size for repository: %v", err) } if err := models.CopyLanguageStat(opts.BaseRepo, repo); err != nil { @@ -109,3 +134,34 @@ func ForkRepository(doer, owner *models.User, opts models.ForkRepoOptions) (_ *m return repo, nil } + +// ConvertForkToNormalRepository convert the provided repo from a forked repo to normal repo +func ConvertForkToNormalRepository(repo *models.Repository) error { + err := db.WithTx(func(ctx context.Context) error { + repo, err := models.GetRepositoryByIDCtx(ctx, repo.ID) + if err != nil { + return err + } + + if !repo.IsFork { + return nil + } + + if err := models.DecrementRepoForkNum(ctx, repo.ForkID); err != nil { + log.Error("Unable to decrement repo fork num for old root repo %d of repository %-v whilst converting from fork. Error: %v", repo.ForkID, repo, err) + return err + } + + repo.IsFork = false + repo.ForkID = 0 + + if err := models.UpdateRepositoryCtx(ctx, repo, false); err != nil { + log.Error("Unable to update repository %-v whilst converting from fork. Error: %v", repo, err) + return err + } + + return nil + }) + + return err +} diff --git a/modules/repository/fork_test.go b/modules/repository/fork_test.go index fc4a724660eba..7a05f9dd97e31 100644 --- a/modules/repository/fork_test.go +++ b/modules/repository/fork_test.go @@ -8,15 +8,16 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestForkRepository(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // user 13 has already forked repo10 - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 13}).(*models.User) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 13}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository) fork, err := ForkRepository(user, user, models.ForkRepoOptions{ BaseRepo: repo, diff --git a/modules/repository/generate.go b/modules/repository/generate.go index 1ba457fb3a551..4fcebc06dc3ea 100644 --- a/modules/repository/generate.go +++ b/modules/repository/generate.go @@ -5,8 +5,8 @@ package repository import ( + "context" "fmt" - "io/ioutil" "os" "path" "path/filepath" @@ -84,7 +84,7 @@ func checkGiteaTemplate(tmpDir string) (*models.GiteaTemplate, error) { return nil, err } - content, err := ioutil.ReadFile(gtPath) + content, err := os.ReadFile(gtPath) if err != nil { return nil, err } @@ -150,12 +150,12 @@ func generateRepoCommit(repo, templateRepo, generateRepo *models.Repository, tmp base := strings.TrimPrefix(filepath.ToSlash(path), tmpDirSlash) for _, g := range gt.Globs() { if g.Match(base) { - content, err := ioutil.ReadFile(path) + content, err := os.ReadFile(path) if err != nil { return err } - if err := ioutil.WriteFile(path, + if err := os.WriteFile(path, []byte(generateExpansion(string(content), templateRepo, generateRepo)), 0644); err != nil { return err @@ -185,8 +185,8 @@ func generateRepoCommit(repo, templateRepo, generateRepo *models.Repository, tmp return initRepoCommit(tmpDir, repo, repo.Owner, templateRepo.DefaultBranch) } -func generateGitContent(ctx models.DBContext, repo, templateRepo, generateRepo *models.Repository) (err error) { - tmpDir, err := ioutil.TempDir(os.TempDir(), "gitea-"+repo.Name) +func generateGitContent(ctx context.Context, repo, templateRepo, generateRepo *models.Repository) (err error) { + tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-"+repo.Name) if err != nil { return fmt.Errorf("Failed to create temp dir for repository %s: %v", repo.RepoPath(), err) } @@ -223,7 +223,7 @@ func generateGitContent(ctx models.DBContext, repo, templateRepo, generateRepo * } // GenerateGitContent generates git content from a template repository -func GenerateGitContent(ctx models.DBContext, templateRepo, generateRepo *models.Repository) error { +func GenerateGitContent(ctx context.Context, templateRepo, generateRepo *models.Repository) error { if err := generateGitContent(ctx, generateRepo, templateRepo, generateRepo); err != nil { return err } @@ -239,7 +239,7 @@ func GenerateGitContent(ctx models.DBContext, templateRepo, generateRepo *models } // GenerateRepository generates a repository from a template -func GenerateRepository(ctx models.DBContext, doer, owner *models.User, templateRepo *models.Repository, opts models.GenerateRepoOptions) (_ *models.Repository, err error) { +func GenerateRepository(ctx context.Context, doer, owner *models.User, templateRepo *models.Repository, opts models.GenerateRepoOptions) (_ *models.Repository, err error) { generateRepo := &models.Repository{ OwnerID: owner.ID, Owner: owner, diff --git a/modules/repository/hooks.go b/modules/repository/hooks.go index 8b4e7d63023ac..6072dda0163fc 100644 --- a/modules/repository/hooks.go +++ b/modules/repository/hooks.go @@ -7,11 +7,11 @@ package repository import ( "context" "fmt" - "io/ioutil" "os" "path/filepath" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -109,7 +109,7 @@ func createDelegateHooks(repoPath string) (err error) { if err = util.Remove(oldHookPath); err != nil && !os.IsNotExist(err) { return fmt.Errorf("unable to pre-remove old hook file '%s' prior to rewriting: %v ", oldHookPath, err) } - if err = ioutil.WriteFile(oldHookPath, []byte(hookTpls[i]), 0777); err != nil { + if err = os.WriteFile(oldHookPath, []byte(hookTpls[i]), 0777); err != nil { return fmt.Errorf("write old hook file '%s': %v", oldHookPath, err) } @@ -120,7 +120,7 @@ func createDelegateHooks(repoPath string) (err error) { if err = util.Remove(newHookPath); err != nil && !os.IsNotExist(err) { return fmt.Errorf("unable to pre-remove new hook file '%s' prior to rewriting: %v", newHookPath, err) } - if err = ioutil.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0777); err != nil { + if err = os.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0777); err != nil { return fmt.Errorf("write new hook file '%s': %v", newHookPath, err) } @@ -191,7 +191,7 @@ func CheckDelegateHooks(repoPath string) ([]string, error) { if cont { continue } - contents, err := ioutil.ReadFile(oldHookPath) + contents, err := os.ReadFile(oldHookPath) if err != nil { return results, err } @@ -201,7 +201,7 @@ func CheckDelegateHooks(repoPath string) ([]string, error) { if !checkExecutable(oldHookPath) { results = append(results, fmt.Sprintf("old hook file %s is not executable", oldHookPath)) } - contents, err = ioutil.ReadFile(newHookPath) + contents, err = os.ReadFile(newHookPath) if err != nil { return results, err } @@ -220,8 +220,8 @@ func CheckDelegateHooks(repoPath string) ([]string, error) { func SyncRepositoryHooks(ctx context.Context) error { log.Trace("Doing: SyncRepositoryHooks") - if err := models.Iterate( - models.DefaultDBContext(), + if err := db.Iterate( + db.DefaultContext, new(models.Repository), builder.Gt{"id": 0}, func(idx int, bean interface{}) error { diff --git a/modules/repository/init.go b/modules/repository/init.go index 50cde4c0b9d17..5a1ff7e98bc6d 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -6,8 +6,8 @@ package repository import ( "bytes" + "context" "fmt" - "io/ioutil" "os" "path/filepath" "strings" @@ -22,7 +22,7 @@ import ( "github.com/unknwon/com" ) -func prepareRepoCommit(ctx models.DBContext, repo *models.Repository, tmpDir, repoPath string, opts models.CreateRepoOptions) error { +func prepareRepoCommit(ctx context.Context, repo *models.Repository, tmpDir, repoPath string, opts models.CreateRepoOptions) error { commitTimeStr := time.Now().Format(time.RFC3339) authorSig := repo.Owner.NewGitSig() @@ -58,7 +58,7 @@ func prepareRepoCommit(ctx models.DBContext, repo *models.Repository, tmpDir, re "CloneURL.HTTPS": cloneLink.HTTPS, "OwnerName": repo.OwnerName, } - if err = ioutil.WriteFile(filepath.Join(tmpDir, "README.md"), + if err = os.WriteFile(filepath.Join(tmpDir, "README.md"), []byte(com.Expand(string(data), match)), 0644); err != nil { return fmt.Errorf("write README.md: %v", err) } @@ -78,7 +78,7 @@ func prepareRepoCommit(ctx models.DBContext, repo *models.Repository, tmpDir, re } if buf.Len() > 0 { - if err = ioutil.WriteFile(filepath.Join(tmpDir, ".gitignore"), buf.Bytes(), 0644); err != nil { + if err = os.WriteFile(filepath.Join(tmpDir, ".gitignore"), buf.Bytes(), 0644); err != nil { return fmt.Errorf("write .gitignore: %v", err) } } @@ -91,7 +91,7 @@ func prepareRepoCommit(ctx models.DBContext, repo *models.Repository, tmpDir, re return fmt.Errorf("GetRepoInitFile[%s]: %v", opts.License, err) } - if err = ioutil.WriteFile(filepath.Join(tmpDir, "LICENSE"), data, 0644); err != nil { + if err = os.WriteFile(filepath.Join(tmpDir, "LICENSE"), data, 0644); err != nil { return fmt.Errorf("write LICENSE: %v", err) } } @@ -196,7 +196,7 @@ func checkInitRepository(owner, name string) (err error) { return nil } -func adoptRepository(ctx models.DBContext, repoPath string, u *models.User, repo *models.Repository, opts models.CreateRepoOptions) (err error) { +func adoptRepository(ctx context.Context, repoPath string, u *models.User, repo *models.Repository, opts models.CreateRepoOptions) (err error) { isExist, err := util.IsExist(repoPath) if err != nil { log.Error("Unable to check if %s exists. Error: %v", repoPath, err) @@ -283,14 +283,14 @@ func adoptRepository(ctx models.DBContext, repoPath string, u *models.User, repo } // InitRepository initializes README and .gitignore if needed. -func initRepository(ctx models.DBContext, repoPath string, u *models.User, repo *models.Repository, opts models.CreateRepoOptions) (err error) { +func initRepository(ctx context.Context, repoPath string, u *models.User, repo *models.Repository, opts models.CreateRepoOptions) (err error) { if err = checkInitRepository(repo.OwnerName, repo.Name); err != nil { return err } // Initialize repository according to user's choice. if opts.AutoInit { - tmpDir, err := ioutil.TempDir(os.TempDir(), "gitea-"+repo.Name) + tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-"+repo.Name) if err != nil { return fmt.Errorf("Failed to create temp dir for repository %s: %v", repo.RepoPath(), err) } diff --git a/modules/repository/main_test.go b/modules/repository/main_test.go index f13f358635ba1..91d0d36ca0260 100644 --- a/modules/repository/main_test.go +++ b/modules/repository/main_test.go @@ -8,9 +8,9 @@ import ( "path/filepath" "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..")) + db.MainTest(m, filepath.Join("..", "..")) } diff --git a/modules/repository/repo.go b/modules/repository/repo.go index 6b870397754ee..6b40a894fb8b2 100644 --- a/modules/repository/repo.go +++ b/modules/repository/repo.go @@ -14,6 +14,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" @@ -132,7 +133,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *models.User, repo *models. } } - if err = repo.UpdateSize(models.DefaultDBContext()); err != nil { + if err = repo.UpdateSize(db.DefaultContext); err != nil { log.Error("Failed to update size for repository: %v", err) } @@ -223,7 +224,11 @@ func CleanUpMigrateInfo(repo *models.Repository) (*models.Repository, error) { // SyncReleasesWithTags synchronizes release table with repository tags func SyncReleasesWithTags(repo *models.Repository, gitRepo *git.Repository) error { existingRelTags := make(map[string]struct{}) - opts := models.FindReleasesOptions{IncludeDrafts: true, IncludeTags: true, ListOptions: models.ListOptions{PageSize: 50}} + opts := models.FindReleasesOptions{ + IncludeDrafts: true, + IncludeTags: true, + ListOptions: db.ListOptions{PageSize: 50}, + } for page := 1; ; page++ { opts.Page = page rels, err := models.GetReleasesByRepoID(repo.ID, opts) @@ -250,7 +255,7 @@ func SyncReleasesWithTags(repo *models.Repository, gitRepo *git.Repository) erro } } } - tags, err := gitRepo.GetTags() + tags, err := gitRepo.GetTags(0, 0) if err != nil { return fmt.Errorf("GetTags: %v", err) } diff --git a/modules/repository/update.go b/modules/repository/update.go index 975f577da8db6..b9a5db2a6a13f 100644 --- a/modules/repository/update.go +++ b/modules/repository/update.go @@ -5,18 +5,20 @@ package repository import ( + "context" "fmt" "strings" "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/timeutil" ) // PushUpdateAddDeleteTags updates a number of added and delete tags func PushUpdateAddDeleteTags(repo *models.Repository, gitRepo *git.Repository, addTags, delTags []string) error { - return models.WithTx(func(ctx models.DBContext) error { + return db.WithTx(func(ctx context.Context) error { if err := models.PushUpdateDeleteTagsContext(ctx, repo, delTags); err != nil { return err } @@ -25,7 +27,7 @@ func PushUpdateAddDeleteTags(repo *models.Repository, gitRepo *git.Repository, a } // pushUpdateAddTags updates a number of add tags -func pushUpdateAddTags(ctx models.DBContext, repo *models.Repository, gitRepo *git.Repository, tags []string) error { +func pushUpdateAddTags(ctx context.Context, repo *models.Repository, gitRepo *git.Repository, tags []string) error { if len(tags) == 0 { return nil } diff --git a/modules/setting/federation.go b/modules/setting/federation.go new file mode 100644 index 0000000000000..c3000607894e7 --- /dev/null +++ b/modules/setting/federation.go @@ -0,0 +1,22 @@ +// Copyright 2021 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 setting + +import "code.gitea.io/gitea/modules/log" + +// Federation settings +var ( + Federation = struct { + Enabled bool + }{ + Enabled: true, + } +) + +func newFederationService() { + if err := Cfg.Section("federation").MapTo(&Federation); err != nil { + log.Fatal("Failed to map Federation settings: %v", err) + } +} diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 02f5cd85489f1..2133184cfc40d 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -9,7 +9,6 @@ import ( "encoding/base64" "fmt" "io" - "io/ioutil" "math" "net" "net/url" @@ -256,8 +255,8 @@ var ( ReactionMaxUserNum: 10, ThemeColorMetaTag: `#6cc644`, MaxDisplayFileSize: 8388608, - DefaultTheme: `gitea`, - Themes: []string{`gitea`, `arc-green`}, + DefaultTheme: `auto`, + Themes: []string{`auto`, `gitea`, `arc-green`}, Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`}, CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"}, @@ -391,11 +390,15 @@ var ( // Metrics settings Metrics = struct { - Enabled bool - Token string + Enabled bool + Token string + EnabledIssueByLabel bool + EnabledIssueByRepository bool }{ - Enabled: false, - Token: "", + Enabled: false, + Token: "", + EnabledIssueByLabel: false, + EnabledIssueByRepository: false, } // I18n settings @@ -764,7 +767,7 @@ func NewContext() { if len(trustedUserCaKeys) > 0 && SSH.AuthorizedPrincipalsEnabled { fname := sec.Key("SSH_TRUSTED_USER_CA_KEYS_FILENAME").MustString(filepath.Join(SSH.RootPath, "gitea-trusted-user-ca-keys.pem")) - if err := ioutil.WriteFile(fname, + if err := os.WriteFile(fname, []byte(strings.Join(trustedUserCaKeys, "\n")), 0600); err != nil { log.Fatal("Failed to create '%s': %v", fname, err) } @@ -899,6 +902,9 @@ func NewContext() { } RunUser = Cfg.Section("").Key("RUN_USER").MustString(user.CurrentUsername()) + // The following is a purposefully undocumented option. Please do not run Gitea as root. It will only cause future headaches. + // Please don't use root as a bandaid to "fix" something that is broken, instead the broken thing should instead be fixed properly. + unsafeAllowRunAsRoot := Cfg.Section("").Key("I_AM_BEING_UNSAFE_RUNNING_AS_ROOT").MustBool(false) RunMode = Cfg.Section("").Key("RUN_MODE").MustString("prod") // Does not check run user when the install lock is off. if InstallLock { @@ -908,6 +914,15 @@ func NewContext() { } } + // check if we run as root + if os.Getuid() == 0 { + if !unsafeAllowRunAsRoot { + // Special thanks to VLC which inspired the wording of this messaging. + log.Fatal("Gitea is not supposed to be run as root. Sorry. If you need to use privileged TCP ports please instead use setcap and the `cap_net_bind_service` permission") + } + log.Critical("You are running Gitea using the root user, and have purposely chosen to skip built-in protections around this. You have been warned against this.") + } + SSH.BuiltinServerUser = Cfg.Section("server").Key("BUILTIN_SSH_SERVER_USER").MustString(RunUser) newRepository() @@ -1030,7 +1045,7 @@ func loadInternalToken(sec *ini.Section) string { } defer fp.Close() - buf, err := ioutil.ReadAll(fp) + buf, err := io.ReadAll(fp) if err != nil { log.Fatal("Failed to read InternalTokenURI (%s): %v", uri, err) } @@ -1189,6 +1204,7 @@ func NewServices() { NewQueueService() newProject() newMimeTypeMap() + newFederationService() } // NewServicesForInstall initializes the services for install diff --git a/modules/storage/local.go b/modules/storage/local.go index 1329f722c285e..54e0d0563d2aa 100644 --- a/modules/storage/local.go +++ b/modules/storage/local.go @@ -7,7 +7,6 @@ package storage import ( "context" "io" - "io/ioutil" "net/url" "os" "path/filepath" @@ -76,7 +75,7 @@ func (l *LocalStorage) Save(path string, r io.Reader, size int64) (int64, error) if err := os.MkdirAll(l.tmpdir, os.ModePerm); err != nil { return 0, err } - tmp, err := ioutil.TempFile(l.tmpdir, "upload-*") + tmp, err := os.CreateTemp(l.tmpdir, "upload-*") if err != nil { return 0, err } diff --git a/modules/structs/nodeinfo.go b/modules/structs/nodeinfo.go new file mode 100644 index 0000000000000..6fd1eb624a77e --- /dev/null +++ b/modules/structs/nodeinfo.go @@ -0,0 +1,44 @@ +// Copyright 2021 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 structs + +// NodeInfo contains standardized way of exposing metadata about a server running one of the distributed social networks +type NodeInfo struct { + Version string `json:"version"` + Software NodeInfoSoftware `json:"software"` + Protocols []string `json:"protocols"` + Services NodeInfoServices `json:"services"` + OpenRegistrations bool `json:"openRegistrations"` + Usage NodeInfoUsage `json:"usage"` + Metadata struct{} `json:"metadata"` +} + +// NodeInfoSoftware contains Metadata about server software in use +type NodeInfoSoftware struct { + Name string `json:"name"` + Version string `json:"version"` + Repository string `json:"repository"` + Homepage string `json:"homepage"` +} + +// NodeInfoServices contains the third party sites this server can connect to via their application API +type NodeInfoServices struct { + Inbound []string `json:"inbound"` + Outbound []string `json:"outbound"` +} + +// NodeInfoUsage contains usage statistics for this server +type NodeInfoUsage struct { + Users NodeInfoUsageUsers `json:"users"` + LocalPosts int `json:"localPosts,omitempty"` + LocalComments int `json:"localComments,omitempty"` +} + +// NodeInfoUsageUsers contains statistics about the users of this server +type NodeInfoUsageUsers struct { + Total int `json:"total,omitempty"` + ActiveHalfyear int `json:"activeHalfyear,omitempty"` + ActiveMonth int `json:"activeMonth,omitempty"` +} diff --git a/modules/structs/notifications.go b/modules/structs/notifications.go index 675dcf76b127e..3fd9088ce3850 100644 --- a/modules/structs/notifications.go +++ b/modules/structs/notifications.go @@ -21,11 +21,13 @@ type NotificationThread struct { // NotificationSubject contains the notification subject (Issue/Pull/Commit) type NotificationSubject struct { - Title string `json:"title"` - URL string `json:"url"` - LatestCommentURL string `json:"latest_comment_url"` - Type NotifySubjectType `json:"type" binding:"In(Issue,Pull,Commit)"` - State StateType `json:"state"` + Title string `json:"title"` + URL string `json:"url"` + LatestCommentURL string `json:"latest_comment_url"` + HTMLURL string `json:"html_url"` + LatestCommentHTMLURL string `json:"latest_comment_html_url"` + Type NotifySubjectType `json:"type" binding:"In(Issue,Pull,Commit,Repository)"` + State StateType `json:"state"` } // NotificationCount number of unread notifications diff --git a/modules/structs/repo_branch.go b/modules/structs/repo_branch.go index efb4caecb0827..1f3bc04e86401 100644 --- a/modules/structs/repo_branch.go +++ b/modules/structs/repo_branch.go @@ -44,6 +44,7 @@ type BranchProtection struct { DismissStaleApprovals bool `json:"dismiss_stale_approvals"` RequireSignedCommits bool `json:"require_signed_commits"` ProtectedFilePatterns string `json:"protected_file_patterns"` + UnprotectedFilePatterns string `json:"unprotected_file_patterns"` // swagger:strfmt date-time Created time.Time `json:"created_at"` // swagger:strfmt date-time @@ -73,6 +74,7 @@ type CreateBranchProtectionOption struct { DismissStaleApprovals bool `json:"dismiss_stale_approvals"` RequireSignedCommits bool `json:"require_signed_commits"` ProtectedFilePatterns string `json:"protected_file_patterns"` + UnprotectedFilePatterns string `json:"unprotected_file_patterns"` } // EditBranchProtectionOption options for editing a branch protection @@ -97,4 +99,5 @@ type EditBranchProtectionOption struct { DismissStaleApprovals *bool `json:"dismiss_stale_approvals"` RequireSignedCommits *bool `json:"require_signed_commits"` ProtectedFilePatterns *string `json:"protected_file_patterns"` + UnprotectedFilePatterns *string `json:"unprotected_file_patterns"` } diff --git a/modules/svg/discover_nobindata.go b/modules/svg/discover_nobindata.go index 8d857551dc47e..e3f13ddf6c531 100644 --- a/modules/svg/discover_nobindata.go +++ b/modules/svg/discover_nobindata.go @@ -8,7 +8,7 @@ package svg import ( - "io/ioutil" + "os" "path/filepath" "code.gitea.io/gitea/modules/setting" @@ -20,7 +20,7 @@ func Discover() map[string]string { files, _ := filepath.Glob(filepath.Join(setting.StaticRootPath, "public", "img", "svg", "*.svg")) for _, file := range files { - content, err := ioutil.ReadFile(file) + content, err := os.ReadFile(file) if err == nil { filename := filepath.Base(file) svgs[filename[:len(filename)-4]] = string(content) diff --git a/modules/templates/dynamic.go b/modules/templates/dynamic.go index 4732fce421fa7..fde58a4a70017 100644 --- a/modules/templates/dynamic.go +++ b/modules/templates/dynamic.go @@ -9,7 +9,6 @@ package templates import ( "html/template" - "io/ioutil" "os" "path" "path/filepath" @@ -28,14 +27,14 @@ var ( // GetAsset returns asset content via name func GetAsset(name string) ([]byte, error) { - bs, err := ioutil.ReadFile(filepath.Join(setting.CustomPath, name)) + bs, err := os.ReadFile(filepath.Join(setting.CustomPath, name)) if err != nil && !os.IsNotExist(err) { return nil, err } else if err == nil { return bs, nil } - return ioutil.ReadFile(filepath.Join(setting.StaticRootPath, name)) + return os.ReadFile(filepath.Join(setting.StaticRootPath, name)) } // GetAssetNames returns assets list @@ -71,7 +70,7 @@ func Mailer() (*texttmpl.Template, *template.Template) { continue } - content, err := ioutil.ReadFile(path.Join(staticDir, filePath)) + content, err := os.ReadFile(path.Join(staticDir, filePath)) if err != nil { log.Warn("Failed to read static %s template. %v", filePath, err) @@ -100,7 +99,7 @@ func Mailer() (*texttmpl.Template, *template.Template) { continue } - content, err := ioutil.ReadFile(path.Join(customDir, filePath)) + content, err := os.ReadFile(path.Join(customDir, filePath)) if err != nil { log.Warn("Failed to read custom %s template. %v", filePath, err) diff --git a/modules/templates/helper.go b/modules/templates/helper.go index f8a93228ddc41..b935eb6cc097f 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -23,6 +23,7 @@ import ( "unicode" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/avatars" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/emoji" "code.gitea.io/gitea/modules/git" @@ -550,16 +551,16 @@ func SVG(icon string, others ...interface{}) template.HTML { // Avatar renders user avatars. args: user, size (int), class (string) func Avatar(item interface{}, others ...interface{}) template.HTML { - size, class := parseOthers(models.DefaultAvatarPixelSize, "ui avatar image", others...) + size, class := parseOthers(avatars.DefaultAvatarPixelSize, "ui avatar image", others...) if user, ok := item.(*models.User); ok { - src := user.RealSizedAvatarLink(size * models.AvatarRenderedSizeFactor) + src := user.AvatarLinkWithSize(size * avatars.AvatarRenderedSizeFactor) if src != "" { return AvatarHTML(src, size, class, user.DisplayName()) } } if user, ok := item.(*models.Collaborator); ok { - src := user.RealSizedAvatarLink(size * models.AvatarRenderedSizeFactor) + src := user.AvatarLinkWithSize(size * avatars.AvatarRenderedSizeFactor) if src != "" { return AvatarHTML(src, size, class, user.DisplayName()) } @@ -575,7 +576,7 @@ func AvatarByAction(action *models.Action, others ...interface{}) template.HTML // RepoAvatar renders repo avatars. args: repo, size(int), class (string) func RepoAvatar(repo *models.Repository, others ...interface{}) template.HTML { - size, class := parseOthers(models.DefaultAvatarPixelSize, "ui avatar image", others...) + size, class := parseOthers(avatars.DefaultAvatarPixelSize, "ui avatar image", others...) src := repo.RelAvatarLink() if src != "" { @@ -586,8 +587,8 @@ func RepoAvatar(repo *models.Repository, others ...interface{}) template.HTML { // AvatarByEmail renders avatars by email address. args: email, name, size (int), class (string) func AvatarByEmail(email string, name string, others ...interface{}) template.HTML { - size, class := parseOthers(models.DefaultAvatarPixelSize, "ui avatar image", others...) - src := models.SizedAvatarLink(email, size*models.AvatarRenderedSizeFactor) + size, class := parseOthers(avatars.DefaultAvatarPixelSize, "ui avatar image", others...) + src := avatars.GenerateEmailAvatarFastLink(email, size*avatars.AvatarRenderedSizeFactor) if src != "" { return AvatarHTML(src, size, class, name) @@ -911,13 +912,13 @@ func TrN(lang string, cnt interface{}, key1, keyN string) string { return keyN } -// MigrationIcon returns a Font Awesome name matching the service an issue/comment was migrated from +// MigrationIcon returns a SVG name matching the service an issue/comment was migrated from func MigrationIcon(hostname string) string { switch hostname { case "github.com": - return "fa-github" + return "octicon-mark-github" default: - return "fa-git-alt" + return "gitea-git" } } diff --git a/modules/templates/static.go b/modules/templates/static.go index ee20b2af3b3a5..fdd68c1e6a06b 100644 --- a/modules/templates/static.go +++ b/modules/templates/static.go @@ -9,7 +9,7 @@ package templates import ( "html/template" - "io/ioutil" + "io" "os" "path" "path/filepath" @@ -28,7 +28,7 @@ var ( // GetAsset get a special asset, only for chi func GetAsset(name string) ([]byte, error) { - bs, err := ioutil.ReadFile(filepath.Join(setting.CustomPath, name)) + bs, err := os.ReadFile(filepath.Join(setting.CustomPath, name)) if err != nil && !os.IsNotExist(err) { return nil, err } else if err == nil { @@ -103,7 +103,7 @@ func Mailer() (*texttmpl.Template, *template.Template) { continue } - content, err := ioutil.ReadFile(path.Join(customDir, filePath)) + content, err := os.ReadFile(path.Join(customDir, filePath)) if err != nil { log.Warn("Failed to read custom %s template. %v", filePath, err) @@ -130,7 +130,7 @@ func Asset(name string) ([]byte, error) { return nil, err } defer f.Close() - return ioutil.ReadAll(f) + return io.ReadAll(f) } func AssetNames() []string { diff --git a/modules/test/context_tests.go b/modules/test/context_tests.go index a15c74d82800b..8a4d2d8bce0eb 100644 --- a/modules/test/context_tests.go +++ b/modules/test/context_tests.go @@ -14,6 +14,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/web/middleware" @@ -52,7 +53,7 @@ func MockContext(t *testing.T, path string) *context.Context { // LoadRepo load a repo into a test context. func LoadRepo(t *testing.T, ctx *context.Context, repoID int64) { ctx.Repo = &context.Repository{} - ctx.Repo.Repository = models.AssertExistsAndLoadBean(t, &models.Repository{ID: repoID}).(*models.Repository) + ctx.Repo.Repository = db.AssertExistsAndLoadBean(t, &models.Repository{ID: repoID}).(*models.Repository) var err error ctx.Repo.Owner, err = models.GetUserByID(ctx.Repo.Repository.OwnerID) assert.NoError(t, err) @@ -77,7 +78,7 @@ func LoadRepoCommit(t *testing.T, ctx *context.Context) { // LoadUser load a user into a test context. func LoadUser(t *testing.T, ctx *context.Context, userID int64) { - ctx.User = models.AssertExistsAndLoadBean(t, &models.User{ID: userID}).(*models.User) + ctx.User = db.AssertExistsAndLoadBean(t, &models.User{ID: userID}).(*models.User) } // LoadGitRepo load a git repo into a test context. Requires that ctx.Repo has diff --git a/modules/web/route.go b/modules/web/route.go index 3c6513da626ce..9b08510264bfe 100644 --- a/modules/web/route.go +++ b/modules/web/route.go @@ -31,6 +31,7 @@ func Wrap(handlers ...interface{}) http.HandlerFunc { func(ctx *context.Context) goctx.CancelFunc, func(*context.APIContext), func(*context.PrivateContext), + func(*context.PrivateContext) goctx.CancelFunc, func(http.Handler) http.Handler: default: panic(fmt.Sprintf("Unsupported handler type: %#v", t)) @@ -59,6 +60,15 @@ func Wrap(handlers ...interface{}) http.HandlerFunc { if ctx.Written() { return } + case func(*context.PrivateContext) goctx.CancelFunc: + ctx := context.GetPrivateContext(req) + cancel := t(ctx) + if cancel != nil { + defer cancel() + } + if ctx.Written() { + return + } case func(ctx *context.Context): ctx := context.GetContext(req) t(ctx) diff --git a/options/license/Verbatim-man-pages b/options/license/Verbatim-man-pages new file mode 100644 index 0000000000000..8a10d8e6840df --- /dev/null +++ b/options/license/Verbatim-man-pages @@ -0,0 +1,21 @@ +Copyright (c) 0000, Obelix the Gaul . + +Permission is granted to make and distribute verbatim copies of this +manual provided the copyright notice and this permission notice are +preserved on all copies. + +Permission is granted to copy and distribute modified versions of +this manual under the conditions for verbatim copying, provided that +the entire resulting derived work is distributed under the terms of +a permission notice identical to this one. + +Since the Linux kernel and libraries are constantly changing, this +manual page may be incorrect or out-of-date. The author(s) assume +no responsibility for errors or omissions, or for damages resulting +from the use of the information contained herein. The author(s) may +not have taken the same level of care in the production of this +manual, which is licensed free of charge, as they might when working +professionally. + +Formatted or processed versions of this manual, if unaccompanied by +the source, must acknowledge the copyright and authors of this work. diff --git a/options/locale/locale_bg-BG.ini b/options/locale/locale_bg-BG.ini index 93a177ad7d0ff..29e4876cfbeae 100644 --- a/options/locale/locale_bg-BG.ini +++ b/options/locale/locale_bg-BG.ini @@ -902,7 +902,6 @@ settings.slack_username=Потребителско име settings.slack_icon_url=URL адрес на икона settings.discord_username=Потребителско име settings.discord_icon_url=URL адрес на икона -settings.slack_color=Цвят settings.event_send_everything=Всички събития settings.event_create=Създаване settings.event_delete=Изтриване diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 9261e5cd3d557..c16b23fe563b5 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -935,7 +935,6 @@ commit_graph=Graf commitů commit_graph.select=Vybrat větve commit_graph.hide_pr_refs=Skrýt požadavky na natažení commit_graph.monochrome=Černobílé -commit_graph.color=Barevné blame=Blame normal_view=Normální zobrazení line=řádek @@ -1155,11 +1154,6 @@ issues.action_milestone_no_select=Žádný milník issues.action_assignee=Zpracovatel issues.action_assignee_no_select=Bez zpracovatele issues.opened_by=otevřeno %[1]s uživatelem %[3]s -pulls.merged_by=od %[3]s sloučen %[1]s -pulls.merged_by_fake=od %[2]s sloučen %[1]s -issues.closed_by=od %[3]s uzavřen %[1]s -issues.opened_by_fake=od %[2]s otevřen %[1]s -issues.closed_by_fake=od %[2]s uzavřen %[1]s issues.previous=Předchozí issues.next=Další issues.open_title=otevřený @@ -1738,7 +1732,6 @@ settings.slack_username=Uživatelské jméno settings.slack_icon_url=URL ikony uživatele settings.discord_username=Uživatelské jméno settings.discord_icon_url=URL ikony -settings.slack_color=Barva settings.event_desc=Spuštěno na: settings.event_push_only=Události nahrání settings.event_send_everything=Všechny události diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 79ed26a518396..be78c2738b62e 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -345,8 +345,6 @@ reset_password.text=Bitte klicke innerhalb von %s auf folgenden Link, um register_success=Registrierung erfolgreich -issue_assigned.pull=@%[1]s hat dich im Repository %[3]s dem Pull Request %[2]s zugewiesen. -issue_assigned.issue=@%[1]s hat dich im Repository %[3]s dem Issue %[2]s zugewiesen. issue.x_mentioned_you=@%s hat dich erwähnt: issue.action.force_push=%[1]s hat %[3]s mit %[4]s auf %[2]s überschrieben. @@ -977,7 +975,6 @@ commit_graph=Commit graph commit_graph.select=Branches auswählen commit_graph.hide_pr_refs=Pull-Requests ausblenden commit_graph.monochrome=Monochrom -commit_graph.color=Bunt blame=Blame normal_view=Normale Ansicht line=zeile @@ -1197,11 +1194,6 @@ issues.action_milestone_no_select=Kein Meilenstein issues.action_assignee=Zuständig issues.action_assignee_no_select=Niemand zuständig issues.opened_by=%[1]s von %[3]s geöffnet -pulls.merged_by=von %[3]s %[1]s zusammengefügt -pulls.merged_by_fake=von %[2]s zusammengefügt %[1]s -issues.closed_by=von %[3]s %[1]s geschlossen -issues.opened_by_fake=von %[2]s %[1]s geöffnet -issues.closed_by_fake=von %[2]s %[1]s geschlossen issues.previous=Vorherige issues.next=Nächste issues.open_title=Offen @@ -1789,7 +1781,6 @@ settings.slack_username=Benutzername settings.slack_icon_url=Icon-URL settings.discord_username=Benutzername settings.discord_icon_url=Icon-URL -settings.slack_color=Farbe settings.event_desc=Auslösen bei: settings.event_push_only=Push-Events settings.event_send_everything=Alle Events diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index 22543a889f2d4..c843068cb3c62 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -266,10 +266,10 @@ forgot_password_title=Ξέχασα Τον Κωδικό Πρόσβασης forgot_password=Ξεχάσατε τον κωδικό πρόσβασης; sign_up_now=Χρειάζεστε λογαριασμό; Εγγραφείτε τώρα. sign_up_successful=Ο λογαριασμός δημιουργήθηκε με επιτυχία. -confirmation_mail_sent_prompt=Ένα νέο email επιβεβαίωσης έχει σταλεί στο %s. Παρακαλώ ελέγξτε τα εισερχόμενά σας μέσα στα επόμενα %s για να ολοκληρώσετε τη διαδικασία εγγραφής. +confirmation_mail_sent_prompt=Ένα νέο email επιβεβαίωσης έχει σταλεί στο %s. Παρακαλώ ελέγξτε τα εισερχόμενα σας μέσα στις επόμενες %s για να ολοκληρώσετε τη διαδικασία εγγραφής. must_change_password=Ενημερώστε τον κωδικό πρόσβασης σας allow_password_change=Απαιτείται από το χρήστη να αλλάξει τον κωδικό πρόσβασης (συνιστόμενο) -reset_password_mail_sent_prompt=Ένα email επιβεβαίωσης έχει σταλεί στο %s. Παρακαλώ ελέγξτε τα εισερχόμενά σας στα επόμενα %s για να ολοκληρώσετε τη διαδικασία ανάκτησης λογαριασμού. +reset_password_mail_sent_prompt=Ένα email επιβεβαίωσης έχει σταλεί στο %s. Παρακαλώ ελέγξτε τα εισερχόμενα σας στις επόμενες %s για να ολοκληρώσετε τη διαδικασία ανάκτησης λογαριασμού. active_your_account=Ενεργοποιήστε Το Λογαριασμό Σας account_activated=Ο λογαριασμός έχει ενεργοποιηθεί prohibit_login=Απαγορεύεται η Σύνδεση @@ -345,8 +345,8 @@ reset_password.text=Κάντε κλικ στον παρακάτω σύνδεσμ register_success=Επιτυχής εγγραφή -issue_assigned.pull=@%[1]s σας ανέθεσε στο Pull Request %[2]s στο αποθετήριο %[3]s. -issue_assigned.issue=@%[1]s σας ανέθεσε στο θέμα %[2]s στο αποθετήριο %[3]s. +issue_assigned.pull=@%[1]s σας έχει αναθέσει στο pull request %[2]s στο αποθετήριο %[3]s. +issue_assigned.issue=@%[1]s σας ανέθεσε το ζήτημα %[2]s στο αποθετήριο %[3]s. issue.x_mentioned_you=@%s σας ανέφερε: issue.action.force_push=%[1]s έκανε force-push το %[2]s από %[3]s σε %[4]s. @@ -569,7 +569,7 @@ add_new_email=Προσθήκη Νέας Διεύθυνσης Email add_new_openid=Προσθήκη Νέου OpenID URI add_email=Προσθήκη Διεύθυνσης Email add_openid=Προσθήκη OpenID URI -add_email_confirmation_sent=Ένα email επιβεβαίωσης έχει σταλεί στο '%s'. Παρακαλώ ελέγξτε τα εισερχόμενά σας στα επόμενα %s για να επιβεβαιώσετε τη διεύθυνση email σας. +add_email_confirmation_sent=Ένα email επιβεβαίωσης έχει σταλεί στο '%s'. Παρακαλώ ελέγξτε τα εισερχόμενα σας στις επόμενες %s για να επιβεβαιώσετε τη διεύθυνση email σας. add_email_success=Η νέα διεύθυνση email έχει προστεθεί. email_preference_set_success=Οι προτιμήσεις email έχουν οριστεί επιτυχώς. add_openid_success=Προστέθηκε η νέα διεύθυνση OpenID. @@ -969,6 +969,7 @@ file_view_rendered=Προβολή Απόδοσης file_view_raw=Προβολή Ακατέργαστου file_permalink=Permalink file_too_large=Το αρχείο είναι πολύ μεγάλο για να εμφανιστεί. +file_copy_permalink=Αντιγραφή Permalink video_not_supported_in_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει την ετικέτα HTML5 'video'. audio_not_supported_in_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει την ετικέτα HTML5 'audio'. stored_lfs=Αποθηκεύτηκε με το Git LFS @@ -977,7 +978,6 @@ commit_graph=Γράφημα Υποβολών commit_graph.select=Επιλογή κλάδων commit_graph.hide_pr_refs=Απόκρυψη Pull Requests commit_graph.monochrome=Μονόχρωμο -commit_graph.color=Έγχρωμο blame=Ευθύνη normal_view=Κανονική Προβολή line=γραμμή @@ -1197,11 +1197,11 @@ issues.action_milestone_no_select=Χωρίς ορόσημο issues.action_assignee=Αποδέκτης issues.action_assignee_no_select=Κανένας Αποδέκτης issues.opened_by=άνοιξαν %[1]s από %[3]s -pulls.merged_by=από %[3]s συγχωνεύτηκαν %[1]s -pulls.merged_by_fake=από %[2]s συγχωνεύτηκαν %[1]s -issues.closed_by=από %[3]s έκλεισε %[1]s -issues.opened_by_fake=από %[2]s άνοιξαν %[1]s -issues.closed_by_fake=από %[2]s έκλεισε %[1]s +pulls.merged_by=συγχώνευσε το %[1]s από %[3]s +pulls.merged_by_fake=συγχώνευσε το %[1]s από %[2]s +issues.closed_by=έκλεισε το %[1]s από %[3]s +issues.opened_by_fake=άνοιξε το %[1]s από %[2]s +issues.closed_by_fake=έκλεισε το %[1]s από %[2]s issues.previous=Προηγούμενο issues.next=Επόμενο issues.open_title=Ανοιχτό @@ -1386,6 +1386,8 @@ pulls.compare_changes=Νέο Pull Request pulls.compare_changes_desc=Επιλέξτε τον κλάδο που θα συγχωνευθεί και τον κλάδο από τον οποίο θα τραβηχτεί. pulls.compare_base=συγχώνευση σε pulls.compare_compare=τράβηγμα από +pulls.switch_comparison_type=Αλλαγή τύπου σύγκρισης +pulls.switch_head_and_base=Αλλαγή κεφαλής και βάσης pulls.filter_branch=Φιλτράρισμα κλάδου pulls.no_results=Δεν βρέθηκαν αποτελέσματα. pulls.nothing_to_compare=Αυτοί οι κλάδοι είναι όμοιοι. Δεν υπάρχει ανάγκη να δημιουργήσετε ένα pull request. @@ -1791,7 +1793,6 @@ settings.slack_username=Όνομα Χρήστη settings.slack_icon_url=URL Εικονιδίου settings.discord_username=Όνομα Χρήστη settings.discord_icon_url=URL Εικονιδίου -settings.slack_color=Χρώμα settings.event_desc=Ενεργοποίηση Σε: settings.event_push_only=Γεγονότα Push settings.event_send_everything=Όλα τα Γεγονότα @@ -1908,6 +1909,8 @@ settings.require_signed_commits=Απαιτούνται Υπογεγραμμέν settings.require_signed_commits_desc=Απόρριψη νέων υποβολών σε αυτόν τον κλάδο εάν είναι μη υπογεγραμμένες ή μη επαληθεύσιμες. settings.protect_protected_file_patterns=Προστατευμένα μοτίβα αρχείων (διαχωρισμένα με χαρακτήρα semicolon '\;'): settings.protect_protected_file_patterns_desc=Προστατευμένα αρχεία που δεν επιτρέπεται να αλλάξουν άμεσα, ακόμη και αν ο χρήστης έχει δικαίωμα να προσθέσει, να επεξεργαστεί ή να διαγράψει αρχεία σε αυτόν τον κλάδο. Πολλαπλά μοτίβα μπορούν να διαχορίζονται με χαρακτήρα semicolon ('\;'). Δείτε την τεκμηρίωση github.com/gobwas/glob για τη σύνταξη των μοτίβων. Παραδείγματα: .drone.yml, /docs/**/*.txt. +settings.protect_unprotected_file_patterns=Μη προστατευμένα μοτίβα αρχείων (διαχωρισμένα με ερωτηματικό '\;'): +settings.protect_unprotected_file_patterns_desc=Μη προστατευμένα αρχεία που επιτρέπεται να αλλάξουν απευθείας εάν ο χρήστης έχει πρόσβαση εγγραφής, παρακάμπτοντας τον περιορισμό για push. Πολλαπλά μοτίβα μπορούν να διαχωριστούν με ερωτηματικό ('\;'). Δείτε την τεκμηρίωση github.com/gobwas/glob για τη σύνταξη του μοτίβου. Παραδείγματα: .drone.yml, /docs/**/*.txt. settings.add_protected_branch=Ενεργοποίηση προστασίας settings.delete_protected_branch=Απενεργοποίηση προστασίας settings.update_protect_branch_success=Η προστασία κλάδου για τον κλάδο '%s' έχει ενημερωθεί. @@ -2011,6 +2014,8 @@ diff.file_byte_size=Μέγεθος diff.file_suppressed=Το diff αρχείου καταστέλλεται επειδή είναι πολύ μεγάλο diff.file_suppressed_line_too_long=Το diff αρχείων καταστέλλεται επειδή μία ή περισσότερες γραμμές είναι πολύ μεγάλες diff.too_many_files=Ορισμένα αρχεία δεν εμφανίστηκαν επειδή έχουν αλλάξει πάρα πολλά αρχεία σε αυτό το diff +diff.generated=δημιουργημένο +diff.vendored=εξωτερικό diff.comment.placeholder=Αφήστε ένα σχόλιο diff.comment.markdown_info=Υποστηρίζεται στυλ με markdown. diff.comment.add_single_comment=Προσθέστε ένα σχόλιο @@ -2417,6 +2422,7 @@ auths.attribute_name=Χαρακτηριστικό Ονόματος auths.attribute_surname=Χαρακτηριστικό Επωνύμου auths.attribute_mail=Χαρακτηριστικό Email auths.attribute_ssh_public_key=Χαρακτηριστικό Δημόσιου Κλειδιού SSH +auths.attribute_avatar=Παράμετρος Εικόνας auths.attributes_in_bind=Λήψη χαρακτηριστικών μέσα στο πλαίσιο του Bind DN auths.allow_deactivate_all=Επιτρέψτε σε ένα κενό αποτέλεσμα αναζήτησης να απενεργοποιήσει όλους τους χρήστες auths.use_paged_search=Χρήση Σελιδοποιημένης Αναζήτησης @@ -2454,6 +2460,8 @@ auths.oauth2_tokenURL=URL Διακριτικού auths.oauth2_authURL=URL Εξουσιοδότησης auths.oauth2_profileURL=URL Προφίλ auths.oauth2_emailURL=Email URL +auths.skip_local_two_fa=Παράλειψη τοπικού 2FA +auths.skip_local_two_fa_helper=Αφήνοντας μη-ορισμένο σημαίνει ότι οι τοπικοί χρήστες με ορισμένο 2FA θα πρέπει να πετύχουν το 2FA για να συνδεθούν auths.oauth2_tenant=Ένοικος auths.enable_auto_register=Ενεργοποίηση Αυτόματης Εγγραφής auths.sspi_auto_create_users=Αυτόματη δημιουργία χρηστών @@ -2726,7 +2734,7 @@ comment_issue=`σχολίασε στο ζήτημα %s#%[ comment_pull=`σχολίασε στο pull request %s#%[2]s` merge_pull_request=`συγχώνευσε το pull request %s#%[2]s` transfer_repo=μετέφερε το αποθετήριο %s σε %s -push_tag=ώθησε την ετικέτα %[4]s σε %[3]s +push_tag=ώθησε την ετικέτα %[4]s στο %[3]s delete_tag=διέγραψε την ετικέτα %[2]s από %[3]s delete_branch=διέγραψε το κλάδο %[2]s από %[3]s compare_branch=Σύγκριση diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 17701094d786d..bc03f86619412 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -96,6 +96,7 @@ error = Error error404 = The page you are trying to reach either does not exist or you are not authorized to view it. never = Never +color = Color [error] occurred = An error has occurred @@ -345,8 +346,8 @@ reset_password.text = Please click the following link to recover your account wi register_success = Registration successful -issue_assigned.pull = @%[1]s assigned you to the pull request %[2]s in repository %[3]s. -issue_assigned.issue = @%[1]s assigned you to the issue %[2]s in repository %[3]s. +issue_assigned.pull = @%[1]s assigned you to pull request %[2]s in repository %[3]s. +issue_assigned.issue = @%[1]s assigned you to issue %[2]s in repository %[3]s. issue.x_mentioned_you = @%s mentioned you: issue.action.force_push = %[1]s force-pushed the %[2]s from %[3]s to %[4]s. @@ -969,6 +970,7 @@ file_view_rendered = View Rendered file_view_raw = View Raw file_permalink = Permalink file_too_large = The file is too large to be shown. +file_copy_permalink = Copy Permalink 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 @@ -977,7 +979,6 @@ commit_graph = Commit Graph commit_graph.select = Select branches commit_graph.hide_pr_refs = Hide Pull Requests commit_graph.monochrome = Mono -commit_graph.color = Color blame = Blame normal_view = Normal View line = line @@ -1197,11 +1198,11 @@ issues.action_milestone_no_select = No milestone issues.action_assignee = Assignee issues.action_assignee_no_select = No assignee issues.opened_by = opened %[1]s by %[3]s -pulls.merged_by = by %[3]s merged %[1]s -pulls.merged_by_fake = by %[2]s merged %[1]s -issues.closed_by = by %[3]s closed %[1]s -issues.opened_by_fake = by %[2]s opened %[1]s -issues.closed_by_fake = by %[2]s closed %[1]s +pulls.merged_by = merged %[1]s by %[3]s +pulls.merged_by_fake = merged %[1]s by %[2]s +issues.closed_by = closed %[1]s by %[3]s +issues.opened_by_fake = opened %[1]s by %[2]s +issues.closed_by_fake = closed %[1]s by %[2]s issues.previous = Previous issues.next = Next issues.open_title = Open @@ -1386,6 +1387,8 @@ pulls.compare_changes = New Pull Request 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 +pulls.switch_comparison_type = Switch comparison type +pulls.switch_head_and_base = Switch head and base pulls.filter_branch = Filter branch pulls.no_results = No results found. pulls.nothing_to_compare = These branches are equal. There is no need to create a pull request. @@ -1791,7 +1794,6 @@ settings.slack_username = Username settings.slack_icon_url = Icon URL settings.discord_username = Username settings.discord_icon_url = Icon URL -settings.slack_color = Color settings.event_desc = Trigger On: settings.event_push_only = Push Events settings.event_send_everything = All Events @@ -1908,6 +1910,8 @@ settings.require_signed_commits = Require Signed Commits settings.require_signed_commits_desc = Reject pushes to this branch if they are unsigned or unverifiable. settings.protect_protected_file_patterns = Protected file patterns (separated using semicolon '\;'): settings.protect_protected_file_patterns_desc = Protected files that are not allowed to be changed directly even if user has rights to add, edit, or delete files in this branch. Multiple patterns can be separated using semicolon ('\;'). See github.com/gobwas/glob documentation for pattern syntax. Examples: .drone.yml, /docs/**/*.txt. +settings.protect_unprotected_file_patterns = Unprotected file patterns (separated using semicolon '\;'): +settings.protect_unprotected_file_patterns_desc = Unprotected files that are allowed to be changed directly if user has write access, bypassing push restriction. Multiple patterns can be separated using semicolon ('\;'). See github.com/gobwas/glob documentation for pattern syntax. Examples: .drone.yml, /docs/**/*.txt. settings.add_protected_branch = Enable protection settings.delete_protected_branch = Disable protection settings.update_protect_branch_success = Branch protection for branch '%s' has been updated. @@ -2419,6 +2423,7 @@ auths.attribute_name = First Name Attribute auths.attribute_surname = Surname Attribute auths.attribute_mail = Email Attribute auths.attribute_ssh_public_key = Public SSH Key Attribute +auths.attribute_avatar = Avatar Attribute auths.attributes_in_bind = Fetch Attributes in Bind DN Context auths.allow_deactivate_all = Allow an empty search result to deactivate all users auths.use_paged_search = Use Paged Search @@ -2456,6 +2461,8 @@ auths.oauth2_tokenURL = Token URL auths.oauth2_authURL = Authorize URL auths.oauth2_profileURL = Profile URL auths.oauth2_emailURL = Email URL +auths.skip_local_two_fa = Skip local 2FA +auths.skip_local_two_fa_helper = Leaving unset means local users with 2FA set will still have to pass 2FA to log on auths.oauth2_tenant = Tenant auths.enable_auto_register = Enable Auto Registration auths.sspi_auto_create_users = Automatically create users diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index fb989735994bf..1f1a443e43331 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -96,6 +96,7 @@ error=Error error404=La página a la que está intentando acceder o no existe o no está autorizado para verla. never=Nunca +color=Color [error] occurred=Se ha producido un error @@ -771,6 +772,7 @@ use_template=Utilizar esta plantilla clone_in_vsc=Clonar en VS Code download_zip=Descargar ZIP download_tar=Descargar TAR.GZ +download_bundle=Descargar BUNDLE generate_repo=Generar repositorio generate_from=Generar desde repo_desc=Descripción @@ -899,6 +901,12 @@ migrate.migrate=Migrar desde %s migrate.migrating=Migrando desde %s... migrate.migrating_failed=La migración desde %s ha fallado. migrate.migrating_failed.error=Error: %s +migrate.github.description=Migrar datos desde github.com u otra instancia de Github. +migrate.git.description=Migrar un repositorio sólo desde cualquier servicio Git. +migrate.gitlab.description=Migrar datos de gitlab.com u otra instancia de GitLab. +migrate.gitea.description=Migrar datos de gitea.com u otra instancia de Gitea. +migrate.gogs.description=Migrar datos de notabug.org u otra instancia de Gogs. +migrate.onedev.description=Migrar datos desde code.onedev.io u otra instancia de OneDev. migrate.migrating_git=Migrando datos de Git migrate.migrating_topics=Migrando Temas migrate.migrating_milestones=Migrando Hitos @@ -970,7 +978,6 @@ commit_graph=Gráfico de commits commit_graph.select=Seleccionar ramas commit_graph.hide_pr_refs=Ocultar Pull Requests commit_graph.monochrome=Mono -commit_graph.color=Color blame=Blame normal_view=Vista normal line=línea @@ -1190,11 +1197,6 @@ issues.action_milestone_no_select=Sin hito issues.action_assignee=Asignado a issues.action_assignee_no_select=Sin asignado issues.opened_by=abierta %[1]s por %[3]s -pulls.merged_by=por %[3]s fusionado %[1]s -pulls.merged_by_fake=por %[2]s fusionado %[1]s -issues.closed_by=por %[3]s cerrado %[1]s -issues.opened_by_fake=por %[2]s abierto %[1]s -issues.closed_by_fake=por %[2]s cerrado %[1]s issues.previous=Página Anterior issues.next=Página Siguiente issues.open_title=Abierta @@ -1325,6 +1327,8 @@ issues.dependency.remove=Eliminar issues.dependency.remove_info=Eliminar esta dependencia issues.dependency.added_dependency=`añadida una nueva dependencia %s` issues.dependency.removed_dependency=`eliminada una dependencia %s` +issues.dependency.pr_closing_blockedby=Cerrando este pull request es bloqueado por las siguientes incidencias +issues.dependency.issue_closing_blockedby=Cerrando esta incidencia esta bloqueado por las siguientes incidencias issues.dependency.issue_close_blocks=Esta incidencia bloquea el cierre de las siguientes incidencias issues.dependency.pr_close_blocks=Este pull request bloquea el cierre de las siguientes incidencias issues.dependency.issue_close_blocked=Necesita cerrar todos las incidencias que bloquean esta incidencia antes de que se puede cerrar. @@ -1459,6 +1463,8 @@ pulls.status_checks_failure=Algunas comprobaciones han fallado pulls.status_checks_error=Algunas comprobaciones reportaron errores pulls.status_checks_requested=Obligatorio pulls.status_checks_details=Detalles +pulls.update_branch=Actualizar rama por fusión +pulls.update_branch_rebase=Actualizar rama por cambio de base pulls.update_branch_success=La actualización de la rama ha finalizado correctamente pulls.update_not_allowed=No tiene permisos para actualizar esta rama pulls.outdated_with_base_branch=Esta rama está desactualizada con la rama base @@ -1780,7 +1786,6 @@ settings.slack_username=Nombre de usuario settings.slack_icon_url=URL de icono settings.discord_username=Usuario settings.discord_icon_url=URL de icono -settings.slack_color=Color settings.event_desc=Activar: settings.event_push_only=Eventos Push settings.event_send_everything=Todos los eventos @@ -2000,6 +2005,8 @@ diff.file_byte_size=Tamaño diff.file_suppressed=La diferencia del archivo ha sido suprimido porque es demasiado grande diff.file_suppressed_line_too_long=Las diferiencias del archivo han sido suprimidas porque una o mas lineas son muy largas diff.too_many_files=Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio +diff.generated=generado +diff.vendored=vendido diff.comment.placeholder=Deja un comentario diff.comment.markdown_info=Es posible estilizar con markdown. diff.comment.add_single_comment=Añadir solo comentario @@ -2166,12 +2173,15 @@ members.member_role=Rol del miembro: members.owner=Propietario members.member=Miembro members.remove=Eliminar +members.remove.detail=¿Destituir a %[1]s de %[2]s? members.leave=Abandonar +members.leave.detail=¿Irse de %s? members.invite_desc=Añadir un miembro nuevo a %s: members.invite_now=Invitar teams.join=Unirse teams.leave=Abandonar +teams.leave.detail=¿Irse de %s? teams.can_create_org_repo=Crear repositorios teams.can_create_org_repo_helper=Los miembros pueden crear nuevos repositorios en la organización. El creador obtendrá acceso al administrador del nuevo repositorio. teams.read_access=Acceso de Lectura diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index 4a440031370b9..922d12fc6620a 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -781,7 +781,6 @@ stored_lfs=ذخیره شده با GIT LFS symbolic_link=پیوند نمادین commit_graph=نمودار کامیت commit_graph.monochrome=مونو -commit_graph.color=رنگ blame=سرزنش normal_view=نمایش عادی line=خط @@ -950,8 +949,6 @@ issues.action_milestone_no_select=بدون نقطه عطف issues.action_assignee=مسئول رسیدگی issues.action_assignee_no_select=بدون مسئول رسیدگی issues.opened_by=%[1]s باز شده توسط %[3]s -issues.closed_by=%[1]s بوسیله %[3]s بسته شده است -issues.closed_by_fake=%[1]s بوسیله %[2]s بسته شده است issues.previous=قبلی issues.next=بعدی issues.open_title=باز @@ -1432,7 +1429,6 @@ settings.slack_username=نام‎کاربری settings.slack_icon_url=نشانی تمثال settings.discord_username=نام‎کاربری settings.discord_icon_url=نشانی تمثال -settings.slack_color=رنگ settings.event_desc=ماشه بر روی: settings.event_push_only=رویداد درج کردن settings.event_send_everything=همه رویدادها diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index 81534306c98b9..34221b76baddf 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -903,7 +903,6 @@ settings.secret=Salaus settings.slack_username=Käyttäjätunnus settings.slack_icon_url=Kuvakkeen URL settings.discord_username=Käyttäjätunnus -settings.slack_color=Väri settings.event_create=Luo settings.event_delete=Poista settings.event_release_desc=Julkaisu julkaistu, päivitetty tai poistettu varastosta. diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index f0943f835b933..bd583f1fbf09c 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -918,7 +918,6 @@ commit_graph=Graphique des révisions commit_graph.select=Sélectionner les branches commit_graph.hide_pr_refs=Masquer les demandes d'ajout commit_graph.monochrome=Monochrome -commit_graph.color=Couleur blame=Annotations normal_view=Vue normale line=ligne @@ -1137,11 +1136,6 @@ issues.action_milestone_no_select=Aucun jalon issues.action_assignee=Assigné à issues.action_assignee_no_select=Pas d'assignataire issues.opened_by=créé %[1]s par %[3]s -pulls.merged_by=par %[3]s fusionné %[1]s -pulls.merged_by_fake=par %[2]s fusionnés %[1]s -issues.closed_by=par %[3]s fermé %[1]s -issues.opened_by_fake=par %[2]s ouverts %[1]s -issues.closed_by_fake=par %[2]s fermé %[1]s issues.previous=Page Précédente issues.next=Page Suivante issues.open_title=Ouvert @@ -1712,7 +1706,6 @@ settings.slack_username=Nom d'utilisateur settings.slack_icon_url=URL de l'icône settings.discord_username=Nom d'utilisateur settings.discord_icon_url=URL de l'icône -settings.slack_color=Couleur settings.event_desc=Événement déclencheur : settings.event_push_only=Événements de poussée settings.event_send_everything=Tous les événements diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini index eca0f26bb0450..b6db458d4fdbe 100644 --- a/options/locale/locale_hu-HU.ini +++ b/options/locale/locale_hu-HU.ini @@ -1215,7 +1215,6 @@ settings.slack_username=Felhasználónév settings.slack_icon_url=Ikon URL settings.discord_username=Felhasználónév settings.discord_icon_url=Ikon URL -settings.slack_color=Szín settings.event_desc=Bekapcsolás ha: settings.event_push_only=Feltöltésekkor settings.event_send_everything=Összes eseményre diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini index 1f0debc712375..8f836a946b99b 100644 --- a/options/locale/locale_id-ID.ini +++ b/options/locale/locale_id-ID.ini @@ -981,7 +981,6 @@ settings.slack_username=Nama pengguna settings.slack_icon_url=Ikon URL settings.discord_username=Nama pengguna settings.discord_icon_url=URL ikon -settings.slack_color=Warna settings.event_create=Menciptakan settings.event_push=Dorong settings.event_repository=Repositori diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index d318de2c1459d..293852f54038c 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -846,7 +846,6 @@ symbolic_link=Link Simbolico commit_graph=Grafico dei commit commit_graph.select=Seleziona rami commit_graph.monochrome=Mono -commit_graph.color=Colore blame=Blame normal_view=Vista normale line=riga @@ -1055,8 +1054,6 @@ issues.action_milestone_no_select=Nessuna pietra miliare issues.action_assignee=Assegnatario issues.action_assignee_no_select=Nessun assegnatario issues.opened_by=aperto %[1]s da %[3]s -issues.closed_by=del %[3]s chiuso %[1]s -issues.closed_by_fake=della %[2]s chiusa %[1]s issues.previous=Pagina precedente issues.next=Pagina successiva issues.open_title=Aperto @@ -1567,7 +1564,6 @@ settings.slack_username=Nome utente settings.slack_icon_url=URL icona settings.discord_username=Nome utente settings.discord_icon_url=URL icona -settings.slack_color=Colore settings.event_desc=Attivato su: settings.event_push_only=Pusha eventi settings.event_send_everything=Tutti gli eventi diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 5a880a52ab0ee..606eea2016abf 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -96,6 +96,7 @@ error=エラー error404=アクセスしようとしたページは存在しないか、閲覧が許可されていません。 never=無し +color=色 [error] occurred=エラーが発生しました @@ -969,6 +970,7 @@ file_view_rendered=レンダリング表示 file_view_raw=Rawデータを見る file_permalink=パーマリンク file_too_large=このファイルは大きすぎるため、表示できません。 +file_copy_permalink=パーマリンクをコピー video_not_supported_in_browser=このブラウザはHTML5のvideoタグをサポートしていません。 audio_not_supported_in_browser=このブラウザーはHTML5のaudioタグをサポートしていません。 stored_lfs=Git LFSで保管されています @@ -977,7 +979,6 @@ commit_graph=コミットグラフ commit_graph.select=ブランチを選択 commit_graph.hide_pr_refs=プルリクエストを非表示 commit_graph.monochrome=モノクロ -commit_graph.color=カラー blame=Blame normal_view=通常表示 line=行 @@ -1197,11 +1198,11 @@ issues.action_milestone_no_select=マイルストーンなし issues.action_assignee=担当者 issues.action_assignee_no_select=担当者なし issues.opened_by=%[3]sが%[1]sに作成 -pulls.merged_by=%[3]sが作成、%[1]sにマージ -pulls.merged_by_fake=%[2]sが作成、%[1]sにマージ -issues.closed_by=%[3]sが作成、%[1]sにクローズ -issues.opened_by_fake=%[2]sが%[1]sにオープン -issues.closed_by_fake=%[2]sが作成、%[1]sにクローズ +pulls.merged_by=%[3]sが%[1]sにマージ +pulls.merged_by_fake=%[2]sが%[1]sにマージ +issues.closed_by=%[3]sが%[1]sにクローズ +issues.opened_by_fake=%[2]sが%[1]sに作成 +issues.closed_by_fake=%[2]sが%[1]sにクローズ issues.previous=前ページ issues.next=次ページ issues.open_title=オープン @@ -1386,6 +1387,8 @@ pulls.compare_changes=新規プルリクエスト pulls.compare_changes_desc=マージ先ブランチとプル元ブランチを選択。 pulls.compare_base=マージ先 pulls.compare_compare=プル元 +pulls.switch_comparison_type=比較の種類を切り替える +pulls.switch_head_and_base=ヘッドとベースを切り替える pulls.filter_branch=ブランチの絞り込み pulls.no_results=結果が見つかりませんでした。 pulls.nothing_to_compare=同じブランチ同士のため、 プルリクエストを作成する必要がありません。 @@ -1791,7 +1794,6 @@ settings.slack_username=ユーザー名 settings.slack_icon_url=アイコンのURL settings.discord_username=ユーザー名 settings.discord_icon_url=アイコンのURL -settings.slack_color=色 settings.event_desc=トリガー: settings.event_push_only=プッシュのイベント settings.event_send_everything=すべてのイベント @@ -1908,6 +1910,8 @@ settings.require_signed_commits=コミット署名必須 settings.require_signed_commits_desc=署名されていない場合、または署名が検証できなかった場合は、このブランチへのプッシュを拒否します。 settings.protect_protected_file_patterns=保護されるファイルのパターン (セミコロン'\;'で区切る): settings.protect_protected_file_patterns_desc=保護されたファイルは、このブランチにファイルを追加・編集・削除する権限を持つユーザーであっても、直接変更することができなくなります。 セミコロン('\;')で区切って複数のパターンを指定できます。 パターンの文法については github.com/gobwas/glob を参照してください。 例: .drone.yml, /docs/**/*.txt +settings.protect_unprotected_file_patterns=保護しないファイルのパターン (セミコロン'\;'で区切る): +settings.protect_unprotected_file_patterns_desc=保護しないファイルは、ユーザーに書き込み権限があればプッシュ制限をバイパスして直接変更できます。 セミコロン('\;')で区切って複数のパターンを指定できます。 パターンの文法については github.com/gobwas/glob を参照してください。 例: .drone.yml, /docs/**/*.txt settings.add_protected_branch=保護を有効にする settings.delete_protected_branch=保護を無効にする settings.update_protect_branch_success=ブランチ '%s' の保護を更新しました。 @@ -2011,6 +2015,8 @@ diff.file_byte_size=サイズ diff.file_suppressed=ファイル差分が大きすぎるため省略します diff.file_suppressed_line_too_long=長すぎる行があるためファイル差分は表示されません diff.too_many_files=変更されたファイルが多すぎるため、一部のファイルは表示されません +diff.generated=generated +diff.vendored=vendored diff.comment.placeholder=コメントを残す diff.comment.markdown_info=Markdownによる書式設定をサポートしています。 diff.comment.add_single_comment=単独のコメントを追加 @@ -2417,6 +2423,7 @@ auths.attribute_name=名 auths.attribute_surname=姓 auths.attribute_mail=メールアドレス auths.attribute_ssh_public_key=SSH公開鍵 +auths.attribute_avatar=アバター auths.attributes_in_bind=バインドDNのコンテクストから属性を取得する auths.allow_deactivate_all=サーチ結果が空のときは全ユーザーを非アクティブ化 auths.use_paged_search=ページ分割検索を使用 @@ -2454,6 +2461,8 @@ auths.oauth2_tokenURL=トークンURL auths.oauth2_authURL=認可URL auths.oauth2_profileURL=プロフィールURL auths.oauth2_emailURL=メールURL +auths.skip_local_two_fa=ローカルの2要素認証をスキップ +auths.skip_local_two_fa_helper=指定しない場合、2要素認証を設定しているローカルユーザーはログオン時に2要素認証を成功させる必要があります。 auths.oauth2_tenant=テナント auths.enable_auto_register=自動登録を有効にする auths.sspi_auto_create_users=自動的にユーザーを作成 diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini index de4a05e6cfd43..eb42e60d582b3 100644 --- a/options/locale/locale_ko-KR.ini +++ b/options/locale/locale_ko-KR.ini @@ -1067,7 +1067,6 @@ settings.slack_username=사용자 이름 settings.slack_icon_url=아이콘 URL settings.discord_username=사용자명 settings.discord_icon_url=아이콘 URL -settings.slack_color=색 settings.event_desc=트리거: settings.event_push_only=푸시 이벤트 settings.event_send_everything=모든 이벤트 diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 5af6c34b6eb7a..7bc8f348eb048 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -345,8 +345,6 @@ reset_password.text=Nospiediet uz saites, lai atjaunotu savu kontu lapā %s@%s pieminēja Jūs: issue.action.force_push=%[1]s veica piespiedu izmaiņu iesūtīšanu atzarā %[2]s no revīzijas %[3]s uz %[4]s. @@ -967,7 +965,6 @@ commit_graph=Revīziju grafs commit_graph.select=Izvēlieties atzarus commit_graph.hide_pr_refs=Paslēpt izmaiņu pieprasījumus commit_graph.monochrome=Melnbalts -commit_graph.color=Krāsains blame=Vainot normal_view=Parastais skats line=rinda @@ -1186,11 +1183,6 @@ issues.action_milestone_no_select=Nav atskaites punkta issues.action_assignee=Atbildīgais issues.action_assignee_no_select=Nav atbildīgā issues.opened_by=%[3]s atvēra %[1]s -pulls.merged_by=%[3]s sapludināja %[1]s -pulls.merged_by_fake=%[2]s sapludināja %[1]s -issues.closed_by=%[3]s aizvēra %[1]s -issues.opened_by_fake=%[2]s atvēra %[1]s -issues.closed_by_fake=%[2]s aizvēra %[1]s issues.previous=Iepriekšējā issues.next=Nākamā issues.open_title=Atvērta @@ -1772,7 +1764,6 @@ settings.slack_username=Lietotājvārds settings.slack_icon_url=Ikonas URL settings.discord_username=Lietotāja vārds settings.discord_icon_url=Ikonas URL -settings.slack_color=Krāsa settings.event_desc=Izsaukt notikumiem: settings.event_push_only=Izmaiņu nosūtīšanas notikumi settings.event_send_everything=Visus notikumus diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index 40d16843cc927..36c64c5460870 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -837,7 +837,6 @@ stored_lfs=Opgeslagen met Git LFS symbolic_link=Symbolic link commit_graph=Commit grafiek commit_graph.monochrome=Monochroom -commit_graph.color=Kleur blame=Blame normal_view=Normale weergave line=regel @@ -1044,8 +1043,6 @@ issues.action_milestone_no_select=Geen mijlpaal issues.action_assignee=Toegewezene issues.action_assignee_no_select=Geen verantwoordelijke issues.opened_by=%[1]s geopend door %[3]s -issues.closed_by=door %[3]s gesloten %[1]s -issues.closed_by_fake=met %[2]gesloten %[1]s issues.previous=Vorige issues.next=Volgende issues.open_title=Open @@ -1570,7 +1567,6 @@ settings.slack_username=Gebruikersnaam settings.slack_icon_url=Icoon URL settings.discord_username=Gebruikersnaam settings.discord_icon_url=Icoon URL -settings.slack_color=Kleur settings.event_desc=Trigger op: settings.event_push_only=Push Events settings.event_send_everything=Alle gebeurtenissen diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index 15db4b37c356f..c2fc7185f2d3d 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -779,7 +779,6 @@ stored_lfs=Przechowane za pomocą Git LFS symbolic_link=Dowiązanie symboliczne commit_graph=Wykres commitów commit_graph.monochrome=Monochromatyczny -commit_graph.color=Kolor blame=Wina normal_view=Zwykły widok line=wiersz @@ -972,8 +971,6 @@ issues.action_milestone_no_select=Brak kamieni milowych issues.action_assignee=Przypisany issues.action_assignee_no_select=Brak przypisania issues.opened_by=otworzone %[1]s przez %[3]s -issues.closed_by=przez %[3]s zamknięte %[1]s -issues.closed_by_fake=przez %[2]s zamknięte %[1]s issues.previous=Poprzedni issues.next=Następny issues.open_title=Otwarty @@ -1473,7 +1470,6 @@ settings.slack_username=Użytkownik settings.slack_icon_url=Adres URL ikony settings.discord_username=Nazwa użytkownika settings.discord_icon_url=Adres URL ikony -settings.slack_color=Kolor settings.event_desc=Wywołaj przy: settings.event_push_only=Wydarzeniach przepchnięcia settings.event_send_everything=Wszystkich wydarzeniach diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index e9f55fb4ddc54..ffebc65e5c794 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -923,7 +923,6 @@ commit_graph=Gráfico de commits commit_graph.select=Selecionar branches commit_graph.hide_pr_refs=Esconder Pull Requests commit_graph.monochrome=Monocromático -commit_graph.color=Cor blame=Anotar normal_view=Visão normal line=linha @@ -1136,8 +1135,6 @@ issues.action_milestone_no_select=Sem marco issues.action_assignee=Responsável issues.action_assignee_no_select=Sem responsável issues.opened_by=aberto por %[3]s %[1]s -pulls.merged_by=por %[3]s merge aplicado %[1]s -pulls.merged_by_fake=por %[2]s merge aplicado %[1]s issues.previous=Anterior issues.next=Próximo issues.open_title=Aberto @@ -1633,7 +1630,6 @@ settings.slack_username=Nome de usuário settings.slack_icon_url=URL do ícone settings.discord_username=Nome de usuário settings.discord_icon_url=URL do ícone -settings.slack_color=Cor settings.event_desc=Acionado em: settings.event_push_only=Eventos de push settings.event_send_everything=Todos os eventos @@ -1717,6 +1713,8 @@ settings.protect_approvals_whitelist_teams=Equipes com permissão de revisão: settings.dismiss_stale_approvals=Descartar aprovações obsoletas settings.dismiss_stale_approvals_desc=Quando novos commits que mudam o conteúdo do pull request são enviados para o branch, as antigas aprovações serão descartadas. settings.require_signed_commits=Exibir commits assinados +settings.protect_unprotected_file_patterns=Padrões de arquivos desprotegidos (separados usando ponto e vírgula '\;'): +settings.protect_unprotected_file_patterns_desc=Arquivos não protegidos que podem ser alterados diretamente se o usuário tiver acesso de gravação, ignorando as restrições de push. Vários padrões podem ser separados usando ponto e vírgula ('\;'). Veja github.com/gobwas/glob documentação para sintaxe de padrões. Exemplos: .drone.yml, /docs/**/*.txt. settings.add_protected_branch=Habilitar proteção settings.delete_protected_branch=Desabilitar proteção settings.update_protect_branch_success=Proteção do branch '%s' foi atualizada. @@ -1797,6 +1795,8 @@ diff.file_image_height=Altura diff.file_byte_size=Tamanho diff.file_suppressed=Diferenças do arquivo suprimidas por serem muito extensas diff.too_many_files=Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff +diff.generated=gerado +diff.vendored=externo diff.comment.placeholder=Deixe um comentário diff.comment.markdown_info=Estilo com markdown é suportado. diff.comment.add_single_comment=Adicionar um único comentário @@ -2049,6 +2049,7 @@ users.name=Nome de usuário users.activated=Ativado users.admin=Administrador users.restricted=Restrito +users.2fa=2FA users.repos=Repositórios users.created=Criado users.last_login=Último acesso @@ -2076,6 +2077,7 @@ users.delete_account=Excluir conta de usuário users.still_own_repo=Este usuário ainda possui um ou mais repositórios. Exclua ou transfira esses repositórios primeiro. users.still_has_org=Este usuário é membro de uma organização. Remova o usuário de qualquer organização primeiro. users.deletion_success=A conta de usuário foi excluída. +users.reset_2fa=Reinicializar 2FA emails.filter_sort.email=E-mail emails.not_updated=Falha ao atualizar o endereço de e-mail solicitado: %v @@ -2153,6 +2155,8 @@ auths.oauth2_tokenURL=URL do Token auths.oauth2_authURL=URL de Authorização 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.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 diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index c247c21438608..839affff11508 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -977,7 +977,6 @@ commit_graph=Gráfico de cometimentos commit_graph.select=Escolher ramos commit_graph.hide_pr_refs=Ocultar pedidos de integração commit_graph.monochrome=Monocromático -commit_graph.color=Colorido blame=Responsabilidade normal_view=Vista normal line=linha @@ -1197,11 +1196,6 @@ issues.action_milestone_no_select=Sem etapa issues.action_assignee=Responsável issues.action_assignee_no_select=Sem responsável issues.opened_by=aberta %[1]s por %[3]s -pulls.merged_by=de %[3]s integrado %[1]s -pulls.merged_by_fake=por %[2]s integrou %[1]s -issues.closed_by=de %[3]s fechada %[1]s -issues.opened_by_fake=de %[2]s aberto %[1]s -issues.closed_by_fake=de %[2]s fechada %[1]s issues.previous=Anterior issues.next=Seguinte issues.open_title=Aberta @@ -1791,7 +1785,6 @@ settings.slack_username=Nome de utilizador settings.slack_icon_url=URL do ícone settings.discord_username=Nome de utilizador settings.discord_icon_url=URL do ícone -settings.slack_color=Cor settings.event_desc=Despoletado por: settings.event_push_only=Eventos de envio settings.event_send_everything=Todos os eventos @@ -1908,6 +1901,8 @@ settings.require_signed_commits=Exigir cometimentos assinados settings.require_signed_commits_desc=Rejeitar envios para este ramo que não estejam assinados ou que não sejam validáveis. settings.protect_protected_file_patterns=Padrões de ficheiros protegidos (separados com ponto e vírgula '\;'): settings.protect_protected_file_patterns_desc=Ficheiros protegidos que não podem ser modificados, mesmo que o utilizador tenha direitos para adicionar, editar ou eliminar ficheiros neste ramo. Múltiplos padrões podem ser separados com ponto e vírgula ('\;'). Veja a documentação em github.com/gobwas/glob para ver a sintaxe. Exemplos: .drone.yml, /docs/**/*.txt. +settings.protect_unprotected_file_patterns=Padrões de ficheiros desprotegidos (separados com ponto e vírgula '\;'): +settings.protect_unprotected_file_patterns_desc=Ficheiros desprotegidos que podem ser modificados se o utilizador tiver direitos de escrita, contornando a restrição no envio. Múltiplos padrões podem ser separados com ponto e vírgula ('\;'). Veja a documentação em github.com/gobwas/glob para ver a sintaxe. Exemplos: .drone.yml, /docs/**/*.txt. settings.add_protected_branch=Habilitar salvaguarda settings.delete_protected_branch=Desabilitar salvaguarda settings.update_protect_branch_success=A salvaguarda do ramo '%s' foi modificada. @@ -2256,7 +2251,7 @@ dashboard.task.cancelled=Tarefa: %[1]s cancelada: %[3]s dashboard.task.error=Erro na tarefa: %[1]s: %[3]s dashboard.task.finished=Tarefa: %[1]s iniciada por %[2]s foi concluída dashboard.task.unknown=Tarefa desconhecida: %[1]s -dashboard.cron.started=Cron iniciado: %[1] +dashboard.cron.started=Cron iniciado: %[1]s dashboard.cron.process=Cron: %[1]s dashboard.cron.cancelled=Cron: %s cancelado: %[3]s dashboard.cron.error=Erro no cron: %s: %[3]s @@ -2322,7 +2317,7 @@ users.full_name=Nome completo users.activated=Operante users.admin=Admin. users.restricted=Restrita -users.2fa=A2F +users.2fa=Autenticação em dois passos users.repos=Repos. users.created=Criada users.last_login=Último acesso @@ -2351,7 +2346,7 @@ users.delete_account=Eliminar conta de utilizador users.still_own_repo=Este utilizador ainda possui um ou mais repositórios. Elimine ou transfira esses repositórios primeiro. users.still_has_org=Este utilizador é membro de uma organização. Remova, primeiro, o utilizador de todas as organizações. users.deletion_success=A conta de utilizador foi eliminada. -users.reset_2fa=Reinicializar 2FA +users.reset_2fa=Reinicializar a autenticação em dois passos emails.email_manage_panel=Gestão de endereços de email do utilizador emails.primary=Principal @@ -2454,6 +2449,8 @@ auths.oauth2_tokenURL=URL do código auths.oauth2_authURL=URL da autorização auths.oauth2_profileURL=URL do perfil auths.oauth2_emailURL=URL do email +auths.skip_local_two_fa=Ignorar a autenticação em dois passos local +auths.skip_local_two_fa_helper=Deixar esta opção desligada faz com que os utilizadores locais que tenham a autenticação em dois passos habilitada sejam obrigados a passar por ela para iniciar a sessão auths.oauth2_tenant=Locatário auths.enable_auto_register=Habilitar o registo automático auths.sspi_auto_create_users=Criar utilizadores automaticamente @@ -2501,7 +2498,7 @@ config.app_name=Título do sítio config.app_ver=Versão do Gitea config.app_url=URL base do Gitea config.custom_conf=Caminho do ficheiro de configuração -config.custom_file_root_path=Caminho personalizado para a raiz dos ficheiros +config.custom_file_root_path=Localização dos ficheiros personalizados config.domain=Domínio do servidor SSH config.offline_mode=Modo local config.disable_router_log=Desabilitar registos do encaminhador @@ -2714,7 +2711,7 @@ notices.delete_success=As notificações do sistema foram eliminadas. [action] create_repo=criou o repositório %s -rename_repo=renomeou o repositório de %[1]s para %[3] +rename_repo=renomeou o repositório de %[1]s para %[3]s commit_repo=enviou para %[3]s em %[4]s create_issue=`abriu a questão %s#%[2]s` close_issue=`fechou a questão %s#%[2]s` @@ -2740,7 +2737,7 @@ reject_pull_request=`sugeriu modificações para %s#%[2]s< publish_release=`lançou "%[4]s" à %[3]s` review_dismissed=`descartou a revisão de %[4]s para %[3]s#%[2]s` review_dismissed_reason=Motivo: -create_branch=criou o ramo %[3]s em %[4] +create_branch=criou o ramo %[3]s em %[4]s [tool] ago=há %s diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 1596b9347861b..376693b882161 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -96,6 +96,7 @@ error=Ошибка error404=Страница, которую вы пытаетесь открыть, либо не существует, либо вы не авторизованы для ее просмотра. never=Никогда +color=Цвет [error] occurred=Произошла ошибка @@ -334,7 +335,7 @@ activate_email.title=%s, пожалуйста, подтвердите ваш а activate_email.text=Пожалуйста, перейдите по ссылке, чтобы подтвердить ваш адрес электронной почты в течение %s: register_notify=Добро пожаловать на Gitea -register_notify.title=%[1], добро пожаловать в %[2] +register_notify.title=%[1]s, добро пожаловать в %[2]s register_notify.text_1=это письмо с вашим подтверждением регистрации в %s! register_notify.text_2=Теперь вы можете войти через логин: %s. register_notify.text_3=Если эта учетная запись была создана для вас, пожалуйста, сначала установите пароль. @@ -345,11 +346,12 @@ reset_password.text=Пожалуйста, перейдите по ссылке, register_success=Регистрация прошла успешно -issue_assigned.pull=@%[1] назначил вам запрос на слияние %[2] в репозитории %[3]. +issue_assigned.pull=@%[1]s назначил вам запрос на слияние %[2]s в репозитории %[3]s. issue_assigned.issue=@%[1]s назначил вам задачу %[2]s в репозитории %[3]s. issue.x_mentioned_you=@%s упомянул вас: issue.action.force_push=%[1]s форсировал отправку изменений %[2]s с %[3]s до %[4]s. +issue.action.push_1=@%[1]s отправил %[3]d изменение %[2]s issue.action.push_n=@%[1]s отправил %[3]d изменений %[2]s issue.action.close=@%[1]s закрыты #%[2]d. issue.action.reopen=@%[1]s переоткрыты #%[2]d. @@ -507,7 +509,7 @@ public_profile=Открытый профиль biography_placeholder=Расскажите немного о себе profile_desc=Ваш адрес электронной почты будет использован для уведомлений и других операций. password_username_disabled=Нелокальным пользователям запрещено изменение их имени пользователя. Для получения более подробной информации обратитесь к администратору сайта. -full_name=ФИО +full_name=Имя и фамилия website=Веб-сайт location=Местоположение update_theme=Обновить тему @@ -596,6 +598,7 @@ ssh_principal_been_used=Участник уже был добавлен на с gpg_key_id_used=Публичный GPG ключ с таким же идентификатором уже существует. gpg_no_key_email_found=Этот GPG ключ не соответствует ни одному активному адресу электронной почты, связанному с вашей учетной записью. Он по-прежнему может быть добавлен, если вы подписали указанный токен. gpg_key_matched_identities=Соответствующие идентификаторы: +gpg_key_matched_identities_long=Встроенные в этот ключ идентификаторы соответствуют следующим активным email-адресам этого пользователя и коммиты, соответствующие этим email-адресам могут быть проверены с помощью этого ключа. gpg_key_verified=Проверенный ключ gpg_key_verified_long=Ключ был проверен токеном и может быть использован для проверки коммитов, соответствующих любым активным адресом электронной почты этого пользователя в дополнение к любым соответствующим идентификаторам этого ключа. gpg_key_verify=Проверить @@ -766,6 +769,10 @@ fork_repo=Форкнуть репозиторий fork_from=Форк от fork_visibility_helper=Видимость форкнутого репозитория изменить нельзя. use_template=Использовать этот шаблон +clone_in_vsc=Клонировать в VS Code +download_zip=Скачать ZIP +download_tar=Скачать TAR.GZ +download_bundle=Скачать BUNDLE generate_repo=Создать репозиторий generate_from=Создать из repo_desc=Описание @@ -894,6 +901,12 @@ migrate.migrate=Миграция из %s migrate.migrating=Перенос из %s... migrate.migrating_failed=Перенос из %s не удался. migrate.migrating_failed.error=Ошибка: %s +migrate.github.description=Перенести данные с github.com или других экземпляров GitHub Enterprise Server. +migrate.git.description=Перенести только репозиторий из любого Git сервиса. +migrate.gitlab.description=Перенести данные с gitlab.com или других экземпляров GitLab. +migrate.gitea.description=Перенести данные с gitea.com или других экземпляров Gitea. +migrate.gogs.description=Перенести данные с notabug.org или других экземпляров Gogs. +migrate.onedev.description=Перенести данные с code.onedev.io или других экземпляров OneDev. migrate.migrating_git=Перенос Git данных migrate.migrating_topics=Миграция тем migrate.migrating_milestones=Миграция этапов @@ -965,7 +978,6 @@ commit_graph=Граф коммитов commit_graph.select=Выбрать ветку commit_graph.hide_pr_refs=Скрыть Pull Requests commit_graph.monochrome=Моно -commit_graph.color=Цвет blame=Вина normal_view=Обычный вид line=строка @@ -1033,6 +1045,7 @@ editor.require_signed_commit=Ветка ожидает подписанный к commits.desc=Просмотр истории изменений исходного кода. commits.commits=Коммитов commits.no_commits=Ничего общего в коммитах. '%s' и '%s' имеют совершенно разные истории. +commits.nothing_to_compare=Эти ветки одинаковы. commits.search=Поиск коммитов… commits.search.tooltip=Вы можете предварять ключевые слова словами "author:", "committer:", "after:", или "before:", например, "revert author:Alice before:2019-04-01". commits.find=Поиск @@ -1184,13 +1197,13 @@ issues.action_milestone_no_select=Нет этапа issues.action_assignee=Ответственный issues.action_assignee_no_select=Нет ответственного issues.opened_by=открыта %[1]s %[3]s -pulls.merged_by=на %[3]s мигрированных %[1]s -pulls.merged_by_fake=%[2]s мигрировал %[1]s -issues.closed_by=на %[3]s закрытых %[1]s -issues.opened_by_fake=%[2]s открыл(а) %[1]s -issues.closed_by_fake=%[2]s закрыл(а) %[1]s -issues.previous=Предыдущая страница -issues.next=Следующая страница +pulls.merged_by=слито %[1]s пользователем %[3]s +pulls.merged_by_fake=слито %[1]s пользователем %[2]s +issues.closed_by=закрыт %[1]s пользователем %[3]s +issues.opened_by_fake=открыт %[1]s пользователем %[2]s +issues.closed_by_fake=закрыт %[1]s пользователем %[2]s +issues.previous=Предыдущая +issues.next=Следующая issues.open_title=Открыто issues.closed_title=Закрыто issues.num_comments=комментариев: %d @@ -1214,8 +1227,8 @@ issues.reopened_at=`переоткрыл(а) эту проблему %[2]s` issues.ref_issue_from=`ссылка на эту проблему %[4]s %[2]s` issues.ref_pull_from=`ссылается на этот Pull Request %[4]s %[2]s` -issues.ref_closing_from=`ссылается на Pull Request %[4], который закроет эту задачу %[2]s` -issues.ref_reopening_from=`ссылается на Pull Request %[4], который вновь откроет эту задачу %[2]s` +issues.ref_closing_from=`ссылается на Pull Request %[4]s, который закроет эту задачу %[2]s` +issues.ref_reopening_from=`ссылается на Pull Request %[4]s, который вновь откроет эту задачу %[2]s` issues.ref_closed_from=`закрыл этот запрос %[4]s %[2]s` issues.ref_reopened_from=`переоткрыл эту задачу %[4]s %[2]s` issues.ref_from=`из %[1]s` @@ -1298,7 +1311,7 @@ issues.error_modifying_due_date=Не удалось изменить срок в issues.error_removing_due_date=Не удалось убрать срок выполнения. issues.push_commit_1=добавил(а) %d коммит %s issues.push_commits_n=добавил(а) %d коммитов %s -issues.force_push_codes=`принудительно залито %[1]s от %[2] к %[4]s %[6]s` +issues.force_push_codes=`принудительно залито %[1]s от %[2]s к %[4]s %[6]s` issues.due_date_form=гггг-мм-дд issues.due_date_form_add=Добавить срок выполнения issues.due_date_form_edit=Редактировать @@ -1319,6 +1332,8 @@ issues.dependency.remove=Удалить issues.dependency.remove_info=Удалить эту зависимость issues.dependency.added_dependency=`добавить новую зависимость %s` issues.dependency.removed_dependency=`убрал зависимость %s` +issues.dependency.pr_closing_blockedby=Закрытие этого Pull Request'а блокируется следующими задачами +issues.dependency.issue_closing_blockedby=Закрытие этой задачи блокируется следующими задачами issues.dependency.issue_close_blocks=Эта задача блокирует закрытие следующих задач issues.dependency.pr_close_blocks=Этот запрос на слияние блокирует закрытие следующих задач issues.dependency.issue_close_blocked=Вам необходимо закрыть все задачи, блокирующие эту задачу, прежде чем вы сможете её закрыть. @@ -1427,6 +1442,10 @@ pulls.no_merge_helper=Включите опции слияния в настро pulls.no_merge_wip=Данный Pull Request не может быть принят, поскольку он помечен как находящийся в разработке. pulls.no_merge_not_ready=Этот запрос не готов к слиянию, обратите внимания на ревью и проверки. pulls.no_merge_access=У вас нет права для слияния данного запроса. +pulls.merge_pull_request=Создать коммит на слияние +pulls.rebase_merge_pull_request=Выпольнить Rebase, а затем fast-forward слияние +pulls.rebase_merge_commit_pull_request=Выпольнить rebase, а затем создать коммит слияния +pulls.squash_merge_pull_request=Создать объединенный (squash) коммит pulls.merge_manually=Слито вручную pulls.merge_commit_id=ID коммита слияния pulls.require_signed_wont_sign=Данная ветка ожидает подписанные коммиты, однако слияние не будет подписано @@ -1449,6 +1468,8 @@ pulls.status_checks_failure=Некоторые проверки не удали pulls.status_checks_error=Некоторые проверки сообщили об ошибках pulls.status_checks_requested=Требуется pulls.status_checks_details=Информация +pulls.update_branch=Обновить ветку посредством слияния +pulls.update_branch_rebase=Обновить ветку через rebase pulls.update_branch_success=Обновление ветки выполнено успешно pulls.update_not_allowed=У вас недостаточно прав для обновления ветки pulls.outdated_with_base_branch=Эта ветка отстает от базовой ветки @@ -1770,7 +1791,6 @@ settings.slack_username=Имя пользователя settings.slack_icon_url=URL иконки settings.discord_username=Имя пользователя settings.discord_icon_url=URL иконки -settings.slack_color=Цвет settings.event_desc=На какие события этот веб-хук должен срабатывать? settings.event_push_only=Просто push событие settings.event_send_everything=Все события @@ -1834,6 +1854,7 @@ settings.add_telegram_hook_desc=Добавить интеграцию с Matrix в ваш репозиторий. settings.add_msteams_hook_desc=Добавить интеграцию с Microsoft Teams в ваш репозиторий. settings.add_feishu_hook_desc=Добавить интеграцию Feishu в ваш репозиторий. +settings.add_Wechat_hook_desc=Добавить интеграцию с Wechatwork в ваш репозиторий. settings.deploy_keys=Ключи развертывания settings.add_deploy_key=Добавить ключ развертывания settings.deploy_key_desc=Ключи развёртывания доступны только для чтения. Это не то же самое что и SSH-ключи аккаунта. @@ -1886,6 +1907,8 @@ settings.require_signed_commits=Требовать подписанные ком settings.require_signed_commits_desc=Отклонить push'ы в эту ветку, если они не подписаны или не проверены. settings.protect_protected_file_patterns=Защищённые шаблоны файлов (разделённые через '\;'): settings.protect_protected_file_patterns_desc=Защищенные файлы, которые не могут быть изменены напрямую, даже если пользователь имеет право добавлять, редактировать или удалять файлы в этой ветке. Шаблоны могут быть разделены точкой с запятой ('\;'). Смотрите github.com/gobwas/glob документацию для синтаксиса шаблонов. Например: .drone.yml, /docs/**/*.txt. +settings.protect_unprotected_file_patterns=Незащищённые шаблоны файлов (разделённые через '\;'): +settings.protect_unprotected_file_patterns_desc=Незащищенные файлы, которые могут быть изменены напрямую, если пользователь имеет доступ на запись, ограничения связанные с push здесь не влияют. Шаблоны могут быть разделены точкой с запятой ('\;'). Смотрите github.com/gobwas/glob документацию для синтаксиса шаблонов. Например: .drone.yml, /docs/**/*.txt. settings.add_protected_branch=Включить защиту settings.delete_protected_branch=Отключить защиту settings.update_protect_branch_success=Настройки защиты ветки '%s' были успешно изменены. @@ -1989,6 +2012,8 @@ diff.file_byte_size=Размер diff.file_suppressed=Разница между файлами не показана из-за своего большого размера diff.file_suppressed_line_too_long=Различия файлов скрыты, потому что одна или несколько строк слишком длинны diff.too_many_files=Некоторые файлы не были показаны из-за большого количества измененных файлов +diff.generated=сгенерированный +diff.vendored=поставляемый diff.comment.placeholder=Оставить комментарий diff.comment.markdown_info=Поддерживается синтаксис Markdown. diff.comment.add_single_comment=Добавить простой комментарий @@ -2155,12 +2180,15 @@ members.member_role=Роль участника: members.owner=Владелец members.member=Участник members.remove=Удалить +members.remove.detail=Исключить %[1]s из %[2]s? members.leave=Покинуть +members.leave.detail=Покинуть %s? members.invite_desc=Добавить нового участника в %s: members.invite_now=Пригласите сейчас teams.join=Объединить teams.leave=Выйти +teams.leave.detail=Покинуть %s? teams.can_create_org_repo=Создать репозитории teams.can_create_org_repo_helper=Участники могут создавать новые репозитории в организации. Создатель получит администраторский доступ к новому репозиторию. teams.read_access=Доступ на чтение @@ -2412,6 +2440,11 @@ auths.smtpport=SMTP-порт auths.allowed_domains=Разрешенные домены auths.allowed_domains_helper=Оставьте пустым, чтобы разрешить все домены. Разделите несколько доменов запятой (','). auths.skip_tls_verify=Пропустить проверку TLS +auths.force_smtps=Принудительный SMTPS +auths.force_smtps_helper=SMTPS всегда использует 465 порт. Установите это, что бы принудительно использовать SMTPS на других портах. (Иначе STARTTLS будет использоваться на других портах, если это поддерживается хостом.) +auths.helo_hostname=HELO Hostname +auths.helo_hostname_helper=Имя хоста отправляется с HELO. Оставьте поле пустым, чтобы отправить текущее имя хоста. +auths.disable_helo=Отключить HELO auths.pam_service_name=Имя службы PAM auths.pam_email_domain=Домен почты PAM (необязательно) auths.oauth2_provider=Поставщик OAuth2 @@ -2424,6 +2457,9 @@ auths.oauth2_tokenURL=URL токена auths.oauth2_authURL=URL авторизации auths.oauth2_profileURL=URL аккаунта auths.oauth2_emailURL=URL-адрес электронной почты +auths.skip_local_two_fa=Пропустить локальную двухфакторную аутентификацию +auths.skip_local_two_fa_helper=Если значение не задано, локальным пользователям с установленной двухфакторной аутентификацией все равно придется пройти двухфакторную аутентификацию для входа в систему +auths.oauth2_tenant=Tenant auths.enable_auto_register=Включить автоматическую регистрацию auths.sspi_auto_create_users=Автоматически создавать пользователей auths.sspi_auto_create_users_helper=Разрешить метод аутентификации SSPI для автоматического создания новых учётных записей для пользователей, которые впервые входят в систему @@ -2695,6 +2731,7 @@ comment_issue=`прокомментировал(а) задачу %s#%[2]s` merge_pull_request=`принял(а) Pull Request %s#%[2]s` transfer_repo=передал(а) репозиторий %s %s +push_tag=создал(а) тэг %[4]s в %[3]s delete_tag=удалил(а) тэг %[2]s из %[3]s delete_branch=удалил(а) ветку %[2]s из %[3]s compare_branch=Сравнить diff --git a/options/locale/locale_sr-SP.ini b/options/locale/locale_sr-SP.ini index 45971b8f287d4..b89902158fd21 100644 --- a/options/locale/locale_sr-SP.ini +++ b/options/locale/locale_sr-SP.ini @@ -421,7 +421,6 @@ settings.update_githook=Ажурирај Hook settings.secret=Тајна settings.slack_username=Корисничко име settings.slack_icon_url=URL адреса иконице -settings.slack_color=Боја settings.event_create=Креирај settings.event_pull_request=Захтев за спајање settings.update_webhook=Ажурирај Webhook diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index 6bb47ca0ee69f..6817ec1ae30d5 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -806,7 +806,6 @@ stored_lfs=Sparad med Git LFS symbolic_link=Symbolisk länk commit_graph=Commit-Graf commit_graph.monochrome=Mono -commit_graph.color=Färg blame=Blame normal_view=Normal vy line=rad @@ -1000,7 +999,6 @@ issues.action_milestone_no_select=Ingen Milsten issues.action_assignee=Tilldelad issues.action_assignee_no_select=Ingen tilldelad issues.opened_by=öppnade %[1]s av %[3]s -issues.closed_by_fake=av %[2]s stängde %[1]s issues.previous=Föregående issues.next=Nästa issues.open_title=Öppen @@ -1445,7 +1443,6 @@ settings.slack_username=Användarnamn settings.slack_icon_url=URL för ikon settings.discord_username=Användarnamn settings.discord_icon_url=URL för ikon -settings.slack_color=Färg settings.event_desc=Trigga vid: settings.event_push_only=Push Events settings.event_send_everything=Alla events diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 043e4b63c65f7..9bddffd2ca5ae 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -345,8 +345,6 @@ reset_password.text=Hesabınızı kurtarmak için lütfen %s içinde link register_success=Kayıt başarılı -issue_assigned.pull=@%[1]s sizi %[3]s deposundaki %[2]s değişiklik isteğine atadı. -issue_assigned.issue=@%[1]s sizi %[3]s deposundaki %[2]s konusuna atadı. issue.x_mentioned_you=@%s sizden bahsetti: issue.action.force_push=%[1]s %[3]s den %[2]s i %[4]s e zorla gönderdi. @@ -955,7 +953,6 @@ commit_graph=İşleme Grafiği commit_graph.select=Dalları seç commit_graph.hide_pr_refs=Değişiklik İsteklerini Gizle commit_graph.monochrome=Siyah Beyaz -commit_graph.color=Renkli blame=Suçlama normal_view=Normal Görünüm line=satır @@ -1175,11 +1172,6 @@ issues.action_milestone_no_select=Kilometre Taşı Yok issues.action_assignee=Atanan issues.action_assignee_no_select=Atanan yok issues.opened_by=%[3]s tarafından %[1]s açıldı -pulls.merged_by=%[1]s %[3]s tarafından açılan istek birleştirildi -pulls.merged_by_fake=%[2]s tarafından açılan istek %[1]s birleştirildi -issues.closed_by=%[1]s %[3]s tarafından kapatıldı -issues.opened_by_fake=%[1]s %[2]s tarafından açıldı -issues.closed_by_fake=%[1]s %[2]s tarafından kapatıldı issues.previous=Önceki issues.next=Sonraki issues.open_title=Açık @@ -1761,7 +1753,6 @@ settings.slack_username=Kullanıcı Adı settings.slack_icon_url=Simge Bağlantısı settings.discord_username=Kullanıcı adı settings.discord_icon_url=Simge URL'si -settings.slack_color=Renk settings.event_desc=Tetikleyici Açık: settings.event_push_only=İtme Olayları settings.event_send_everything=Tüm Olaylar diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 15555f315c14f..1d37fb345a5b1 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -341,8 +341,6 @@ reset_password.text=Перейдіть за цим посиланням, щоб register_success=Реєстрація успішна -issue_assigned.pull=@%[1]s призначив вам запит на злиття %[2]s в репозиторії %[3]s. -issue_assigned.issue=@%[1]s призначив вам проблему %[2]s у репозиторії %[3]s. issue.x_mentioned_you=@%s згадав вас: issue.action.force_push=%[1]s force-pushed %[2]s з %[3]s в %[4]s. @@ -947,7 +945,6 @@ commit_graph=Графік комітів commit_graph.select=Виберіть гілки commit_graph.hide_pr_refs=Приховати запити на злиття commit_graph.monochrome=Монохром -commit_graph.color=Колір blame=Звинувачення normal_view=Звичайний вигляд line=рядок @@ -1166,11 +1163,6 @@ issues.action_milestone_no_select=Етап відсутній issues.action_assignee=Виконавець issues.action_assignee_no_select=Немає виконавеця issues.opened_by=%[1]s відкрито %[3]s -pulls.merged_by=до %[3] злито %[1]s -pulls.merged_by_fake=%[2]s об'єднаний %[1]s -issues.closed_by=закрито %[3]s %[1]s -issues.opened_by_fake=%[2]s відкрив(ла) %[1]s -issues.closed_by_fake=закрито %[2]s %[1]s issues.previous=Попередній issues.next=Далі issues.open_title=Відкрито @@ -1751,7 +1743,6 @@ settings.slack_username=Ім'я кристувача settings.slack_icon_url=URL іконки settings.discord_username=Ім'я кристувача settings.discord_icon_url=URL іконки -settings.slack_color=Колір settings.event_desc=Тригер: settings.event_push_only=Push події settings.event_send_everything=Всі події @@ -2687,7 +2678,6 @@ mirror_sync_delete=синхронізовано й видалено посила approve_pull_request=`схвалив %s#%[2]s` reject_pull_request=`запропонував зміни до %s#%[2]s` publish_release=`опублікував випуск "%[4]s" з %[3]s` -review_dismissed=`відхилений відгук від %[4] у %[3]s#%[2]s` review_dismissed_reason=Причина: create_branch=створено гілку %[3]s у %[4]s diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 28ee68d194d1a..775891da17a0d 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -345,8 +345,6 @@ reset_password.text=请点击以下链接,恢复你在 %s 的账户: register_success=注册成功 -issue_assigned.pull=@%[1]s 已将代码库 %[3]s 中的合并请求 %[2]s 指派给您 -issue_assigned.issue=@%[1]s 已将代码库 %[3]s 中的工单 %[2]s 指派给您 issue.x_mentioned_you=@%s 提到了您: issue.action.force_push=%[1]s 强制从 %[3]s 推送 %[2]s 至 [4]s。 @@ -977,7 +975,6 @@ commit_graph=提交图 commit_graph.select=选择分支 commit_graph.hide_pr_refs=隐藏合并请求 commit_graph.monochrome=黑白 -commit_graph.color=颜色 blame=Blame normal_view=普通视图 line=行 @@ -1197,11 +1194,6 @@ issues.action_milestone_no_select=无里程碑 issues.action_assignee=指派人筛选 issues.action_assignee_no_select=未指派 issues.opened_by=由 %[3]s 于 %[1]s创建 -pulls.merged_by=%[3]s 合并于 %[1]s -pulls.merged_by_fake=%[2]s 合并于 %[1]s -issues.closed_by=%[3]s 关闭于 %[1]s -issues.opened_by_fake=%[2]s 创建于 %[1]s -issues.closed_by_fake=%[2]s 关闭于 %[1]s issues.previous=上一页 issues.next=下一页 issues.open_title=开启中 @@ -1791,7 +1783,6 @@ settings.slack_username=服务名称 settings.slack_icon_url=图标 URL settings.discord_username=用户名 settings.discord_icon_url=图标 URL -settings.slack_color=颜色代码 settings.event_desc=触发条件: settings.event_push_only=推送事件 settings.event_send_everything=所有事件 @@ -1908,6 +1899,8 @@ settings.require_signed_commits=需要签名提交 settings.require_signed_commits_desc=拒绝推送未签名或无法验证的提交到分支 settings.protect_protected_file_patterns=受保护的文件模式(使用分号分隔) settings.protect_protected_file_patterns_desc=即使用户有权在此分支中添加、编辑或删除文件,也不允许直接更改受保护文件。 可以使用分号分隔多个模式 ('\;')。语法文档见 github.com/gobwas/glob。示例:.drone.yml/docs/**/*.txt。 +settings.protect_unprotected_file_patterns=不受保护的文件模式 (使用分号 '\;' 分隔): +settings.protect_unprotected_file_patterns_desc=允许有写入权限的用户进行直接更改的不受保护的文件,可以绕过推送限制。可用分号 ('\;') 分隔多个模式。模式语法参见 github.com/gobwas/glob文档。示例:.drone.yml,/docs/**/*.txt。 settings.add_protected_branch=启用保护 settings.delete_protected_branch=禁用保护 settings.update_protect_branch_success=分支 "%s" 的分支保护已更新。 @@ -2011,6 +2004,8 @@ diff.file_byte_size=大小 diff.file_suppressed=文件差异内容过多而无法显示 diff.file_suppressed_line_too_long=文件差异因一行或多行过长而隐藏 diff.too_many_files=部分文件因为文件数量过多而无法显示 +diff.generated=自动生成的 +diff.vendored=vendored diff.comment.placeholder=留下评论 diff.comment.markdown_info=支持使用Markdown格式。 diff.comment.add_single_comment=添加单条评论 @@ -2454,6 +2449,8 @@ auths.oauth2_tokenURL=令牌 URL auths.oauth2_authURL=授权 URL auths.oauth2_profileURL=Profile URL auths.oauth2_emailURL=电子邮件 URL +auths.skip_local_two_fa=跳过本地两步验证 +auths.skip_local_two_fa_helper=不设置意味着设置了两步验证的本地用户仍然需要通过两步验证才能登录 auths.oauth2_tenant=租户 auths.enable_auto_register=允许用户自动注册 auths.sspi_auto_create_users=自动创建用户 diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini index 54a3420978acf..ac557b2c6d006 100644 --- a/options/locale/locale_zh-HK.ini +++ b/options/locale/locale_zh-HK.ini @@ -504,7 +504,6 @@ settings.update_githook=更新 Hook 設定 settings.secret=金鑰文本 settings.slack_username=服務名稱 settings.slack_icon_url=圖標 URL -settings.slack_color=顏色代碼 settings.event_create=建立 settings.event_push=推送 settings.event_pull_request=合併請求 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 89a4fe556b8da..b4167e948d531 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -12,7 +12,7 @@ website=網站 version=版本 powered_by=技術提供: %s page=頁面 -template=範本 +template=模板 language=語言 notifications=通知 active_stopwatch=進行中的時間追蹤 @@ -96,6 +96,7 @@ error=錯誤 error404=您正嘗試訪問的頁面 不存在您尚未被授權 查看該頁面。 never=從來沒有 +color=顏色 [error] occurred=發生錯誤 @@ -118,7 +119,7 @@ license_desc=取得 安裝指南再來調整設定。 -requite_db_desc=Gitea 必須搭配 MySQL、PostgreSQL、MSSQL 或 SQLite3 資料庫使用。 +requite_db_desc=Gitea 必須搭配 MySQL、PostgreSQL、MSSQL、SQLite3 等資料庫使用。 db_title=資料庫設定 db_type=資料庫類型 host=主機 @@ -969,6 +970,7 @@ file_view_rendered=檢視渲染圖 file_view_raw=查看原始文件 file_permalink=永久連結 file_too_large=檔案太大,無法顯示。 +file_copy_permalink=複製固定連結 video_not_supported_in_browser=您的瀏覽器不支援使用 HTML5 播放影片。 audio_not_supported_in_browser=您的瀏覽器不支援 HTML5 的「audio」標籤 stored_lfs=已使用 Git LFS 儲存 @@ -977,7 +979,6 @@ commit_graph=提交線圖 commit_graph.select=選擇分支 commit_graph.hide_pr_refs=隱藏合併請求 commit_graph.monochrome=單色 -commit_graph.color=彩色 blame=Blame normal_view=標準檢視 line=行 @@ -1196,12 +1197,12 @@ issues.action_milestone=里程碑 issues.action_milestone_no_select=無里程碑 issues.action_assignee=成員 issues.action_assignee_no_select=沒有成員 -issues.opened_by=由 %[3]s 於 %[1]s建立 -pulls.merged_by=由 %[3]s 建立,%[1]s合併 -pulls.merged_by_fake=由 %[2]s 建立,%[1]s合併 -issues.closed_by=由 %[3]s 建立,%[1]s關閉 -issues.opened_by_fake=由 %[2]s 建立,%[1]s開放 -issues.closed_by_fake=由 %[2]s 建立,%[1]s關閉 +issues.opened_by=建立於 %[1]s 由 %[3]s +pulls.merged_by=合併於 %[1]s,由 %[3]s 建立 +pulls.merged_by_fake=合併於 %[1]s,由 %[2]s 建立 +issues.closed_by=關閉於 %[1]s,由 %[3]s 建立 +issues.opened_by_fake=建立於 %[1]s 由 %[2]s +issues.closed_by_fake=關閉於 %[1]s,由 %[2]s 建立 issues.previous=上一頁 issues.next=下一頁 issues.open_title=開放中 @@ -1386,6 +1387,8 @@ pulls.compare_changes=建立合併請求 pulls.compare_changes_desc=選擇合併的目標分支和來源分支。 pulls.compare_base=合併到 pulls.compare_compare=拉取自 +pulls.switch_comparison_type=切換比較類型 +pulls.switch_head_and_base=切換 head 和 base pulls.filter_branch=過濾分支 pulls.no_results=未找到結果 pulls.nothing_to_compare=這些分支的內容相同,無需建立合併請求。 @@ -1791,7 +1794,6 @@ settings.slack_username=服務名稱 settings.slack_icon_url=圖標 URL settings.discord_username=使用者名稱 settings.discord_icon_url=Icon URL -settings.slack_color=顏色代碼 settings.event_desc=觸發條件: settings.event_push_only=推送事件 settings.event_send_everything=所有事件 @@ -1907,7 +1909,9 @@ settings.dismiss_stale_approvals_desc=當新的提交有修改到合併請求的 settings.require_signed_commits=僅接受經簽署的提交 settings.require_signed_commits_desc=拒絕未經簽署或未經驗證的提交推送到此分支。 settings.protect_protected_file_patterns=受保護的檔案模式(以分號區隔「\;」): -settings.protect_protected_file_patterns_desc=即便使用者有權限新增、修改和刪除此分支的檔案,仍不允許直接修改受保護的檔案。可以用半形分號「\;」分隔多個模式。請於github.com/gobwas/glob 文件查看模式格式。範例:.drone.yml, /docs/**/*.txt。 +settings.protect_protected_file_patterns_desc=即便使用者有權限新增、修改、刪除此分支的檔案,仍不允許直接修改受保護的檔案。可以用半形分號「\;」分隔多個模式。請於github.com/gobwas/glob 文件查看模式格式。範例:.drone.yml, /docs/**/*.txt。 +settings.protect_unprotected_file_patterns=未受保護的檔案模式(以分號區隔「\;」): +settings.protect_unprotected_file_patterns_desc=當使用者有寫入權限時,可繞過推送限制,直接修改未受保護的檔案。可以用半形分號「\;」分隔多個模式。請於github.com/gobwas/glob 文件查看模式格式。範例:.drone.yml, /docs/**/*.txt。 settings.add_protected_branch=啟用保護 settings.delete_protected_branch=停用保護 settings.update_protect_branch_success=已更新「%s」的分支保護。 @@ -2011,6 +2015,8 @@ diff.file_byte_size=大小 diff.file_suppressed=檔案差異因為檔案過大而無法顯示 diff.file_suppressed_line_too_long=檔案差異因為一行或多行太長而無法顯示 diff.too_many_files=本差異變更的檔案數量過多導致部分檔案未顯示 +diff.generated=generated +diff.vendored=vendored diff.comment.placeholder=留言... diff.comment.markdown_info=支援 markdown 格式。 diff.comment.add_single_comment=加入單獨的留言 @@ -2417,6 +2423,7 @@ auths.attribute_name=名字屬性 auths.attribute_surname=姓氏屬性 auths.attribute_mail=電子郵件屬性 auths.attribute_ssh_public_key=SSH 公鑰屬性 +auths.attribute_avatar=大頭貼屬性 auths.attributes_in_bind=從 Bind DN 中取得屬性資訊 auths.allow_deactivate_all=允許在搜尋結果為空白時停用所有使用者帳戶 auths.use_paged_search=使用分頁查詢 @@ -2454,6 +2461,8 @@ auths.oauth2_tokenURL=Token URL auths.oauth2_authURL=授權 URL auths.oauth2_profileURL=個人資料 URL auths.oauth2_emailURL=電子郵件 URL +auths.skip_local_two_fa=跳過本地兩步驟驗證 +auths.skip_local_two_fa_helper=保持未設定代表使用兩步驟驗證的本地使用者仍然需要通過兩步驟驗證才能登入 auths.oauth2_tenant=租戶 auths.enable_auto_register=允許授權用戶自動註冊 auths.sspi_auto_create_users=自動建立使用者 diff --git a/public/img/svg/gitea-git.svg b/public/img/svg/gitea-git.svg index ed592a6bb5f78..f0d692251dc9e 100644 --- a/public/img/svg/gitea-git.svg +++ b/public/img/svg/gitea-git.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index e5a75da759ea2..2d585b60401ba 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -11,6 +11,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/log" @@ -27,12 +28,12 @@ func parseLoginSource(ctx *context.APIContext, u *models.User, sourceID int64, l return } - source, err := models.GetLoginSourceByID(sourceID) + source, err := login.GetSourceByID(sourceID) if err != nil { - if models.IsErrLoginSourceNotExist(err) { + if login.IsErrSourceNotExist(err) { ctx.Error(http.StatusUnprocessableEntity, "", err) } else { - ctx.Error(http.StatusInternalServerError, "GetLoginSourceByID", err) + ctx.Error(http.StatusInternalServerError, "login.GetSourceByID", err) } return } @@ -74,7 +75,7 @@ func CreateUser(ctx *context.APIContext) { Passwd: form.Password, MustChangePassword: true, IsActive: true, - LoginType: models.LoginPlain, + LoginType: login.Plain, } if form.MustChangePassword != nil { u.MustChangePassword = *form.MustChangePassword diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index e74ff40995140..0a967e3c5a78f 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -87,7 +87,6 @@ import ( "code.gitea.io/gitea/services/forms" "gitea.com/go-chi/binding" - "gitea.com/go-chi/session" "github.com/go-chi/cors" ) @@ -547,20 +546,11 @@ func bind(obj interface{}) http.HandlerFunc { } // Routes registers all v1 APIs routes to web application. -func Routes() *web.Route { +func Routes(sessioner func(http.Handler) http.Handler) *web.Route { var m = web.NewRoute() - m.Use(session.Sessioner(session.Options{ - Provider: setting.SessionConfig.Provider, - ProviderConfig: setting.SessionConfig.ProviderConfig, - CookieName: setting.SessionConfig.CookieName, - CookiePath: setting.SessionConfig.CookiePath, - Gclifetime: setting.SessionConfig.Gclifetime, - Maxlifetime: setting.SessionConfig.Maxlifetime, - Secure: setting.SessionConfig.Secure, - SameSite: setting.SessionConfig.SameSite, - Domain: setting.SessionConfig.Domain, - })) + m.Use(sessioner) + m.Use(securityHeaders()) if setting.CORSConfig.Enabled { m.Use(cors.Handler(cors.Options{ @@ -590,6 +580,9 @@ func Routes() *web.Route { }) } m.Get("/version", misc.Version) + if setting.Federation.Enabled { + m.Get("/nodeinfo", misc.NodeInfo) + } m.Get("/signing-key.gpg", misc.SigningKey) m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown) m.Post("/markdown/raw", misc.MarkdownRaw) @@ -907,8 +900,7 @@ func Routes() *web.Route { m.Group("/{index}", func() { m.Combo("").Get(repo.GetPullRequest). Patch(reqToken(), bind(api.EditPullRequestOption{}), repo.EditPullRequest) - m.Get(".diff", repo.DownloadPullDiff) - m.Get(".patch", repo.DownloadPullPatch) + m.Get(".{diffType:diff|patch}", repo.DownloadPullDiffOrPatch) m.Post("/update", reqToken(), repo.UpdatePullRequest) m.Get("/commits", repo.GetPullRequestCommits) m.Combo("/merge").Get(repo.IsPullRequestMerged). @@ -947,6 +939,7 @@ func Routes() *web.Route { m.Group("/git", func() { m.Group("/commits", func() { m.Get("/{sha}", repo.GetSingleCommit) + m.Get("/{sha}.{diffType:diff|patch}", repo.DownloadCommitDiffOrPatch) }) m.Get("/refs", repo.GetGitAllRefs) m.Get("/refs/*", repo.GetGitRefs) diff --git a/routers/api/v1/misc/markdown_test.go b/routers/api/v1/misc/markdown_test.go index 3ae3165fd3734..dc6762c4cf239 100644 --- a/routers/api/v1/misc/markdown_test.go +++ b/routers/api/v1/misc/markdown_test.go @@ -5,7 +5,7 @@ package misc import ( - "io/ioutil" + "io" "net/http" "net/http/httptest" "net/url" @@ -172,7 +172,7 @@ func TestAPI_RenderRaw(t *testing.T) { ctx := wrap(m) for i := 0; i < len(simpleCases); i += 2 { - ctx.Req.Body = ioutil.NopCloser(strings.NewReader(simpleCases[i])) + ctx.Req.Body = io.NopCloser(strings.NewReader(simpleCases[i])) MarkdownRaw(ctx) assert.Equal(t, simpleCases[i+1], resp.Body.String()) resp.Body.Reset() diff --git a/routers/api/v1/misc/nodeinfo.go b/routers/api/v1/misc/nodeinfo.go new file mode 100644 index 0000000000000..bc36fa1be1284 --- /dev/null +++ b/routers/api/v1/misc/nodeinfo.go @@ -0,0 +1,45 @@ +// Copyright 2021 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 misc + +import ( + "net/http" + + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" +) + +// NodeInfo returns the NodeInfo for the Gitea instance to allow for federation +func NodeInfo(ctx *context.APIContext) { + // swagger:operation GET /nodeinfo miscellaneous getNodeInfo + // --- + // summary: Returns the nodeinfo of the Gitea application + // produces: + // - application/json + // responses: + // "200": + // "$ref": "#/responses/NodeInfo" + + nodeInfo := &structs.NodeInfo{ + Version: "2.1", + Software: structs.NodeInfoSoftware{ + Name: "gitea", + Version: setting.AppVer, + Repository: "https://github.com/go-gitea/gitea.git", + Homepage: "https://gitea.io/", + }, + Protocols: []string{"activitypub"}, + Services: structs.NodeInfoServices{ + Inbound: []string{}, + Outbound: []string{}, + }, + OpenRegistrations: setting.Service.ShowRegistrationButton, + Usage: structs.NodeInfoUsage{ + Users: structs.NodeInfoUsageUsers{}, + }, + } + ctx.JSON(http.StatusOK, nodeInfo) +} diff --git a/routers/api/v1/notify/repo.go b/routers/api/v1/notify/repo.go index 1a36642b6a26b..382d221b857c3 100644 --- a/routers/api/v1/notify/repo.go +++ b/routers/api/v1/notify/repo.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/structs" ) func statusStringToNotificationStatus(status string) models.NotificationStatus { @@ -176,7 +177,7 @@ func ReadRepoNotifications(ctx *context.APIContext) { // required: false // responses: // "205": - // "$ref": "#/responses/empty" + // "$ref": "#/responses/NotificationThreadList" lastRead := int64(0) qLastRead := ctx.FormTrim("last_read_at") @@ -213,14 +214,16 @@ func ReadRepoNotifications(ctx *context.APIContext) { targetStatus = models.NotificationStatusRead } + changed := make([]*structs.NotificationThread, len(nl)) + for _, n := range nl { - err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus) + notif, err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus) if err != nil { ctx.InternalServerError(err) return } - ctx.Status(http.StatusResetContent) + _ = notif.LoadAttributes() + changed = append(changed, convert.ToNotificationThread(notif)) } - - ctx.Status(http.StatusResetContent) + ctx.JSON(http.StatusResetContent, changed) } diff --git a/routers/api/v1/notify/threads.go b/routers/api/v1/notify/threads.go index 1774c0b412492..2e241080b48d3 100644 --- a/routers/api/v1/notify/threads.go +++ b/routers/api/v1/notify/threads.go @@ -71,7 +71,7 @@ func ReadThread(ctx *context.APIContext) { // required: false // responses: // "205": - // "$ref": "#/responses/empty" + // "$ref": "#/responses/NotificationThread" // "403": // "$ref": "#/responses/forbidden" // "404": @@ -87,12 +87,16 @@ func ReadThread(ctx *context.APIContext) { targetStatus = models.NotificationStatusRead } - err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus) + notif, err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus) if err != nil { ctx.InternalServerError(err) return } - ctx.Status(http.StatusResetContent) + if err = notif.LoadAttributes(); err != nil { + ctx.InternalServerError(err) + return + } + ctx.JSON(http.StatusResetContent, convert.ToNotificationThread(notif)) } func getThread(ctx *context.APIContext) *models.Notification { diff --git a/routers/api/v1/notify/user.go b/routers/api/v1/notify/user.go index e4626cb719845..6e4c19d1bf2bb 100644 --- a/routers/api/v1/notify/user.go +++ b/routers/api/v1/notify/user.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" + "code.gitea.io/gitea/modules/structs" ) // ListNotifications list users's notification threads @@ -125,7 +126,7 @@ func ReadNotifications(ctx *context.APIContext) { // required: false // responses: // "205": - // "$ref": "#/responses/empty" + // "$ref": "#/responses/NotificationThreadList" lastRead := int64(0) qLastRead := ctx.FormTrim("last_read_at") @@ -158,14 +159,17 @@ func ReadNotifications(ctx *context.APIContext) { targetStatus = models.NotificationStatusRead } + changed := make([]*structs.NotificationThread, 0, len(nl)) + for _, n := range nl { - err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus) + notif, err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus) if err != nil { ctx.InternalServerError(err) return } - ctx.Status(http.StatusResetContent) + _ = notif.LoadAttributes() + changed = append(changed, convert.ToNotificationThread(notif)) } - ctx.Status(http.StatusResetContent) + ctx.JSON(http.StatusResetContent, changed) } diff --git a/routers/api/v1/org/label.go b/routers/api/v1/org/label.go index 09acb0bf04426..b3752841898d9 100644 --- a/routers/api/v1/org/label.go +++ b/routers/api/v1/org/label.go @@ -56,7 +56,7 @@ func ListLabels(ctx *context.APIContext) { } ctx.SetTotalCountHeader(count) - ctx.JSON(http.StatusOK, convert.ToLabelList(labels)) + ctx.JSON(http.StatusOK, convert.ToLabelList(labels, nil, ctx.Org.Organization)) } // CreateLabel create a label for a repository @@ -103,7 +103,8 @@ func CreateLabel(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "NewLabel", err) return } - ctx.JSON(http.StatusCreated, convert.ToLabel(label)) + + ctx.JSON(http.StatusCreated, convert.ToLabel(label, nil, ctx.Org.Organization)) } // GetLabel get label by organization and label id @@ -148,7 +149,7 @@ func GetLabel(ctx *context.APIContext) { return } - ctx.JSON(http.StatusOK, convert.ToLabel(label)) + ctx.JSON(http.StatusOK, convert.ToLabel(label, nil, ctx.Org.Organization)) } // EditLabel modify a label for an Organization @@ -212,7 +213,8 @@ func EditLabel(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "UpdateLabel", err) return } - ctx.JSON(http.StatusOK, convert.ToLabel(label)) + + ctx.JSON(http.StatusOK, convert.ToLabel(label, nil, ctx.Org.Organization)) } // DeleteLabel delete a label for an organization diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index 8653b0bc809aa..c57075e3b84cd 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -498,6 +498,7 @@ func CreateBranchProtection(ctx *context.APIContext) { DismissStaleApprovals: form.DismissStaleApprovals, RequireSignedCommits: form.RequireSignedCommits, ProtectedFilePatterns: form.ProtectedFilePatterns, + UnprotectedFilePatterns: form.UnprotectedFilePatterns, BlockOnOutdatedBranch: form.BlockOnOutdatedBranch, } @@ -643,6 +644,10 @@ func EditBranchProtection(ctx *context.APIContext) { protectBranch.ProtectedFilePatterns = *form.ProtectedFilePatterns } + if form.UnprotectedFilePatterns != nil { + protectBranch.UnprotectedFilePatterns = *form.UnprotectedFilePatterns + } + if form.BlockOnOutdatedBranch != nil { protectBranch.BlockOnOutdatedBranch = *form.BlockOnOutdatedBranch } diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go index 975b9cab2a9ce..639100757bdd7 100644 --- a/routers/api/v1/repo/commits.go +++ b/routers/api/v1/repo/commits.go @@ -213,3 +213,53 @@ func GetAllCommits(ctx *context.APIContext) { ctx.JSON(http.StatusOK, &apiCommits) } + +// DownloadCommitDiffOrPatch render a commit's raw diff or patch +func DownloadCommitDiffOrPatch(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/git/commits/{sha}.{diffType} repository repoDownloadCommitDiffOrPatch + // --- + // summary: Get a commit's diff or patch + // produces: + // - text/plain + // 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: sha + // in: path + // description: SHA of the commit to get + // type: string + // required: true + // - name: diffType + // in: path + // description: whether the output is diff or patch + // type: string + // enum: [diff, patch] + // required: true + // responses: + // "200": + // "$ref": "#/responses/string" + // "404": + // "$ref": "#/responses/notFound" + repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) + if err := git.GetRawDiff( + repoPath, + ctx.Params(":sha"), + git.RawDiffType(ctx.Params(":diffType")), + ctx.Resp, + ); err != nil { + if git.IsErrNotExist(err) { + ctx.NotFound(ctx.Params(":sha")) + return + } + ctx.Error(http.StatusInternalServerError, "DownloadCommitDiffOrPatch", err) + return + } +} diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index 2b14d1a93cb1f..d9451b8090c6c 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -119,13 +119,15 @@ func GetArchive(ctx *context.APIContext) { // "$ref": "#/responses/notFound" repoPath := models.RepoPath(ctx.Params(":username"), ctx.Params(":reponame")) - gitRepo, err := git.OpenRepository(repoPath) - if err != nil { - ctx.Error(http.StatusInternalServerError, "OpenRepository", err) - return + if ctx.Repo.GitRepo == nil { + gitRepo, err := git.OpenRepository(repoPath) + if err != nil { + ctx.Error(http.StatusInternalServerError, "OpenRepository", err) + return + } + ctx.Repo.GitRepo = gitRepo + defer gitRepo.Close() } - ctx.Repo.GitRepo = gitRepo - defer gitRepo.Close() repo.Download(ctx.Context) } diff --git a/routers/api/v1/repo/hook_test.go b/routers/api/v1/repo/hook_test.go index 8ed4bc4b0c3ce..79b8ec171c927 100644 --- a/routers/api/v1/repo/hook_test.go +++ b/routers/api/v1/repo/hook_test.go @@ -9,6 +9,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/test" @@ -16,7 +17,7 @@ import ( ) func TestTestHook(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/wiki/_pages") ctx.SetParams(":id", "1") @@ -26,8 +27,8 @@ func TestTestHook(t *testing.T) { TestHook(&context.APIContext{Context: ctx, Org: nil}) assert.EqualValues(t, http.StatusNoContent, ctx.Resp.Status()) - models.AssertExistsAndLoadBean(t, &models.HookTask{ + db.AssertExistsAndLoadBean(t, &models.HookTask{ RepoID: 1, HookID: 1, - }, models.Cond("is_delivered=?", false)) + }, db.Cond("is_delivered=?", false)) } diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index e8ef2f3d05fd7..17a3becd5bbb5 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -13,6 +13,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" issue_indexer "code.gitea.io/gitea/modules/indexer/issues" @@ -226,7 +227,7 @@ func SearchIssues(ctx *context.APIContext) { // This would otherwise return all issues if no issues were found by the search. if len(keyword) == 0 || len(issueIDs) > 0 || len(includedLabelNames) > 0 || len(includedMilestones) > 0 { issuesOpt := &models.IssuesOptions{ - ListOptions: models.ListOptions{ + ListOptions: db.ListOptions{ Page: ctx.FormInt("page"), PageSize: limit, }, @@ -261,7 +262,7 @@ func SearchIssues(ctx *context.APIContext) { return } - issuesOpt.ListOptions = models.ListOptions{ + issuesOpt.ListOptions = db.ListOptions{ Page: -1, } if filteredCount, err = models.CountIssues(issuesOpt); err != nil { @@ -317,27 +318,27 @@ func ListIssues(ctx *context.APIContext) { // type: string // - name: since // in: query - // description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format + // description: Only show items updated after the given time. This is a timestamp in RFC 3339 format // type: string // format: date-time // required: false // - name: before // in: query - // description: Only show notifications updated before the given time. This is a timestamp in RFC 3339 format + // description: Only show items updated before the given time. This is a timestamp in RFC 3339 format // type: string // format: date-time // required: false // - name: created_by // in: query - // description: filter (issues / pulls) created to + // description: Only show items which were created by the the given user // type: string // - name: assigned_by // in: query - // description: filter (issues / pulls) assigned to + // description: Only show items for which the given user is assigned // type: string // - name: mentioned_by // in: query - // description: filter (issues / pulls) mentioning to + // description: Only show items in which the given user was mentioned // type: string // - name: page // in: query @@ -470,7 +471,7 @@ func ListIssues(ctx *context.APIContext) { return } - issuesOpt.ListOptions = models.ListOptions{ + issuesOpt.ListOptions = db.ListOptions{ Page: -1, } if filteredCount, err = models.CountIssues(issuesOpt); err != nil { @@ -789,6 +790,15 @@ func EditIssue(ctx *context.APIContext) { } } if form.State != nil { + if issue.IsPull { + if pr, err := issue.GetPullRequest(); err != nil { + ctx.Error(http.StatusInternalServerError, "GetPullRequest", err) + return + } else if pr.HasMerged { + ctx.Error(http.StatusPreconditionFailed, "MergedPRState", "cannot change state of this pull request, it was already merged") + return + } + } issue.IsClosed = api.StateClosed == api.StateType(*form.State) } statusChangeComment, titleChanged, err := models.UpdateIssueByAPI(issue, ctx.User) diff --git a/routers/api/v1/repo/issue_label.go b/routers/api/v1/repo/issue_label.go index d7f64b2d995eb..0469ae247c3b4 100644 --- a/routers/api/v1/repo/issue_label.go +++ b/routers/api/v1/repo/issue_label.go @@ -61,7 +61,7 @@ func ListIssueLabels(ctx *context.APIContext) { return } - ctx.JSON(http.StatusOK, convert.ToLabelList(issue.Labels)) + ctx.JSON(http.StatusOK, convert.ToLabelList(issue.Labels, ctx.Repo.Repository, ctx.Repo.Owner)) } // AddIssueLabels add labels for an issue @@ -117,7 +117,7 @@ func AddIssueLabels(ctx *context.APIContext) { return } - ctx.JSON(http.StatusOK, convert.ToLabelList(labels)) + ctx.JSON(http.StatusOK, convert.ToLabelList(labels, ctx.Repo.Repository, ctx.Repo.Owner)) } // DeleteIssueLabel delete a label for an issue @@ -243,7 +243,7 @@ func ReplaceIssueLabels(ctx *context.APIContext) { return } - ctx.JSON(http.StatusOK, convert.ToLabelList(labels)) + ctx.JSON(http.StatusOK, convert.ToLabelList(labels, ctx.Repo.Repository, ctx.Repo.Owner)) } // ClearIssueLabels delete all the labels for an issue diff --git a/routers/api/v1/repo/label.go b/routers/api/v1/repo/label.go index 1de5705aa28e4..67682fc60da9f 100644 --- a/routers/api/v1/repo/label.go +++ b/routers/api/v1/repo/label.go @@ -62,7 +62,7 @@ func ListLabels(ctx *context.APIContext) { } ctx.SetTotalCountHeader(count) - ctx.JSON(http.StatusOK, convert.ToLabelList(labels)) + ctx.JSON(http.StatusOK, convert.ToLabelList(labels, ctx.Repo.Repository, nil)) } // GetLabel get label by repository and label id @@ -112,7 +112,7 @@ func GetLabel(ctx *context.APIContext) { return } - ctx.JSON(http.StatusOK, convert.ToLabel(label)) + ctx.JSON(http.StatusOK, convert.ToLabel(label, ctx.Repo.Repository, nil)) } // CreateLabel create a label for a repository @@ -165,7 +165,8 @@ func CreateLabel(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "NewLabel", err) return } - ctx.JSON(http.StatusCreated, convert.ToLabel(label)) + + ctx.JSON(http.StatusCreated, convert.ToLabel(label, ctx.Repo.Repository, nil)) } // EditLabel modify a label for a repository @@ -235,7 +236,8 @@ func EditLabel(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "UpdateLabel", err) return } - ctx.JSON(http.StatusOK, convert.ToLabel(label)) + + ctx.JSON(http.StatusOK, convert.ToLabel(label, ctx.Repo.Repository, nil)) } // DeleteLabel delete a label for a repository diff --git a/routers/api/v1/repo/main_test.go b/routers/api/v1/repo/main_test.go index 656758ffba440..7a66370e05b21 100644 --- a/routers/api/v1/repo/main_test.go +++ b/routers/api/v1/repo/main_test.go @@ -8,9 +8,9 @@ import ( "path/filepath" "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..", "..", "..")) + db.MainTest(m, filepath.Join("..", "..", "..", "..")) } diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index dee9a94bca6bd..d8e0d8099ab0d 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -115,7 +115,7 @@ func ListPullRequests(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err) return } - apiPrs[i] = convert.ToAPIPullRequest(prs[i]) + apiPrs[i] = convert.ToAPIPullRequest(prs[i], ctx.User) } ctx.SetLinkHeader(int(maxResults), listOptions.PageSize) @@ -171,14 +171,14 @@ func GetPullRequest(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err) return } - ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(pr)) + ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(pr, ctx.User)) } -// DownloadPullDiff render a pull's raw diff -func DownloadPullDiff(ctx *context.APIContext) { - // swagger:operation GET /repos/{owner}/{repo}/pulls/{index}.diff repository repoDownloadPullDiff +// DownloadPullDiffOrPatch render a pull's raw diff or patch +func DownloadPullDiffOrPatch(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/pulls/{index}.{diffType} repository repoDownloadPullDiffOrPatch // --- - // summary: Get a pull request diff + // summary: Get a pull request diff or patch // produces: // - text/plain // parameters: @@ -198,48 +198,21 @@ func DownloadPullDiff(ctx *context.APIContext) { // type: integer // format: int64 // required: true - // responses: - // "200": - // "$ref": "#/responses/string" - // "404": - // "$ref": "#/responses/notFound" - DownloadPullDiffOrPatch(ctx, false) -} - -// DownloadPullPatch render a pull's raw patch -func DownloadPullPatch(ctx *context.APIContext) { - // swagger:operation GET /repos/{owner}/{repo}/pulls/{index}.patch repository repoDownloadPullPatch - // --- - // summary: Get a pull request patch file - // produces: - // - text/plain - // parameters: - // - name: owner - // in: path - // description: owner of the repo - // type: string - // required: true - // - name: repo + // - name: diffType // in: path - // description: name of the repo + // description: whether the output is diff or patch // type: string + // enum: [diff, patch] // required: true - // - name: index - // in: path - // description: index of the pull request to get - // type: integer - // format: int64 - // required: true + // - name: binary + // in: query + // description: whether to include binary file changes. if true, the diff is applicable with `git apply` + // type: boolean // responses: // "200": // "$ref": "#/responses/string" // "404": // "$ref": "#/responses/notFound" - DownloadPullDiffOrPatch(ctx, true) -} - -// DownloadPullDiffOrPatch render a pull's raw diff or patch -func DownloadPullDiffOrPatch(ctx *context.APIContext, patch bool) { pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrPullRequestNotExist(err) { @@ -249,8 +222,16 @@ func DownloadPullDiffOrPatch(ctx *context.APIContext, patch bool) { } return } + var patch bool + if ctx.Params(":diffType") == "diff" { + patch = false + } else { + patch = true + } + + binary := ctx.FormBool("binary") - if err := pull_service.DownloadDiffOrPatch(pr, ctx, patch); err != nil { + if err := pull_service.DownloadDiffOrPatch(pr, ctx, patch, binary); err != nil { ctx.InternalServerError(err) return } @@ -436,7 +417,7 @@ func CreatePullRequest(ctx *context.APIContext) { } log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID) - ctx.JSON(http.StatusCreated, convert.ToAPIPullRequest(pr)) + ctx.JSON(http.StatusCreated, convert.ToAPIPullRequest(pr, ctx.User)) } // EditPullRequest does what it says @@ -583,6 +564,10 @@ func EditPullRequest(ctx *context.APIContext) { } if form.State != nil { + if pr.HasMerged { + ctx.Error(http.StatusPreconditionFailed, "MergedPRState", "cannot change state of this pull request, it was already merged") + return + } issue.IsClosed = api.StateClosed == api.StateType(*form.State) } statusChangeComment, titleChanged, err := models.UpdateIssueByAPI(issue, ctx.User) @@ -604,7 +589,7 @@ func EditPullRequest(ctx *context.APIContext) { } // change pull target branch - if len(form.Base) != 0 && form.Base != pr.BaseBranch { + if !pr.HasMerged && len(form.Base) != 0 && form.Base != pr.BaseBranch { if !ctx.Repo.GitRepo.IsBranchExist(form.Base) { ctx.Error(http.StatusNotFound, "NewBaseBranchNotExist", fmt.Errorf("new base '%s' not exist", form.Base)) return @@ -639,7 +624,7 @@ func EditPullRequest(ctx *context.APIContext) { } // TODO this should be 200, not 201 - ctx.JSON(http.StatusCreated, convert.ToAPIPullRequest(pr)) + ctx.JSON(http.StatusCreated, convert.ToAPIPullRequest(pr, ctx.User)) } // IsPullRequestMerged checks if a PR exists given an index @@ -1031,7 +1016,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) return nil, nil, nil, nil, "", "" } - compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch) + compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch, true) if err != nil { headGitRepo.Close() ctx.Error(http.StatusInternalServerError, "GetCompareInfo", err) @@ -1208,9 +1193,9 @@ func GetPullRequestCommits(ctx *context.APIContext) { } defer baseGitRepo.Close() if pr.HasMerged { - prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.MergeBase, pr.GetGitRefName()) + prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.MergeBase, pr.GetGitRefName(), true) } else { - prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName()) + prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName(), true) } if err != nil { ctx.ServerError("GetCompareInfo", err) diff --git a/routers/api/v1/repo/repo_test.go b/routers/api/v1/repo/repo_test.go index a1bd3e85d75a5..e4fd4f94240ef 100644 --- a/routers/api/v1/repo/repo_test.go +++ b/routers/api/v1/repo/repo_test.go @@ -9,6 +9,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" @@ -18,7 +19,7 @@ import ( ) func TestRepoEdit(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1") test.LoadRepo(t, ctx, 1) @@ -59,13 +60,13 @@ func TestRepoEdit(t *testing.T) { Edit(apiCtx) assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) - models.AssertExistsAndLoadBean(t, &models.Repository{ + db.AssertExistsAndLoadBean(t, &models.Repository{ ID: 1, - }, models.Cond("name = ? AND is_archived = 1", *opts.Name)) + }, db.Cond("name = ? AND is_archived = 1", *opts.Name)) } func TestRepoEditNameChange(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1") test.LoadRepo(t, ctx, 1) @@ -81,7 +82,7 @@ func TestRepoEditNameChange(t *testing.T) { Edit(apiCtx) assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) - models.AssertExistsAndLoadBean(t, &models.Repository{ + db.AssertExistsAndLoadBean(t, &models.Repository{ ID: 1, - }, models.Cond("name = ?", opts.Name)) + }, db.Cond("name = ?", opts.Name)) } diff --git a/routers/api/v1/swagger/nodeinfo.go b/routers/api/v1/swagger/nodeinfo.go new file mode 100644 index 0000000000000..c1ecf3a3f36da --- /dev/null +++ b/routers/api/v1/swagger/nodeinfo.go @@ -0,0 +1,16 @@ +// Copyright 2021 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 swagger + +import ( + api "code.gitea.io/gitea/modules/structs" +) + +// NodeInfo +// swagger:response NodeInfo +type swaggerResponseNodeInfo struct { + // in:body + Body api.NodeInfo `json:"body"` +} diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go index f0f7cb4159b65..bf45bf4dd5a65 100644 --- a/routers/api/v1/user/app.go +++ b/routers/api/v1/user/app.go @@ -12,6 +12,7 @@ import ( "strconv" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" @@ -212,7 +213,7 @@ func CreateOauth2Application(ctx *context.APIContext) { data := web.GetForm(ctx).(*api.CreateOAuth2ApplicationOptions) - app, err := models.CreateOAuth2Application(models.CreateOAuth2ApplicationOptions{ + app, err := login.CreateOAuth2Application(login.CreateOAuth2ApplicationOptions{ Name: data.Name, UserID: ctx.User.ID, RedirectURIs: data.RedirectURIs, @@ -251,7 +252,7 @@ func ListOauth2Applications(ctx *context.APIContext) { // "200": // "$ref": "#/responses/OAuth2ApplicationList" - apps, total, err := models.ListOAuth2Applications(ctx.User.ID, utils.GetListOptions(ctx)) + apps, total, err := login.ListOAuth2Applications(ctx.User.ID, utils.GetListOptions(ctx)) if err != nil { ctx.Error(http.StatusInternalServerError, "ListOAuth2Applications", err) return @@ -287,8 +288,8 @@ func DeleteOauth2Application(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" appID := ctx.ParamsInt64(":id") - if err := models.DeleteOAuth2Application(appID, ctx.User.ID); err != nil { - if models.IsErrOAuthApplicationNotFound(err) { + if err := login.DeleteOAuth2Application(appID, ctx.User.ID); err != nil { + if login.IsErrOAuthApplicationNotFound(err) { ctx.NotFound() } else { ctx.Error(http.StatusInternalServerError, "DeleteOauth2ApplicationByID", err) @@ -319,9 +320,9 @@ func GetOauth2Application(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" appID := ctx.ParamsInt64(":id") - app, err := models.GetOAuth2ApplicationByID(appID) + app, err := login.GetOAuth2ApplicationByID(appID) if err != nil { - if models.IsErrOauthClientIDInvalid(err) || models.IsErrOAuthApplicationNotFound(err) { + if login.IsErrOauthClientIDInvalid(err) || login.IsErrOAuthApplicationNotFound(err) { ctx.NotFound() } else { ctx.Error(http.StatusInternalServerError, "GetOauth2ApplicationByID", err) @@ -362,14 +363,14 @@ func UpdateOauth2Application(ctx *context.APIContext) { data := web.GetForm(ctx).(*api.CreateOAuth2ApplicationOptions) - app, err := models.UpdateOAuth2Application(models.UpdateOAuth2ApplicationOptions{ + app, err := login.UpdateOAuth2Application(login.UpdateOAuth2ApplicationOptions{ Name: data.Name, UserID: ctx.User.ID, ID: appID, RedirectURIs: data.RedirectURIs, }) if err != nil { - if models.IsErrOauthClientIDInvalid(err) || models.IsErrOAuthApplicationNotFound(err) { + if login.IsErrOauthClientIDInvalid(err) || login.IsErrOAuthApplicationNotFound(err) { ctx.NotFound() } else { ctx.Error(http.StatusInternalServerError, "UpdateOauth2ApplicationByID", err) diff --git a/routers/api/v1/user/gpg_key.go b/routers/api/v1/user/gpg_key.go index f32d60d03816a..9066268bba293 100644 --- a/routers/api/v1/user/gpg_key.go +++ b/routers/api/v1/user/gpg_key.go @@ -9,6 +9,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" @@ -16,7 +17,7 @@ import ( "code.gitea.io/gitea/routers/api/v1/utils" ) -func listGPGKeys(ctx *context.APIContext, uid int64, listOptions models.ListOptions) { +func listGPGKeys(ctx *context.APIContext, uid int64, listOptions db.ListOptions) { keys, err := models.ListGPGKeys(uid, listOptions) if err != nil { ctx.Error(http.StatusInternalServerError, "ListGPGKeys", err) diff --git a/routers/api/v1/user/star.go b/routers/api/v1/user/star.go index 8ee167685639c..f067722bfa821 100644 --- a/routers/api/v1/user/star.go +++ b/routers/api/v1/user/star.go @@ -9,6 +9,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" @@ -17,7 +18,7 @@ import ( // getStarredRepos returns the repos that the user with the specified userID has // starred -func getStarredRepos(user *models.User, private bool, listOptions models.ListOptions) ([]*api.Repository, error) { +func getStarredRepos(user *models.User, private bool, listOptions db.ListOptions) ([]*api.Repository, error) { starredRepos, err := models.GetStarredRepos(user.ID, private, listOptions) if err != nil { return nil, err diff --git a/routers/api/v1/user/watch.go b/routers/api/v1/user/watch.go index f32ce7359864a..3c6f8b30704f3 100644 --- a/routers/api/v1/user/watch.go +++ b/routers/api/v1/user/watch.go @@ -8,6 +8,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" @@ -15,7 +16,7 @@ import ( ) // getWatchedRepos returns the repos that the user with the specified userID is watching -func getWatchedRepos(user *models.User, private bool, listOptions models.ListOptions) ([]*api.Repository, int64, error) { +func getWatchedRepos(user *models.User, private bool, listOptions db.ListOptions) ([]*api.Repository, int64, error) { watchedRepos, total, err := models.GetWatchedRepos(user.ID, private, listOptions) if err != nil { return nil, 0, err diff --git a/routers/api/v1/utils/utils.go b/routers/api/v1/utils/utils.go index 81f5086c9611c..7564857115715 100644 --- a/routers/api/v1/utils/utils.go +++ b/routers/api/v1/utils/utils.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" ) @@ -60,8 +60,8 @@ func prepareQueryArg(ctx *context.APIContext, name string) (value string, err er } // GetListOptions returns list options using the page and limit parameters -func GetListOptions(ctx *context.APIContext) models.ListOptions { - return models.ListOptions{ +func GetListOptions(ctx *context.APIContext) db.ListOptions { + return db.ListOptions{ Page: ctx.FormInt("page"), PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), } diff --git a/routers/common/db.go b/routers/common/db.go index 069a46f64fe4d..e5848796fbe5b 100644 --- a/routers/common/db.go +++ b/routers/common/db.go @@ -9,7 +9,7 @@ import ( "fmt" "time" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/migrations" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -25,7 +25,7 @@ func InitDBEngine(ctx context.Context) (err error) { default: } log.Info("ORM engine initialization attempt #%d/%d...", i+1, setting.Database.DBConnectRetries) - if err = models.NewEngine(ctx, migrations.Migrate); err == nil { + if err = db.NewEngine(ctx, migrations.Migrate); err == nil { break } else if i == setting.Database.DBConnectRetries-1 { return err @@ -34,6 +34,6 @@ func InitDBEngine(ctx context.Context) (err error) { log.Info("Backing off for %d seconds", int64(setting.Database.DBConnectBackoff/time.Second)) time.Sleep(setting.Database.DBConnectBackoff) } - models.HasEngine = true + db.HasEngine = true return nil } diff --git a/routers/init.go b/routers/init.go index 27cd066b73ae6..52eacfd02b17e 100644 --- a/routers/init.go +++ b/routers/init.go @@ -41,6 +41,8 @@ import ( pull_service "code.gitea.io/gitea/services/pull" "code.gitea.io/gitea/services/repository" "code.gitea.io/gitea/services/webhook" + + "gitea.com/go-chi/session" ) // NewServices init new services @@ -79,6 +81,7 @@ func GlobalInit(ctx context.Context) { log.Info("AppWorkPath: %s", setting.AppWorkPath) log.Info("Custom path: %s", setting.CustomPath) log.Info("Log path: %s", setting.LogRootPath) + log.Info("Configuration file: %s", setting.CustomConf) log.Info("Run Mode: %s", strings.Title(setting.RunMode)) // Setup i18n @@ -145,8 +148,20 @@ func NormalRoutes() *web.Route { r.Use(middle) } - r.Mount("/", web_routers.Routes()) - r.Mount("/api/v1", apiv1.Routes()) + sessioner := session.Sessioner(session.Options{ + Provider: setting.SessionConfig.Provider, + ProviderConfig: setting.SessionConfig.ProviderConfig, + CookieName: setting.SessionConfig.CookieName, + CookiePath: setting.SessionConfig.CookiePath, + Gclifetime: setting.SessionConfig.Gclifetime, + Maxlifetime: setting.SessionConfig.Maxlifetime, + Secure: setting.SessionConfig.Secure, + SameSite: setting.SessionConfig.SameSite, + Domain: setting.SessionConfig.Domain, + }) + + r.Mount("/", web_routers.Routes(sessioner)) + r.Mount("/api/v1", apiv1.Routes(sessioner)) r.Mount("/api/internal", private.Routes()) return r } diff --git a/routers/install/install.go b/routers/install/install.go index ad985cf184882..8143ad8089a8d 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -15,6 +15,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/generate" @@ -207,7 +208,7 @@ func SubmitInstall(ctx *context.Context) { } // Set test engine. - if err = models.NewTestEngine(); err != nil { + if err = db.NewTestEngine(); err != nil { if strings.Contains(err.Error(), `Unknown database type: sqlite3`) { ctx.Data["Err_DbType"] = true ctx.RenderWithErr(ctx.Tr("install.sqlite3_not_available", "https://docs.gitea.io/en-us/install-from-binary/"), tplInstall, &form) diff --git a/routers/install/setting.go b/routers/install/setting.go index 7b9b7bd8c225b..e2af66cfb4553 100644 --- a/routers/install/setting.go +++ b/routers/install/setting.go @@ -22,6 +22,7 @@ func PreloadSettings(ctx context.Context) bool { log.Info("AppWorkPath: %s", setting.AppWorkPath) log.Info("Custom path: %s", setting.CustomPath) log.Info("Log path: %s", setting.LogRootPath) + log.Info("Configuration file: %s", setting.CustomConf) log.Info("Preparing to run install page") translation.InitLocales() if setting.EnableSQLite3 { diff --git a/routers/private/default_branch.go b/routers/private/default_branch.go new file mode 100644 index 0000000000000..ec6adc4805020 --- /dev/null +++ b/routers/private/default_branch.go @@ -0,0 +1,75 @@ +// Copyright 2021 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 private includes all internal routes. The package name internal is ideal but Golang is not allowed, so we use private as package name instead. +package private + +import ( + "fmt" + "net/http" + + "code.gitea.io/gitea/models" + gitea_context "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/private" +) + +// ________ _____ .__ __ +// \______ \ _____/ ____\____ __ __| |_/ |_ +// | | \_/ __ \ __\\__ \ | | \ |\ __\ +// | ` \ ___/| | / __ \| | / |_| | +// /_______ /\___ >__| (____ /____/|____/__| +// \/ \/ \/ +// __________ .__ +// \______ \____________ ____ ____ | |__ +// | | _/\_ __ \__ \ / \_/ ___\| | \ +// | | \ | | \// __ \| | \ \___| Y \ +// |______ / |__| (____ /___| /\___ >___| / +// \/ \/ \/ \/ \/ + +// SetDefaultBranch updates the default branch +func SetDefaultBranch(ctx *gitea_context.PrivateContext) { + ownerName := ctx.Params(":owner") + repoName := ctx.Params(":repo") + branch := ctx.Params(":branch") + repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) + if err != nil { + log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err), + }) + return + } + if repo.OwnerName == "" { + repo.OwnerName = ownerName + } + + repo.DefaultBranch = branch + gitRepo, err := git.OpenRepository(repo.RepoPath()) + if err != nil { + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: fmt.Sprintf("Failed to get git repository: %s/%s Error: %v", ownerName, repoName, err), + }) + return + } + if err := gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { + if !git.IsErrUnsupportedVersion(err) { + gitRepo.Close() + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: fmt.Sprintf("Unable to set default branch on repository: %s/%s Error: %v", ownerName, repoName, err), + }) + return + } + } + gitRepo.Close() + + if err := repo.UpdateDefaultBranch(); err != nil { + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: fmt.Sprintf("Unable to set default branch on repository: %s/%s Error: %v", ownerName, repoName, err), + }) + return + } + ctx.PlainText(http.StatusOK, []byte("success")) +} diff --git a/routers/private/hook.go b/routers/private/hook.go deleted file mode 100644 index 40edcd9c5a116..0000000000000 --- a/routers/private/hook.go +++ /dev/null @@ -1,760 +0,0 @@ -// 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 private includes all internal routes. The package name internal is ideal but Golang is not allowed, so we use private as package name instead. -package private - -import ( - "bufio" - "context" - "fmt" - "io" - "net/http" - "os" - "strings" - - "code.gitea.io/gitea/models" - gitea_context "code.gitea.io/gitea/modules/context" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/private" - repo_module "code.gitea.io/gitea/modules/repository" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/modules/web" - "code.gitea.io/gitea/services/agit" - pull_service "code.gitea.io/gitea/services/pull" - repo_service "code.gitea.io/gitea/services/repository" -) - -func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env []string) error { - stdoutReader, stdoutWriter, err := os.Pipe() - if err != nil { - log.Error("Unable to create os.Pipe for %s", repo.Path) - return err - } - defer func() { - _ = stdoutReader.Close() - _ = stdoutWriter.Close() - }() - - // This is safe as force pushes are already forbidden - err = git.NewCommand("rev-list", oldCommitID+"..."+newCommitID). - RunInDirTimeoutEnvFullPipelineFunc(env, -1, repo.Path, - stdoutWriter, nil, nil, - func(ctx context.Context, cancel context.CancelFunc) error { - _ = stdoutWriter.Close() - err := readAndVerifyCommitsFromShaReader(stdoutReader, repo, env) - if err != nil { - log.Error("%v", err) - cancel() - } - _ = stdoutReader.Close() - return err - }) - if err != nil && !isErrUnverifiedCommit(err) { - log.Error("Unable to check commits from %s to %s in %s: %v", oldCommitID, newCommitID, repo.Path, err) - } - return err -} - -func readAndVerifyCommitsFromShaReader(input io.ReadCloser, repo *git.Repository, env []string) error { - scanner := bufio.NewScanner(input) - for scanner.Scan() { - line := scanner.Text() - err := readAndVerifyCommit(line, repo, env) - if err != nil { - log.Error("%v", err) - return err - } - } - return scanner.Err() -} - -func readAndVerifyCommit(sha string, repo *git.Repository, env []string) error { - stdoutReader, stdoutWriter, err := os.Pipe() - if err != nil { - log.Error("Unable to create pipe for %s: %v", repo.Path, err) - return err - } - defer func() { - _ = stdoutReader.Close() - _ = stdoutWriter.Close() - }() - hash := git.MustIDFromString(sha) - - return git.NewCommand("cat-file", "commit", sha). - RunInDirTimeoutEnvFullPipelineFunc(env, -1, repo.Path, - stdoutWriter, nil, nil, - func(ctx context.Context, cancel context.CancelFunc) error { - _ = stdoutWriter.Close() - commit, err := git.CommitFromReader(repo, hash, stdoutReader) - if err != nil { - return err - } - verification := models.ParseCommitWithSignature(commit) - if !verification.Verified { - cancel() - return &errUnverifiedCommit{ - commit.ID.String(), - } - } - return nil - }) -} - -type errUnverifiedCommit struct { - sha string -} - -func (e *errUnverifiedCommit) Error() string { - return fmt.Sprintf("Unverified commit: %s", e.sha) -} - -func isErrUnverifiedCommit(err error) bool { - _, ok := err.(*errUnverifiedCommit) - return ok -} - -// HookPreReceive checks whether a individual commit is acceptable -func HookPreReceive(ctx *gitea_context.PrivateContext) { - opts := web.GetForm(ctx).(*private.HookOptions) - ownerName := ctx.Params(":owner") - repoName := ctx.Params(":repo") - repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) - if err != nil { - log.Error("Unable to get repository: %s/%s Error: %v", ownerName, repoName, err) - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: err.Error(), - }) - return - } - repo.OwnerName = ownerName - gitRepo, err := git.OpenRepository(repo.RepoPath()) - if err != nil { - log.Error("Unable to get git repository for: %s/%s Error: %v", ownerName, repoName, err) - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: err.Error(), - }) - return - } - defer gitRepo.Close() - - // Generate git environment for checking commits - env := os.Environ() - if opts.GitAlternativeObjectDirectories != "" { - env = append(env, - private.GitAlternativeObjectDirectories+"="+opts.GitAlternativeObjectDirectories) - } - if opts.GitObjectDirectory != "" { - env = append(env, - private.GitObjectDirectory+"="+opts.GitObjectDirectory) - } - if opts.GitQuarantinePath != "" { - env = append(env, - private.GitQuarantinePath+"="+opts.GitQuarantinePath) - } - - if git.SupportProcReceive { - pusher, err := models.GetUserByID(opts.UserID) - if err != nil { - log.Error("models.GetUserByID:%v", err) - ctx.Error(http.StatusInternalServerError, "") - return - } - - perm, err := models.GetUserRepoPermission(repo, pusher) - if err != nil { - log.Error("models.GetUserRepoPermission:%v", err) - ctx.Error(http.StatusInternalServerError, "") - return - } - - canCreatePullRequest := perm.CanRead(models.UnitTypePullRequests) - - for _, refFullName := range opts.RefFullNames { - // if user want update other refs (branch or tag), - // should check code write permission because - // this check was delayed. - if !strings.HasPrefix(refFullName, git.PullRequestPrefix) { - if !perm.CanWrite(models.UnitTypeCode) { - ctx.JSON(http.StatusForbidden, map[string]interface{}{ - "err": "User permission denied.", - }) - return - } - - break - } else if repo.IsEmpty { - ctx.JSON(http.StatusForbidden, map[string]interface{}{ - "err": "Can't create pull request for an empty repository.", - }) - return - } else if !canCreatePullRequest { - ctx.JSON(http.StatusForbidden, map[string]interface{}{ - "err": "User permission denied.", - }) - return - } else if opts.IsWiki { - // TODO: maybe can do it ... - ctx.JSON(http.StatusForbidden, map[string]interface{}{ - "err": "not support send pull request to wiki.", - }) - return - } - } - } - - protectedTags, err := repo.GetProtectedTags() - if err != nil { - log.Error("Unable to get protected tags for %-v Error: %v", repo, err) - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: err.Error(), - }) - return - } - - // Iterate across the provided old commit IDs - for i := range opts.OldCommitIDs { - oldCommitID := opts.OldCommitIDs[i] - newCommitID := opts.NewCommitIDs[i] - refFullName := opts.RefFullNames[i] - - if strings.HasPrefix(refFullName, git.BranchPrefix) { - 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) - ctx.JSON(http.StatusForbidden, private.Response{ - Err: fmt.Sprintf("branch %s is the default branch and cannot be deleted", branchName), - }) - return - } - - protectBranch, err := models.GetProtectedBranchBy(repo.ID, branchName) - if err != nil { - log.Error("Unable to get protected branch: %s in %-v Error: %v", branchName, repo, err) - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: err.Error(), - }) - return - } - - // Allow pushes to non-protected branches - if protectBranch == nil || !protectBranch.IsProtected() { - continue - } - - // This ref is a protected branch. - // - // First of all we need to enforce absolutely: - // - // 1. Detect and prevent deletion of the branch - if newCommitID == git.EmptySHA { - log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo) - ctx.JSON(http.StatusForbidden, private.Response{ - Err: fmt.Sprintf("branch %s is protected from deletion", branchName), - }) - return - } - - // 2. Disallow force pushes to protected branches - if git.EmptySHA != oldCommitID { - output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDirWithEnv(repo.RepoPath(), env) - if err != nil { - log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err) - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: fmt.Sprintf("Fail to detect force push: %v", err), - }) - return - } else if len(output) > 0 { - log.Warn("Forbidden: Branch: %s in %-v is protected from force push", branchName, repo) - ctx.JSON(http.StatusForbidden, private.Response{ - Err: fmt.Sprintf("branch %s is protected from force push", branchName), - }) - return - - } - } - - // 3. Enforce require signed commits - if protectBranch.RequireSignedCommits { - err := verifyCommits(oldCommitID, newCommitID, gitRepo, env) - if err != nil { - if !isErrUnverifiedCommit(err) { - log.Error("Unable to check commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err) - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: fmt.Sprintf("Unable to check commits from %s to %s: %v", oldCommitID, newCommitID, err), - }) - return - } - unverifiedCommit := err.(*errUnverifiedCommit).sha - log.Warn("Forbidden: Branch: %s in %-v is protected from unverified commit %s", branchName, repo, unverifiedCommit) - ctx.JSON(http.StatusForbidden, private.Response{ - Err: fmt.Sprintf("branch %s is protected from unverified commit %s", branchName, unverifiedCommit), - }) - return - } - } - - // Now there are several tests which can be overridden: - // - // 4. Check protected file patterns - this is overridable from the UI - changedProtectedfiles := false - protectedFilePath := "" - - globs := protectBranch.GetProtectedFilePatterns() - if len(globs) > 0 { - _, err := pull_service.CheckFileProtection(oldCommitID, newCommitID, globs, 1, env, gitRepo) - if err != nil { - if !models.IsErrFilePathProtected(err) { - log.Error("Unable to check file protection for commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err) - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: fmt.Sprintf("Unable to check file protection for commits from %s to %s: %v", oldCommitID, newCommitID, err), - }) - return - } - - changedProtectedfiles = true - protectedFilePath = err.(models.ErrFilePathProtected).Path - } - } - - // 5. Check if the doer is allowed to push - canPush := false - if opts.IsDeployKey { - canPush = !changedProtectedfiles && protectBranch.CanPush && (!protectBranch.EnableWhitelist || protectBranch.WhitelistDeployKeys) - } else { - canPush = !changedProtectedfiles && protectBranch.CanUserPush(opts.UserID) - } - - // 6. If we're not allowed to push directly - if !canPush { - // Is this is a merge from the UI/API? - if opts.PullRequestID == 0 { - // 6a. If we're not merging from the UI/API then there are two ways we got here: - // - // We are changing a protected file and we're not allowed to do that - if changedProtectedfiles { - log.Warn("Forbidden: Branch: %s in %-v is protected from changing file %s", branchName, repo, protectedFilePath) - ctx.JSON(http.StatusForbidden, private.Response{ - Err: fmt.Sprintf("branch %s is protected from changing file %s", branchName, protectedFilePath), - }) - return - } - - // Or we're simply not able to push to this protected branch - log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v", opts.UserID, branchName, repo) - ctx.JSON(http.StatusForbidden, private.Response{ - Err: fmt.Sprintf("Not allowed to push to protected branch %s", branchName), - }) - return - } - // 6b. Merge (from UI or API) - - // Get the PR, user and permissions for the user in the repository - pr, err := models.GetPullRequestByID(opts.PullRequestID) - if err != nil { - log.Error("Unable to get PullRequest %d Error: %v", opts.PullRequestID, err) - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: fmt.Sprintf("Unable to get PullRequest %d Error: %v", opts.PullRequestID, err), - }) - return - } - user, err := models.GetUserByID(opts.UserID) - if err != nil { - log.Error("Unable to get User id %d Error: %v", opts.UserID, err) - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: fmt.Sprintf("Unable to get User id %d Error: %v", opts.UserID, err), - }) - return - } - perm, err := models.GetUserRepoPermission(repo, user) - if err != nil { - log.Error("Unable to get Repo permission of repo %s/%s of User %s", repo.OwnerName, repo.Name, user.Name, err) - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: fmt.Sprintf("Unable to get Repo permission of repo %s/%s of User %s: %v", repo.OwnerName, repo.Name, user.Name, err), - }) - return - } - - // Now check if the user is allowed to merge PRs for this repository - allowedMerge, err := pull_service.IsUserAllowedToMerge(pr, perm, user) - if err != nil { - log.Error("Error calculating if allowed to merge: %v", err) - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: fmt.Sprintf("Error calculating if allowed to merge: %v", err), - }) - return - } - - if !allowedMerge { - log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v and is not allowed to merge pr #%d", opts.UserID, branchName, repo, pr.Index) - ctx.JSON(http.StatusForbidden, private.Response{ - Err: fmt.Sprintf("Not allowed to push to protected branch %s", branchName), - }) - return - } - - // If we're an admin for the repository we can ignore status checks, reviews and override protected files - if perm.IsAdmin() { - continue - } - - // Now if we're not an admin - we can't overwrite protected files so fail now - if changedProtectedfiles { - log.Warn("Forbidden: Branch: %s in %-v is protected from changing file %s", branchName, repo, protectedFilePath) - ctx.JSON(http.StatusForbidden, private.Response{ - Err: fmt.Sprintf("branch %s is protected from changing file %s", branchName, protectedFilePath), - }) - return - } - - // Check all status checks and reviews are ok - if err := pull_service.CheckPRReadyToMerge(pr, true); err != nil { - if models.IsErrNotAllowedToMerge(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", opts.UserID, branchName, repo, pr.Index, err.Error()) - ctx.JSON(http.StatusForbidden, private.Response{ - Err: fmt.Sprintf("Not allowed to push to protected branch %s and pr #%d is not ready to be merged: %s", branchName, opts.PullRequestID, err.Error()), - }) - return - } - log.Error("Unable to check if mergable: protected branch %s in %-v and pr #%d. Error: %v", opts.UserID, branchName, repo, pr.Index, err) - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: fmt.Sprintf("Unable to get status of pull request %d. Error: %v", opts.PullRequestID, err), - }) - return - } - } - } else if strings.HasPrefix(refFullName, git.TagPrefix) { - tagName := strings.TrimPrefix(refFullName, git.TagPrefix) - - isAllowed, err := models.IsUserAllowedToControlTag(protectedTags, tagName, opts.UserID) - if err != nil { - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: err.Error(), - }) - return - } - if !isAllowed { - log.Warn("Forbidden: Tag %s in %-v is protected", tagName, repo) - ctx.JSON(http.StatusForbidden, private.Response{ - Err: fmt.Sprintf("Tag %s is protected", tagName), - }) - return - } - } else if git.SupportProcReceive && strings.HasPrefix(refFullName, git.PullRequestPrefix) { - baseBranchName := opts.RefFullNames[i][len(git.PullRequestPrefix):] - - baseBranchExist := false - if gitRepo.IsBranchExist(baseBranchName) { - baseBranchExist = true - } - - if !baseBranchExist { - for p, v := range baseBranchName { - if v == '/' && gitRepo.IsBranchExist(baseBranchName[:p]) && p != len(baseBranchName)-1 { - baseBranchExist = true - break - } - } - } - - if !baseBranchExist { - ctx.JSON(http.StatusForbidden, private.Response{ - Err: fmt.Sprintf("Unexpected ref: %s", refFullName), - }) - return - } - } else { - log.Error("Unexpected ref: %s", refFullName) - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: fmt.Sprintf("Unexpected ref: %s", refFullName), - }) - return - } - } - - ctx.PlainText(http.StatusOK, []byte("ok")) -} - -// HookPostReceive updates services and users -func HookPostReceive(ctx *gitea_context.PrivateContext) { - opts := web.GetForm(ctx).(*private.HookOptions) - ownerName := ctx.Params(":owner") - repoName := ctx.Params(":repo") - - var repo *models.Repository - updates := make([]*repo_module.PushUpdateOptions, 0, len(opts.OldCommitIDs)) - wasEmpty := false - - for i := range opts.OldCommitIDs { - refFullName := opts.RefFullNames[i] - - // Only trigger activity updates for changes to branches or - // tags. Updates to other refs (eg, refs/notes, refs/changes, - // or other less-standard refs spaces are ignored since there - // may be a very large number of them). - if strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) { - if repo == nil { - var err error - repo, err = models.GetRepositoryByOwnerAndName(ownerName, repoName) - if err != nil { - log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err) - ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ - Err: fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err), - }) - return - } - if repo.OwnerName == "" { - repo.OwnerName = ownerName - } - wasEmpty = repo.IsEmpty - } - - option := repo_module.PushUpdateOptions{ - RefFullName: refFullName, - OldCommitID: opts.OldCommitIDs[i], - NewCommitID: opts.NewCommitIDs[i], - PusherID: opts.UserID, - PusherName: opts.UserName, - RepoUserName: ownerName, - RepoName: repoName, - } - updates = append(updates, &option) - if repo.IsEmpty && option.IsBranch() && (option.BranchName() == "master" || option.BranchName() == "main") { - // put the master/main branch first - copy(updates[1:], updates) - updates[0] = &option - } - } - } - - if repo != nil && len(updates) > 0 { - if err := repo_service.PushUpdates(updates); err != nil { - log.Error("Failed to Update: %s/%s Total Updates: %d", ownerName, repoName, len(updates)) - for i, update := range updates { - log.Error("Failed to Update: %s/%s Update: %d/%d: Branch: %s", ownerName, repoName, i, len(updates), update.BranchName()) - } - log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err) - - ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ - Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err), - }) - return - } - } - - // Push Options - if repo != nil && len(opts.GitPushOptions) > 0 { - repo.IsPrivate = opts.GitPushOptions.Bool(private.GitPushOptionRepoPrivate, repo.IsPrivate) - repo.IsTemplate = opts.GitPushOptions.Bool(private.GitPushOptionRepoTemplate, repo.IsTemplate) - if err := models.UpdateRepositoryCols(repo, "is_private", "is_template"); err != nil { - log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err) - ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ - Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err), - }) - } - } - - results := make([]private.HookPostReceiveBranchResult, 0, len(opts.OldCommitIDs)) - - // We have to reload the repo in case its state is changed above - repo = nil - var baseRepo *models.Repository - - for i := range opts.OldCommitIDs { - refFullName := opts.RefFullNames[i] - newCommitID := opts.NewCommitIDs[i] - - branch := git.RefEndName(opts.RefFullNames[i]) - - if newCommitID != git.EmptySHA && strings.HasPrefix(refFullName, git.BranchPrefix) { - if repo == nil { - var err error - repo, err = models.GetRepositoryByOwnerAndName(ownerName, repoName) - if err != nil { - log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err) - ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ - Err: fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err), - RepoWasEmpty: wasEmpty, - }) - return - } - if repo.OwnerName == "" { - repo.OwnerName = ownerName - } - - if !repo.AllowsPulls() { - // We can stop there's no need to go any further - ctx.JSON(http.StatusOK, private.HookPostReceiveResult{ - RepoWasEmpty: wasEmpty, - }) - return - } - baseRepo = repo - - if repo.IsFork { - if err := repo.GetBaseRepo(); err != nil { - log.Error("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err) - ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ - Err: fmt.Sprintf("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err), - RepoWasEmpty: wasEmpty, - }) - return - } - baseRepo = repo.BaseRepo - } - } - - if !repo.IsFork && branch == baseRepo.DefaultBranch { - results = append(results, private.HookPostReceiveBranchResult{}) - continue - } - - pr, err := models.GetUnmergedPullRequest(repo.ID, baseRepo.ID, branch, baseRepo.DefaultBranch, models.PullRequestFlowGithub) - if err != nil && !models.IsErrPullRequestNotExist(err) { - log.Error("Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err) - ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ - Err: fmt.Sprintf( - "Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err), - RepoWasEmpty: wasEmpty, - }) - return - } - - if pr == nil { - if repo.IsFork { - branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch) - } - results = append(results, private.HookPostReceiveBranchResult{ - Message: setting.Git.PullRequestPushMessage && repo.AllowsPulls(), - Create: true, - Branch: branch, - URL: fmt.Sprintf("%s/compare/%s...%s", baseRepo.HTMLURL(), util.PathEscapeSegments(baseRepo.DefaultBranch), util.PathEscapeSegments(branch)), - }) - } else { - results = append(results, private.HookPostReceiveBranchResult{ - Message: setting.Git.PullRequestPushMessage && repo.AllowsPulls(), - Create: false, - Branch: branch, - URL: fmt.Sprintf("%s/pulls/%d", baseRepo.HTMLURL(), pr.Index), - }) - } - } - } - ctx.JSON(http.StatusOK, private.HookPostReceiveResult{ - Results: results, - RepoWasEmpty: wasEmpty, - }) -} - -// HookProcReceive proc-receive hook -func HookProcReceive(ctx *gitea_context.PrivateContext) { - opts := web.GetForm(ctx).(*private.HookOptions) - if !git.SupportProcReceive { - ctx.Status(http.StatusNotFound) - return - } - - cancel := loadRepositoryAndGitRepoByParams(ctx) - if ctx.Written() { - return - } - defer cancel() - - results := agit.ProcRecive(ctx, opts) - if ctx.Written() { - return - } - - ctx.JSON(http.StatusOK, private.HookProcReceiveResult{ - Results: results, - }) -} - -// SetDefaultBranch updates the default branch -func SetDefaultBranch(ctx *gitea_context.PrivateContext) { - ownerName := ctx.Params(":owner") - repoName := ctx.Params(":repo") - branch := ctx.Params(":branch") - repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) - if err != nil { - log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err) - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err), - }) - return - } - if repo.OwnerName == "" { - repo.OwnerName = ownerName - } - - repo.DefaultBranch = branch - gitRepo, err := git.OpenRepository(repo.RepoPath()) - if err != nil { - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: fmt.Sprintf("Failed to get git repository: %s/%s Error: %v", ownerName, repoName, err), - }) - return - } - if err := gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { - if !git.IsErrUnsupportedVersion(err) { - gitRepo.Close() - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: fmt.Sprintf("Unable to set default branch on repository: %s/%s Error: %v", ownerName, repoName, err), - }) - return - } - } - gitRepo.Close() - - if err := repo.UpdateDefaultBranch(); err != nil { - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: fmt.Sprintf("Unable to set default branch on repository: %s/%s Error: %v", ownerName, repoName, err), - }) - return - } - ctx.PlainText(http.StatusOK, []byte("success")) -} - -func loadRepositoryAndGitRepoByParams(ctx *gitea_context.PrivateContext) context.CancelFunc { - ownerName := ctx.Params(":owner") - repoName := ctx.Params(":repo") - - repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) - if err != nil { - log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "Err": fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err), - }) - return nil - } - if repo.OwnerName == "" { - repo.OwnerName = ownerName - } - - gitRepo, err := git.OpenRepository(repo.RepoPath()) - if err != nil { - log.Error("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "Err": fmt.Sprintf("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err), - }) - return nil - } - - ctx.Repo = &gitea_context.Repository{ - Repository: repo, - GitRepo: gitRepo, - } - - // We opened it, we should close it - cancel := func() { - // If it's been set to nil then assume someone else has closed it. - if ctx.Repo.GitRepo != nil { - ctx.Repo.GitRepo.Close() - } - } - - return cancel -} diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go new file mode 100644 index 0000000000000..4dbe74a9aa08d --- /dev/null +++ b/routers/private/hook_post_receive.go @@ -0,0 +1,201 @@ +// Copyright 2021 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 private includes all internal routes. The package name internal is ideal but Golang is not allowed, so we use private as package name instead. +package private + +import ( + "fmt" + "net/http" + "strings" + + "code.gitea.io/gitea/models" + gitea_context "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/private" + repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/web" + repo_service "code.gitea.io/gitea/services/repository" +) + +// HookPostReceive updates services and users +func HookPostReceive(ctx *gitea_context.PrivateContext) { + opts := web.GetForm(ctx).(*private.HookOptions) + + // We don't rely on RepoAssignment here because: + // a) we don't need the git repo in this function + // b) our update function will likely change the repository in the db so we will need to refresh it + // c) we don't always need the repo + + ownerName := ctx.Params(":owner") + repoName := ctx.Params(":repo") + + // defer getting the repository at this point - as we should only retrieve it if we're going to call update + var repo *models.Repository + + updates := make([]*repo_module.PushUpdateOptions, 0, len(opts.OldCommitIDs)) + wasEmpty := false + + for i := range opts.OldCommitIDs { + refFullName := opts.RefFullNames[i] + + // Only trigger activity updates for changes to branches or + // tags. Updates to other refs (eg, refs/notes, refs/changes, + // or other less-standard refs spaces are ignored since there + // may be a very large number of them). + if strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) { + if repo == nil { + repo = loadRepository(ctx, ownerName, repoName) + if ctx.Written() { + // Error handled in loadRepository + return + } + wasEmpty = repo.IsEmpty + } + + option := repo_module.PushUpdateOptions{ + RefFullName: refFullName, + OldCommitID: opts.OldCommitIDs[i], + NewCommitID: opts.NewCommitIDs[i], + PusherID: opts.UserID, + PusherName: opts.UserName, + RepoUserName: ownerName, + RepoName: repoName, + } + updates = append(updates, &option) + if repo.IsEmpty && option.IsBranch() && (option.BranchName() == "master" || option.BranchName() == "main") { + // put the master/main branch first + copy(updates[1:], updates) + updates[0] = &option + } + } + } + + if repo != nil && len(updates) > 0 { + if err := repo_service.PushUpdates(updates); err != nil { + log.Error("Failed to Update: %s/%s Total Updates: %d", ownerName, repoName, len(updates)) + for i, update := range updates { + log.Error("Failed to Update: %s/%s Update: %d/%d: Branch: %s", ownerName, repoName, i, len(updates), update.BranchName()) + } + log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err) + + ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ + Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err), + }) + return + } + } + + // Handle Push Options + if len(opts.GitPushOptions) > 0 { + // load the repository + if repo == nil { + repo = loadRepository(ctx, ownerName, repoName) + if ctx.Written() { + // Error handled in loadRepository + return + } + wasEmpty = repo.IsEmpty + } + + repo.IsPrivate = opts.GitPushOptions.Bool(private.GitPushOptionRepoPrivate, repo.IsPrivate) + repo.IsTemplate = opts.GitPushOptions.Bool(private.GitPushOptionRepoTemplate, repo.IsTemplate) + if err := models.UpdateRepositoryCols(repo, "is_private", "is_template"); err != nil { + log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err) + ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ + Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err), + }) + } + } + + results := make([]private.HookPostReceiveBranchResult, 0, len(opts.OldCommitIDs)) + + // We have to reload the repo in case its state is changed above + repo = nil + var baseRepo *models.Repository + + // Now handle the pull request notification trailers + for i := range opts.OldCommitIDs { + refFullName := opts.RefFullNames[i] + newCommitID := opts.NewCommitIDs[i] + + branch := git.RefEndName(opts.RefFullNames[i]) + + // If we've pushed a branch (and not deleted it) + if newCommitID != git.EmptySHA && strings.HasPrefix(refFullName, git.BranchPrefix) { + + // First ensure we have the repository loaded, we're allowed pulls requests and we can get the base repo + if repo == nil { + repo = loadRepository(ctx, ownerName, repoName) + if ctx.Written() { + return + } + + if !repo.AllowsPulls() { + // We can stop there's no need to go any further + ctx.JSON(http.StatusOK, private.HookPostReceiveResult{ + RepoWasEmpty: wasEmpty, + }) + return + } + baseRepo = repo + + if repo.IsFork { + if err := repo.GetBaseRepo(); err != nil { + log.Error("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err) + ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ + Err: fmt.Sprintf("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err), + RepoWasEmpty: wasEmpty, + }) + return + } + baseRepo = repo.BaseRepo + } + } + + // If our branch is the default branch of an unforked repo - there's no PR to create or refer to + if !repo.IsFork && branch == baseRepo.DefaultBranch { + results = append(results, private.HookPostReceiveBranchResult{}) + continue + } + + pr, err := models.GetUnmergedPullRequest(repo.ID, baseRepo.ID, branch, baseRepo.DefaultBranch, models.PullRequestFlowGithub) + if err != nil && !models.IsErrPullRequestNotExist(err) { + log.Error("Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err) + ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ + Err: fmt.Sprintf( + "Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err), + RepoWasEmpty: wasEmpty, + }) + return + } + + if pr == nil { + if repo.IsFork { + branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch) + } + results = append(results, private.HookPostReceiveBranchResult{ + Message: setting.Git.PullRequestPushMessage && repo.AllowsPulls(), + Create: true, + Branch: branch, + URL: fmt.Sprintf("%s/compare/%s...%s", baseRepo.HTMLURL(), util.PathEscapeSegments(baseRepo.DefaultBranch), util.PathEscapeSegments(branch)), + }) + } else { + results = append(results, private.HookPostReceiveBranchResult{ + Message: setting.Git.PullRequestPushMessage && repo.AllowsPulls(), + Create: false, + Branch: branch, + URL: fmt.Sprintf("%s/pulls/%d", baseRepo.HTMLURL(), pr.Index), + }) + } + } + } + ctx.JSON(http.StatusOK, private.HookPostReceiveResult{ + Results: results, + RepoWasEmpty: wasEmpty, + }) +} diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go new file mode 100644 index 0000000000000..3e4995376cef8 --- /dev/null +++ b/routers/private/hook_pre_receive.go @@ -0,0 +1,471 @@ +// 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 private includes all internal routes. The package name internal is ideal but Golang is not allowed, so we use private as package name instead. +package private + +import ( + "fmt" + "net/http" + "os" + "strings" + + "code.gitea.io/gitea/models" + gitea_context "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/private" + "code.gitea.io/gitea/modules/web" + pull_service "code.gitea.io/gitea/services/pull" +) + +type preReceiveContext struct { + *gitea_context.PrivateContext + user *models.User + perm models.Permission + + canCreatePullRequest bool + checkedCanCreatePullRequest bool + + canWriteCode bool + checkedCanWriteCode bool + + protectedTags []*models.ProtectedTag + gotProtectedTags bool + + env []string + + opts *private.HookOptions +} + +// User gets or loads User +func (ctx *preReceiveContext) User() *models.User { + if ctx.user == nil { + ctx.user, ctx.perm = loadUserAndPermission(ctx.PrivateContext, ctx.opts.UserID) + } + return ctx.user +} + +// Perm gets or loads Perm +func (ctx *preReceiveContext) Perm() *models.Permission { + if ctx.user == nil { + ctx.user, ctx.perm = loadUserAndPermission(ctx.PrivateContext, ctx.opts.UserID) + } + return &ctx.perm +} + +// CanWriteCode returns true if can write code +func (ctx *preReceiveContext) CanWriteCode() bool { + if !ctx.checkedCanWriteCode { + ctx.canWriteCode = ctx.Perm().CanWrite(models.UnitTypeCode) + ctx.checkedCanWriteCode = true + } + return ctx.canWriteCode +} + +// AssertCanWriteCode returns true if can write code +func (ctx *preReceiveContext) AssertCanWriteCode() bool { + if !ctx.CanWriteCode() { + if ctx.Written() { + return false + } + ctx.JSON(http.StatusForbidden, map[string]interface{}{ + "err": "User permission denied.", + }) + return false + } + return true +} + +// CanCreatePullRequest returns true if can create pull requests +func (ctx *preReceiveContext) CanCreatePullRequest() bool { + if !ctx.checkedCanCreatePullRequest { + ctx.canCreatePullRequest = ctx.Perm().CanRead(models.UnitTypePullRequests) + ctx.checkedCanCreatePullRequest = true + } + return ctx.canCreatePullRequest +} + +// AssertCanCreatePullRequest returns true if can create pull requests +func (ctx *preReceiveContext) AssertCreatePullRequest() bool { + if !ctx.CanCreatePullRequest() { + if ctx.Written() { + return false + } + ctx.JSON(http.StatusForbidden, map[string]interface{}{ + "err": "User permission denied.", + }) + return false + } + return true +} + +// HookPreReceive checks whether a individual commit is acceptable +func HookPreReceive(ctx *gitea_context.PrivateContext) { + opts := web.GetForm(ctx).(*private.HookOptions) + + ourCtx := &preReceiveContext{ + PrivateContext: ctx, + env: generateGitEnv(opts), // Generate git environment for checking commits + opts: opts, + } + + // Iterate across the provided old commit IDs + for i := range opts.OldCommitIDs { + oldCommitID := opts.OldCommitIDs[i] + newCommitID := opts.NewCommitIDs[i] + refFullName := opts.RefFullNames[i] + + switch { + case strings.HasPrefix(refFullName, git.BranchPrefix): + preReceiveBranch(ourCtx, oldCommitID, newCommitID, refFullName) + case strings.HasPrefix(refFullName, git.TagPrefix): + preReceiveTag(ourCtx, oldCommitID, newCommitID, refFullName) + case git.SupportProcReceive && strings.HasPrefix(refFullName, git.PullRequestPrefix): + preReceivePullRequest(ourCtx, oldCommitID, newCommitID, refFullName) + default: + ourCtx.AssertCanWriteCode() + } + if ctx.Written() { + return + } + } + + ctx.PlainText(http.StatusOK, []byte("ok")) +} + +func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID, refFullName string) { + 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) + ctx.JSON(http.StatusForbidden, private.Response{ + Err: fmt.Sprintf("branch %s is the default branch and cannot be deleted", branchName), + }) + return + } + + protectBranch, err := models.GetProtectedBranchBy(repo.ID, branchName) + if err != nil { + log.Error("Unable to get protected branch: %s in %-v Error: %v", branchName, repo, err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: err.Error(), + }) + return + } + + // Allow pushes to non-protected branches + if protectBranch == nil || !protectBranch.IsProtected() { + return + } + + // This ref is a protected branch. + // + // First of all we need to enforce absolutely: + // + // 1. Detect and prevent deletion of the branch + if newCommitID == git.EmptySHA { + log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo) + ctx.JSON(http.StatusForbidden, private.Response{ + Err: fmt.Sprintf("branch %s is protected from deletion", branchName), + }) + return + } + + // 2. Disallow force pushes to protected branches + if git.EmptySHA != oldCommitID { + output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDirWithEnv(repo.RepoPath(), ctx.env) + if err != nil { + log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: fmt.Sprintf("Fail to detect force push: %v", err), + }) + return + } else if len(output) > 0 { + log.Warn("Forbidden: Branch: %s in %-v is protected from force push", branchName, repo) + ctx.JSON(http.StatusForbidden, private.Response{ + Err: fmt.Sprintf("branch %s is protected from force push", branchName), + }) + return + + } + } + + // 3. Enforce require signed commits + if protectBranch.RequireSignedCommits { + err := verifyCommits(oldCommitID, newCommitID, gitRepo, ctx.env) + if err != nil { + if !isErrUnverifiedCommit(err) { + log.Error("Unable to check commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: fmt.Sprintf("Unable to check commits from %s to %s: %v", oldCommitID, newCommitID, err), + }) + return + } + unverifiedCommit := err.(*errUnverifiedCommit).sha + log.Warn("Forbidden: Branch: %s in %-v is protected from unverified commit %s", branchName, repo, unverifiedCommit) + ctx.JSON(http.StatusForbidden, private.Response{ + Err: fmt.Sprintf("branch %s is protected from unverified commit %s", branchName, unverifiedCommit), + }) + return + } + } + + // Now there are several tests which can be overridden: + // + // 4. Check protected file patterns - this is overridable from the UI + changedProtectedfiles := false + protectedFilePath := "" + + globs := protectBranch.GetProtectedFilePatterns() + if len(globs) > 0 { + _, err := pull_service.CheckFileProtection(oldCommitID, newCommitID, globs, 1, ctx.env, gitRepo) + if err != nil { + if !models.IsErrFilePathProtected(err) { + log.Error("Unable to check file protection for commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: fmt.Sprintf("Unable to check file protection for commits from %s to %s: %v", oldCommitID, newCommitID, err), + }) + return + + } + + changedProtectedfiles = true + protectedFilePath = err.(models.ErrFilePathProtected).Path + } + } + + // 5. Check if the doer is allowed to push + canPush := false + if ctx.opts.IsDeployKey { + canPush = !changedProtectedfiles && protectBranch.CanPush && (!protectBranch.EnableWhitelist || protectBranch.WhitelistDeployKeys) + } else { + canPush = !changedProtectedfiles && protectBranch.CanUserPush(ctx.opts.UserID) + } + + // 6. If we're not allowed to push directly + if !canPush { + // Is this is a merge from the UI/API? + if ctx.opts.PullRequestID == 0 { + // 6a. If we're not merging from the UI/API then there are two ways we got here: + // + // We are changing a protected file and we're not allowed to do that + if changedProtectedfiles { + log.Warn("Forbidden: Branch: %s in %-v is protected from changing file %s", branchName, repo, protectedFilePath) + ctx.JSON(http.StatusForbidden, private.Response{ + Err: fmt.Sprintf("branch %s is protected from changing file %s", branchName, protectedFilePath), + }) + return + } + + // Allow commits that only touch unprotected files + globs := protectBranch.GetUnprotectedFilePatterns() + if len(globs) > 0 { + unprotectedFilesOnly, err := pull_service.CheckUnprotectedFiles(oldCommitID, newCommitID, globs, ctx.env, gitRepo) + if err != nil { + log.Error("Unable to check file protection for commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: fmt.Sprintf("Unable to check file protection for commits from %s to %s: %v", oldCommitID, newCommitID, err), + }) + return + } + if unprotectedFilesOnly { + // Commit only touches unprotected files, this is allowed + return + } + } + + // Or we're simply not able to push to this protected branch + log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v", ctx.opts.UserID, branchName, repo) + ctx.JSON(http.StatusForbidden, private.Response{ + Err: fmt.Sprintf("Not allowed to push to protected branch %s", branchName), + }) + return + } + // 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) + if err != nil { + log.Error("Unable to get PullRequest %d Error: %v", ctx.opts.PullRequestID, err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: fmt.Sprintf("Unable to get PullRequest %d Error: %v", ctx.opts.PullRequestID, err), + }) + return + } + + // 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.perm, ctx.user) + if err != nil { + log.Error("Error calculating if allowed to merge: %v", err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: fmt.Sprintf("Error calculating if allowed to merge: %v", err), + }) + return + } + + if !allowedMerge { + log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v and is not allowed to merge pr #%d", ctx.opts.UserID, branchName, repo, pr.Index) + ctx.JSON(http.StatusForbidden, private.Response{ + Err: fmt.Sprintf("Not allowed to push to protected branch %s", branchName), + }) + return + } + + // If we're an admin for the repository we can ignore status checks, reviews and override protected files + if ctx.perm.IsAdmin() { + return + } + + // Now if we're not an admin - we can't overwrite protected files so fail now + if changedProtectedfiles { + log.Warn("Forbidden: Branch: %s in %-v is protected from changing file %s", branchName, repo, protectedFilePath) + ctx.JSON(http.StatusForbidden, private.Response{ + Err: fmt.Sprintf("branch %s is protected from changing file %s", branchName, protectedFilePath), + }) + return + } + + // Check all status checks and reviews are ok + if err := pull_service.CheckPRReadyToMerge(pr, true); err != nil { + if models.IsErrNotAllowedToMerge(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{ + Err: fmt.Sprintf("Not allowed to push to protected branch %s and pr #%d is not ready to be merged: %s", branchName, ctx.opts.PullRequestID, err.Error()), + }) + return + } + log.Error("Unable to check if mergable: protected branch %s in %-v and pr #%d. Error: %v", ctx.opts.UserID, branchName, repo, pr.Index, err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: fmt.Sprintf("Unable to get status of pull request %d. Error: %v", ctx.opts.PullRequestID, err), + }) + return + } + } +} + +func preReceiveTag(ctx *preReceiveContext, oldCommitID, newCommitID, refFullName string) { + if !ctx.AssertCanWriteCode() { + return + } + + tagName := strings.TrimPrefix(refFullName, git.TagPrefix) + + if !ctx.gotProtectedTags { + var err error + ctx.protectedTags, err = ctx.Repo.Repository.GetProtectedTags() + if err != nil { + log.Error("Unable to get protected tags for %-v Error: %v", ctx.Repo.Repository, err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: err.Error(), + }) + return + } + ctx.gotProtectedTags = true + } + + isAllowed, err := models.IsUserAllowedToControlTag(ctx.protectedTags, tagName, ctx.opts.UserID) + if err != nil { + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: err.Error(), + }) + return + } + if !isAllowed { + log.Warn("Forbidden: Tag %s in %-v is protected", tagName, ctx.Repo.Repository) + ctx.JSON(http.StatusForbidden, private.Response{ + Err: fmt.Sprintf("Tag %s is protected", tagName), + }) + return + } +} + +func preReceivePullRequest(ctx *preReceiveContext, oldCommitID, newCommitID, refFullName string) { + if !ctx.AssertCreatePullRequest() { + return + } + + if ctx.Repo.Repository.IsEmpty { + ctx.JSON(http.StatusForbidden, map[string]interface{}{ + "err": "Can't create pull request for an empty repository.", + }) + return + } + + if ctx.opts.IsWiki { + ctx.JSON(http.StatusForbidden, map[string]interface{}{ + "err": "Pull requests are not suppported on the wiki.", + }) + return + } + + baseBranchName := refFullName[len(git.PullRequestPrefix):] + + baseBranchExist := false + if ctx.Repo.GitRepo.IsBranchExist(baseBranchName) { + baseBranchExist = true + } + + if !baseBranchExist { + for p, v := range baseBranchName { + if v == '/' && ctx.Repo.GitRepo.IsBranchExist(baseBranchName[:p]) && p != len(baseBranchName)-1 { + baseBranchExist = true + break + } + } + } + + if !baseBranchExist { + ctx.JSON(http.StatusForbidden, private.Response{ + Err: fmt.Sprintf("Unexpected ref: %s", refFullName), + }) + return + } +} + +func generateGitEnv(opts *private.HookOptions) (env []string) { + env = os.Environ() + if opts.GitAlternativeObjectDirectories != "" { + env = append(env, + private.GitAlternativeObjectDirectories+"="+opts.GitAlternativeObjectDirectories) + } + if opts.GitObjectDirectory != "" { + env = append(env, + private.GitObjectDirectory+"="+opts.GitObjectDirectory) + } + if opts.GitQuarantinePath != "" { + env = append(env, + private.GitQuarantinePath+"="+opts.GitQuarantinePath) + } + return env +} + +func loadUserAndPermission(ctx *gitea_context.PrivateContext, id int64) (user *models.User, perm models.Permission) { + user, err := models.GetUserByID(id) + if err != nil { + log.Error("Unable to get User id %d Error: %v", id, err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: fmt.Sprintf("Unable to get User id %d Error: %v", id, err), + }) + return + } + + perm, err = models.GetUserRepoPermission(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{ + Err: fmt.Sprintf("Unable to get Repo permission of repo %s/%s of User %s: %v", ctx.Repo.Repository.OwnerName, ctx.Repo.Repository.Name, user.Name, err), + }) + return + } + + return +} diff --git a/routers/private/hook_proc_receive.go b/routers/private/hook_proc_receive.go new file mode 100644 index 0000000000000..e427a55c56161 --- /dev/null +++ b/routers/private/hook_proc_receive.go @@ -0,0 +1,34 @@ +// Copyright 2021 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 private includes all internal routes. The package name internal is ideal but Golang is not allowed, so we use private as package name instead. +package private + +import ( + "net/http" + + gitea_context "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/private" + "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/agit" +) + +// HookProcReceive proc-receive hook - only handles agit Proc-Receive requests at present +func HookProcReceive(ctx *gitea_context.PrivateContext) { + opts := web.GetForm(ctx).(*private.HookOptions) + if !git.SupportProcReceive { + ctx.Status(http.StatusNotFound) + return + } + + results := agit.ProcRecive(ctx, opts) + if ctx.Written() { + return + } + + ctx.JSON(http.StatusOK, private.HookProcReceiveResult{ + Results: results, + }) +} diff --git a/routers/private/hook_verification.go b/routers/private/hook_verification.go new file mode 100644 index 0000000000000..8c7492ea6859c --- /dev/null +++ b/routers/private/hook_verification.go @@ -0,0 +1,122 @@ +// Copyright 2021 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 private includes all internal routes. The package name internal is ideal but Golang is not allowed, so we use private as package name instead. +package private + +import ( + "bufio" + "context" + "fmt" + "io" + "os" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" +) + +// _________ .__ __ +// \_ ___ \ ____ _____ _____ |__|/ |_ +// / \ \/ / _ \ / \ / \| \ __\ +// \ \___( <_> ) Y Y \ Y Y \ || | +// \______ /\____/|__|_| /__|_| /__||__| +// \/ \/ \/ +// ____ ____ .__ _____.__ __ .__ +// \ \ / /___________|__|/ ____\__| ____ _____ _/ |_|__| ____ ____ +// \ Y // __ \_ __ \ \ __\| |/ ___\\__ \\ __\ |/ _ \ / \ +// \ /\ ___/| | \/ || | | \ \___ / __ \| | | ( <_> ) | \ +// \___/ \___ >__| |__||__| |__|\___ >____ /__| |__|\____/|___| / +// \/ \/ \/ \/ +// +// This file contains commit verification functions for refs passed across in hooks + +func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env []string) error { + stdoutReader, stdoutWriter, err := os.Pipe() + if err != nil { + log.Error("Unable to create os.Pipe for %s", repo.Path) + return err + } + defer func() { + _ = stdoutReader.Close() + _ = stdoutWriter.Close() + }() + + // This is safe as force pushes are already forbidden + err = git.NewCommand("rev-list", oldCommitID+"..."+newCommitID). + RunInDirTimeoutEnvFullPipelineFunc(env, -1, repo.Path, + stdoutWriter, nil, nil, + func(ctx context.Context, cancel context.CancelFunc) error { + _ = stdoutWriter.Close() + err := readAndVerifyCommitsFromShaReader(stdoutReader, repo, env) + if err != nil { + log.Error("%v", err) + cancel() + } + _ = stdoutReader.Close() + return err + }) + if err != nil && !isErrUnverifiedCommit(err) { + log.Error("Unable to check commits from %s to %s in %s: %v", oldCommitID, newCommitID, repo.Path, err) + } + return err +} + +func readAndVerifyCommitsFromShaReader(input io.ReadCloser, repo *git.Repository, env []string) error { + scanner := bufio.NewScanner(input) + for scanner.Scan() { + line := scanner.Text() + err := readAndVerifyCommit(line, repo, env) + if err != nil { + log.Error("%v", err) + return err + } + } + return scanner.Err() +} + +func readAndVerifyCommit(sha string, repo *git.Repository, env []string) error { + stdoutReader, stdoutWriter, err := os.Pipe() + if err != nil { + log.Error("Unable to create pipe for %s: %v", repo.Path, err) + return err + } + defer func() { + _ = stdoutReader.Close() + _ = stdoutWriter.Close() + }() + hash := git.MustIDFromString(sha) + + return git.NewCommand("cat-file", "commit", sha). + RunInDirTimeoutEnvFullPipelineFunc(env, -1, repo.Path, + stdoutWriter, nil, nil, + func(ctx context.Context, cancel context.CancelFunc) error { + _ = stdoutWriter.Close() + commit, err := git.CommitFromReader(repo, hash, stdoutReader) + if err != nil { + return err + } + verification := models.ParseCommitWithSignature(commit) + if !verification.Verified { + cancel() + return &errUnverifiedCommit{ + commit.ID.String(), + } + } + return nil + }) +} + +type errUnverifiedCommit struct { + sha string +} + +func (e *errUnverifiedCommit) Error() string { + return fmt.Sprintf("Unverified commit: %s", e.sha) +} + +func isErrUnverifiedCommit(err error) bool { + _, ok := err.(*errUnverifiedCommit) + return ok +} diff --git a/routers/private/internal.go b/routers/private/internal.go index 155e8c036ba7b..183ab5e98ac28 100644 --- a/routers/private/internal.go +++ b/routers/private/internal.go @@ -56,10 +56,10 @@ func Routes() *web.Route { r.Post("/ssh/authorized_keys", AuthorizedPublicKeyByContent) r.Post("/ssh/{id}/update/{repoid}", UpdatePublicKeyInRepo) r.Post("/ssh/log", bind(private.SSHLogOption{}), SSHLog) - r.Post("/hook/pre-receive/{owner}/{repo}", bind(private.HookOptions{}), HookPreReceive) + r.Post("/hook/pre-receive/{owner}/{repo}", RepoAssignment, bind(private.HookOptions{}), HookPreReceive) r.Post("/hook/post-receive/{owner}/{repo}", bind(private.HookOptions{}), HookPostReceive) - r.Post("/hook/proc-receive/{owner}/{repo}", bind(private.HookOptions{}), HookProcReceive) - r.Post("/hook/set-default-branch/{owner}/{repo}/{branch}", SetDefaultBranch) + r.Post("/hook/proc-receive/{owner}/{repo}", RepoAssignment, bind(private.HookOptions{}), HookProcReceive) + r.Post("/hook/set-default-branch/{owner}/{repo}/{branch}", RepoAssignment, SetDefaultBranch) r.Get("/serv/none/{keyid}", ServNoCommand) r.Get("/serv/command/{keyid}/{owner}/{repo}", ServCommand) r.Post("/manager/shutdown", Shutdown) diff --git a/routers/private/internal_repo.go b/routers/private/internal_repo.go new file mode 100644 index 0000000000000..60daa1dbeaae2 --- /dev/null +++ b/routers/private/internal_repo.go @@ -0,0 +1,84 @@ +// Copyright 2021 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 private includes all internal routes. The package name internal is ideal but Golang is not allowed, so we use private as package name instead. +package private + +import ( + "context" + "fmt" + "net/http" + + "code.gitea.io/gitea/models" + gitea_context "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" +) + +// __________ +// \______ \ ____ ______ ____ +// | _// __ \\____ \ / _ \ +// | | \ ___/| |_> > <_> ) +// |____|_ /\___ > __/ \____/ +// \/ \/|__| +// _____ .__ __ +// / _ \ ______ _____|__| ____ ____ _____ ____ _____/ |_ +// / /_\ \ / ___// ___/ |/ ___\ / \ / \_/ __ \ / \ __\ +// / | \\___ \ \___ \| / /_/ > | \ Y Y \ ___/| | \ | +// \____|__ /____ >____ >__\___ /|___| /__|_| /\___ >___| /__| +// \/ \/ \/ /_____/ \/ \/ \/ \/ + +// This file contains common functions relating to setting the Repository for the +// internal routes + +// RepoAssignment assigns the repository and gitrepository to the private context +func RepoAssignment(ctx *gitea_context.PrivateContext) context.CancelFunc { + ownerName := ctx.Params(":owner") + repoName := ctx.Params(":repo") + + repo := loadRepository(ctx, ownerName, repoName) + if ctx.Written() { + // Error handled in loadRepository + return nil + } + + gitRepo, err := git.OpenRepository(repo.RepoPath()) + if err != nil { + log.Error("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "Err": fmt.Sprintf("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err), + }) + return nil + } + + ctx.Repo = &gitea_context.Repository{ + Repository: repo, + GitRepo: gitRepo, + } + + // We opened it, we should close it + cancel := func() { + // If it's been set to nil then assume someone else has closed it. + if ctx.Repo.GitRepo != nil { + ctx.Repo.GitRepo.Close() + } + } + + return cancel +} + +func loadRepository(ctx *gitea_context.PrivateContext, ownerName, repoName string) *models.Repository { + repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) + if err != nil { + log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "Err": fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err), + }) + return nil + } + if repo.OwnerName == "" { + repo.OwnerName = ownerName + } + return repo +} diff --git a/routers/private/restore_repo.go b/routers/private/restore_repo.go index 87d60537cd8ad..b7f7ed176f7c9 100644 --- a/routers/private/restore_repo.go +++ b/routers/private/restore_repo.go @@ -5,7 +5,7 @@ package private import ( - "io/ioutil" + "io" "net/http" myCtx "code.gitea.io/gitea/modules/context" @@ -16,7 +16,7 @@ import ( // RestoreRepo restore a repository from data func RestoreRepo(ctx *myCtx.PrivateContext) { - bs, err := ioutil.ReadAll(ctx.Req.Body) + bs, err := io.ReadAll(ctx.Req.Body) if err != nil { ctx.JSON(http.StatusInternalServerError, private.Response{ Err: err.Error(), diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index 342318e04e9b5..803dcafa28bd9 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -11,6 +11,7 @@ import ( "regexp" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/auth/pam" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" @@ -18,6 +19,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" + auth_service "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth/source/ldap" "code.gitea.io/gitea/services/auth/source/oauth2" pamService "code.gitea.io/gitea/services/auth/source/pam" @@ -46,13 +48,13 @@ func Authentications(ctx *context.Context) { ctx.Data["PageIsAdminAuthentications"] = true var err error - ctx.Data["Sources"], err = models.LoginSources() + ctx.Data["Sources"], err = login.Sources() if err != nil { - ctx.ServerError("LoginSources", err) + ctx.ServerError("login.Sources", err) return } - ctx.Data["Total"] = models.CountLoginSources() + ctx.Data["Total"] = login.CountSources() ctx.HTML(http.StatusOK, tplAuths) } @@ -64,14 +66,14 @@ type dropdownItem struct { var ( authSources = func() []dropdownItem { items := []dropdownItem{ - {models.LoginNames[models.LoginLDAP], models.LoginLDAP}, - {models.LoginNames[models.LoginDLDAP], models.LoginDLDAP}, - {models.LoginNames[models.LoginSMTP], models.LoginSMTP}, - {models.LoginNames[models.LoginOAuth2], models.LoginOAuth2}, - {models.LoginNames[models.LoginSSPI], models.LoginSSPI}, + {login.LDAP.String(), login.LDAP}, + {login.DLDAP.String(), login.DLDAP}, + {login.SMTP.String(), login.SMTP}, + {login.OAuth2.String(), login.OAuth2}, + {login.SSPI.String(), login.SSPI}, } if pam.Supported { - items = append(items, dropdownItem{models.LoginNames[models.LoginPAM], models.LoginPAM}) + items = append(items, dropdownItem{login.Names[login.PAM], login.PAM}) } return items }() @@ -89,8 +91,8 @@ func NewAuthSource(ctx *context.Context) { ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminAuthentications"] = true - ctx.Data["type"] = models.LoginLDAP - ctx.Data["CurrentTypeName"] = models.LoginNames[models.LoginLDAP] + ctx.Data["type"] = login.LDAP + ctx.Data["CurrentTypeName"] = login.Names[login.LDAP] ctx.Data["CurrentSecurityProtocol"] = ldap.SecurityProtocolNames[ldap.SecurityProtocolUnencrypted] ctx.Data["smtp_auth"] = "PLAIN" ctx.Data["is_active"] = true @@ -134,6 +136,7 @@ func parseLDAPConfig(form forms.AuthenticationForm) *ldap.Source { AttributeMail: form.AttributeMail, AttributesInBind: form.AttributesInBind, AttributeSSHPublicKey: form.AttributeSSHPublicKey, + AttributeAvatar: form.AttributeAvatar, SearchPageSize: pageSize, Filter: form.Filter, GroupsEnabled: form.GroupsEnabled, @@ -145,6 +148,7 @@ func parseLDAPConfig(form forms.AuthenticationForm) *ldap.Source { RestrictedFilter: form.RestrictedFilter, AllowDeactivateAll: form.AllowDeactivateAll, Enabled: true, + SkipLocalTwoFA: form.SkipLocalTwoFA, } } @@ -158,6 +162,7 @@ func parseSMTPConfig(form forms.AuthenticationForm) *smtp.Source { SkipVerify: form.SkipVerify, HeloHostname: form.HeloHostname, DisableHelo: form.DisableHelo, + SkipLocalTwoFA: form.SkipLocalTwoFA, } } @@ -181,6 +186,7 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source { OpenIDConnectAutoDiscoveryURL: form.OpenIDConnectAutoDiscoveryURL, CustomURLMapping: customURLMapping, IconURL: form.Oauth2IconURL, + SkipLocalTwoFA: form.SkipLocalTwoFA, } } @@ -215,7 +221,7 @@ func NewAuthSourcePost(ctx *context.Context) { ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminAuthentications"] = true - ctx.Data["CurrentTypeName"] = models.LoginNames[models.LoginType(form.Type)] + ctx.Data["CurrentTypeName"] = login.Type(form.Type).String() ctx.Data["CurrentSecurityProtocol"] = ldap.SecurityProtocolNames[ldap.SecurityProtocol(form.SecurityProtocol)] ctx.Data["AuthSources"] = authSources ctx.Data["SecurityProtocols"] = securityProtocols @@ -231,28 +237,29 @@ func NewAuthSourcePost(ctx *context.Context) { hasTLS := false var config convert.Conversion - switch models.LoginType(form.Type) { - case models.LoginLDAP, models.LoginDLDAP: + switch login.Type(form.Type) { + case login.LDAP, login.DLDAP: config = parseLDAPConfig(form) hasTLS = ldap.SecurityProtocol(form.SecurityProtocol) > ldap.SecurityProtocolUnencrypted - case models.LoginSMTP: + case login.SMTP: config = parseSMTPConfig(form) hasTLS = true - case models.LoginPAM: + case login.PAM: config = &pamService.Source{ - ServiceName: form.PAMServiceName, - EmailDomain: form.PAMEmailDomain, + ServiceName: form.PAMServiceName, + EmailDomain: form.PAMEmailDomain, + SkipLocalTwoFA: form.SkipLocalTwoFA, } - case models.LoginOAuth2: + case login.OAuth2: config = parseOAuth2Config(form) - case models.LoginSSPI: + case login.SSPI: var err error config, err = parseSSPIConfig(ctx, form) if err != nil { ctx.RenderWithErr(err.Error(), tplAuthNew, form) return } - existing, err := models.LoginSourcesByType(models.LoginSSPI) + existing, err := login.SourcesByType(login.SSPI) if err != nil || len(existing) > 0 { ctx.Data["Err_Type"] = true ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_of_type_exist"), tplAuthNew, form) @@ -269,18 +276,18 @@ func NewAuthSourcePost(ctx *context.Context) { return } - if err := models.CreateLoginSource(&models.LoginSource{ - Type: models.LoginType(form.Type), + if err := login.CreateSource(&login.Source{ + Type: login.Type(form.Type), Name: form.Name, IsActive: form.IsActive, IsSyncEnabled: form.IsSyncEnabled, Cfg: config, }); err != nil { - if models.IsErrLoginSourceAlreadyExist(err) { + if login.IsErrSourceAlreadyExist(err) { ctx.Data["Err_Name"] = true - ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_exist", err.(models.ErrLoginSourceAlreadyExist).Name), tplAuthNew, form) + ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_exist", err.(login.ErrSourceAlreadyExist).Name), tplAuthNew, form) } else { - ctx.ServerError("CreateSource", err) + ctx.ServerError("login.CreateSource", err) } return } @@ -302,9 +309,9 @@ func EditAuthSource(ctx *context.Context) { oauth2providers := oauth2.GetOAuth2Providers() ctx.Data["OAuth2Providers"] = oauth2providers - source, err := models.GetLoginSourceByID(ctx.ParamsInt64(":authid")) + source, err := login.GetSourceByID(ctx.ParamsInt64(":authid")) if err != nil { - ctx.ServerError("GetLoginSourceByID", err) + ctx.ServerError("login.GetSourceByID", err) return } ctx.Data["Source"] = source @@ -337,9 +344,9 @@ func EditAuthSourcePost(ctx *context.Context) { oauth2providers := oauth2.GetOAuth2Providers() ctx.Data["OAuth2Providers"] = oauth2providers - source, err := models.GetLoginSourceByID(ctx.ParamsInt64(":authid")) + source, err := login.GetSourceByID(ctx.ParamsInt64(":authid")) if err != nil { - ctx.ServerError("GetLoginSourceByID", err) + ctx.ServerError("login.GetSourceByID", err) return } ctx.Data["Source"] = source @@ -351,19 +358,19 @@ func EditAuthSourcePost(ctx *context.Context) { } var config convert.Conversion - switch models.LoginType(form.Type) { - case models.LoginLDAP, models.LoginDLDAP: + switch login.Type(form.Type) { + case login.LDAP, login.DLDAP: config = parseLDAPConfig(form) - case models.LoginSMTP: + case login.SMTP: config = parseSMTPConfig(form) - case models.LoginPAM: + case login.PAM: config = &pamService.Source{ ServiceName: form.PAMServiceName, EmailDomain: form.PAMEmailDomain, } - case models.LoginOAuth2: + case login.OAuth2: config = parseOAuth2Config(form) - case models.LoginSSPI: + case login.SSPI: config, err = parseSSPIConfig(ctx, form) if err != nil { ctx.RenderWithErr(err.Error(), tplAuthEdit, form) @@ -378,7 +385,7 @@ func EditAuthSourcePost(ctx *context.Context) { source.IsActive = form.IsActive source.IsSyncEnabled = form.IsSyncEnabled source.Cfg = config - if err := models.UpdateSource(source); err != nil { + if err := login.UpdateSource(source); err != nil { if models.IsErrOpenIDConnectInitialize(err) { ctx.Flash.Error(err.Error(), true) ctx.HTML(http.StatusOK, tplAuthEdit) @@ -395,17 +402,17 @@ func EditAuthSourcePost(ctx *context.Context) { // DeleteAuthSource response for deleting an auth source func DeleteAuthSource(ctx *context.Context) { - source, err := models.GetLoginSourceByID(ctx.ParamsInt64(":authid")) + source, err := login.GetSourceByID(ctx.ParamsInt64(":authid")) if err != nil { - ctx.ServerError("GetLoginSourceByID", err) + ctx.ServerError("login.GetSourceByID", err) return } - if err = models.DeleteSource(source); err != nil { - if models.IsErrLoginSourceInUse(err) { + if err = auth_service.DeleteLoginSource(source); err != nil { + if login.IsErrSourceInUse(err) { ctx.Flash.Error(ctx.Tr("admin.auths.still_in_used")) } else { - ctx.Flash.Error(fmt.Sprintf("DeleteSource: %v", err)) + ctx.Flash.Error(fmt.Sprintf("DeleteLoginSource: %v", err)) } ctx.JSON(http.StatusOK, map[string]interface{}{ "redirect": setting.AppSubURL + "/admin/auths/" + ctx.Params(":authid"), diff --git a/routers/web/admin/emails.go b/routers/web/admin/emails.go index 017d696e202de..5cbe70020b703 100644 --- a/routers/web/admin/emails.go +++ b/routers/web/admin/emails.go @@ -10,6 +10,7 @@ import ( "net/url" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" @@ -28,7 +29,7 @@ func Emails(ctx *context.Context) { ctx.Data["PageIsAdminEmails"] = true opts := &models.SearchEmailOptions{ - ListOptions: models.ListOptions{ + ListOptions: db.ListOptions{ PageSize: setting.UI.Admin.UserPagingNum, Page: ctx.FormInt("page"), }, diff --git a/routers/web/admin/main_test.go b/routers/web/admin/main_test.go index 352907c73717c..cfdefdb1b4afd 100644 --- a/routers/web/admin/main_test.go +++ b/routers/web/admin/main_test.go @@ -8,9 +8,9 @@ import ( "path/filepath" "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..", "..")) + db.MainTest(m, filepath.Join("..", "..", "..")) } diff --git a/routers/web/admin/orgs.go b/routers/web/admin/orgs.go index a2b3ed1bcc0fe..df3118b60f22c 100644 --- a/routers/web/admin/orgs.go +++ b/routers/web/admin/orgs.go @@ -7,6 +7,7 @@ package admin import ( "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" @@ -27,7 +28,7 @@ func Organizations(ctx *context.Context) { explore.RenderUserSearch(ctx, &models.SearchUserOptions{ Actor: ctx.User, Type: models.UserTypeOrganization, - ListOptions: models.ListOptions{ + ListOptions: db.ListOptions{ PageSize: setting.UI.Admin.OrgPagingNum, }, Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate}, diff --git a/routers/web/admin/repos.go b/routers/web/admin/repos.go index 4c3f2ad614ee8..2f4d182af8fe4 100644 --- a/routers/web/admin/repos.go +++ b/routers/web/admin/repos.go @@ -10,6 +10,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" @@ -68,7 +69,7 @@ func UnadoptedRepos(ctx *context.Context) { ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminRepositories"] = true - opts := models.ListOptions{ + opts := db.ListOptions{ PageSize: setting.UI.Admin.UserPagingNum, Page: ctx.FormInt("page"), } diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index acccc516bb456..ea666ab4d4dbb 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -12,6 +12,8 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" @@ -39,7 +41,7 @@ func Users(ctx *context.Context) { explore.RenderUserSearch(ctx, &models.SearchUserOptions{ Actor: ctx.User, Type: models.UserTypeIndividual, - ListOptions: models.ListOptions{ + ListOptions: db.ListOptions{ PageSize: setting.UI.Admin.UserPagingNum, }, SearchByEmail: true, @@ -56,9 +58,9 @@ func NewUser(ctx *context.Context) { ctx.Data["login_type"] = "0-0" - sources, err := models.LoginSources() + sources, err := login.Sources() if err != nil { - ctx.ServerError("LoginSources", err) + ctx.ServerError("login.Sources", err) return } ctx.Data["Sources"] = sources @@ -75,9 +77,9 @@ func NewUserPost(ctx *context.Context) { ctx.Data["PageIsAdminUsers"] = true ctx.Data["DefaultUserVisibilityMode"] = setting.Service.DefaultUserVisibilityMode - sources, err := models.LoginSources() + sources, err := login.Sources() if err != nil { - ctx.ServerError("LoginSources", err) + ctx.ServerError("login.Sources", err) return } ctx.Data["Sources"] = sources @@ -94,19 +96,19 @@ func NewUserPost(ctx *context.Context) { Email: form.Email, Passwd: form.Password, IsActive: true, - LoginType: models.LoginPlain, + LoginType: login.Plain, } if len(form.LoginType) > 0 { fields := strings.Split(form.LoginType, "-") if len(fields) == 2 { lType, _ := strconv.ParseInt(fields[0], 10, 0) - u.LoginType = models.LoginType(lType) + u.LoginType = login.Type(lType) u.LoginSource, _ = strconv.ParseInt(fields[1], 10, 64) u.LoginName = form.LoginName } } - if u.LoginType == models.LoginNoType || u.LoginType == models.LoginPlain { + if u.LoginType == login.NoType || u.LoginType == login.Plain { if len(form.Password) < setting.MinPasswordLength { ctx.Data["Err_Password"] = true ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplUserNew, &form) @@ -176,26 +178,26 @@ func prepareUserInfo(ctx *context.Context) *models.User { ctx.Data["User"] = u if u.LoginSource > 0 { - ctx.Data["LoginSource"], err = models.GetLoginSourceByID(u.LoginSource) + ctx.Data["LoginSource"], err = login.GetSourceByID(u.LoginSource) if err != nil { - ctx.ServerError("GetLoginSourceByID", err) + ctx.ServerError("login.GetSourceByID", err) return nil } } else { - ctx.Data["LoginSource"] = &models.LoginSource{} + ctx.Data["LoginSource"] = &login.Source{} } - sources, err := models.LoginSources() + sources, err := login.Sources() if err != nil { - ctx.ServerError("LoginSources", err) + ctx.ServerError("login.Sources", err) return nil } ctx.Data["Sources"] = sources ctx.Data["TwoFactorEnabled"] = true - _, err = models.GetTwoFactorByUID(u.ID) + _, err = login.GetTwoFactorByUID(u.ID) if err != nil { - if !models.IsErrTwoFactorNotEnrolled(err) { + if !login.IsErrTwoFactorNotEnrolled(err) { ctx.ServerError("IsErrTwoFactorNotEnrolled", err) return nil } @@ -247,7 +249,7 @@ func EditUserPost(ctx *context.Context) { if u.LoginSource != loginSource { u.LoginSource = loginSource - u.LoginType = models.LoginType(loginType) + u.LoginType = login.Type(loginType) } } @@ -293,13 +295,13 @@ func EditUserPost(ctx *context.Context) { } if form.Reset2FA { - tf, err := models.GetTwoFactorByUID(u.ID) - if err != nil && !models.IsErrTwoFactorNotEnrolled(err) { + tf, err := login.GetTwoFactorByUID(u.ID) + if err != nil && !login.IsErrTwoFactorNotEnrolled(err) { ctx.ServerError("GetTwoFactorByUID", err) return } - if err = models.DeleteTwoFactorByID(tf.ID, u.ID); err != nil { + if err = login.DeleteTwoFactorByID(tf.ID, u.ID); err != nil { ctx.ServerError("DeleteTwoFactorByID", err) return } diff --git a/routers/web/admin/users_test.go b/routers/web/admin/users_test.go index 3d0b11a77449c..022d8f662c8f1 100644 --- a/routers/web/admin/users_test.go +++ b/routers/web/admin/users_test.go @@ -8,6 +8,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" @@ -19,10 +20,10 @@ import ( func TestNewUserPost_MustChangePassword(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "admin/users/new") - u := models.AssertExistsAndLoadBean(t, &models.User{ + u := db.AssertExistsAndLoadBean(t, &models.User{ IsAdmin: true, ID: 2, }).(*models.User) @@ -56,10 +57,10 @@ func TestNewUserPost_MustChangePassword(t *testing.T) { } func TestNewUserPost_MustChangePasswordFalse(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "admin/users/new") - u := models.AssertExistsAndLoadBean(t, &models.User{ + u := db.AssertExistsAndLoadBean(t, &models.User{ IsAdmin: true, ID: 2, }).(*models.User) @@ -93,10 +94,10 @@ func TestNewUserPost_MustChangePasswordFalse(t *testing.T) { } func TestNewUserPost_InvalidEmail(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "admin/users/new") - u := models.AssertExistsAndLoadBean(t, &models.User{ + u := db.AssertExistsAndLoadBean(t, &models.User{ IsAdmin: true, ID: 2, }).(*models.User) @@ -123,10 +124,10 @@ func TestNewUserPost_InvalidEmail(t *testing.T) { } func TestNewUserPost_VisibilityDefaultPublic(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "admin/users/new") - u := models.AssertExistsAndLoadBean(t, &models.User{ + u := db.AssertExistsAndLoadBean(t, &models.User{ IsAdmin: true, ID: 2, }).(*models.User) @@ -161,10 +162,10 @@ func TestNewUserPost_VisibilityDefaultPublic(t *testing.T) { } func TestNewUserPost_VisibilityPrivate(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "admin/users/new") - u := models.AssertExistsAndLoadBean(t, &models.User{ + u := db.AssertExistsAndLoadBean(t, &models.User{ IsAdmin: true, ID: 2, }).(*models.User) diff --git a/routers/web/base.go b/routers/web/base.go index 9238ea217317a..f50c5229b1d23 100644 --- a/routers/web/base.go +++ b/routers/web/base.go @@ -14,7 +14,6 @@ import ( "path/filepath" "strings" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/log" @@ -147,15 +146,7 @@ func Recovery() func(next http.Handler) http.Handler { "i18n": lc, } - var user *models.User - if apiContext := context.GetAPIContext(req); apiContext != nil { - user = apiContext.User - } - if user == nil { - if ctx := context.GetContext(req); ctx != nil { - user = ctx.User - } - } + var user = context.GetContextUser(req) if user == nil { // Get user from session if logged in - do not attempt to sign-in user = auth.SessionUser(sessionStore) diff --git a/routers/web/events/events.go b/routers/web/events/events.go index a630d9c224e8e..974aa755d16ce 100644 --- a/routers/web/events/events.go +++ b/routers/web/events/events.go @@ -9,6 +9,7 @@ import ( "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" @@ -93,7 +94,7 @@ loop: go unregister() break loop case <-stopwatchTimer.C: - sws, err := models.GetUserStopwatches(ctx.User.ID, models.ListOptions{}) + sws, err := models.GetUserStopwatches(ctx.User.ID, db.ListOptions{}) if err != nil { log.Error("Unable to GetUserStopwatches: %v", err) continue diff --git a/routers/web/explore/org.go b/routers/web/explore/org.go index 470e0eb8530b1..d005cfa50322d 100644 --- a/routers/web/explore/org.go +++ b/routers/web/explore/org.go @@ -6,6 +6,7 @@ package explore import ( "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" @@ -33,7 +34,7 @@ func Organizations(ctx *context.Context) { RenderUserSearch(ctx, &models.SearchUserOptions{ Actor: ctx.User, Type: models.UserTypeOrganization, - ListOptions: models.ListOptions{PageSize: setting.UI.ExplorePagingNum}, + ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum}, Visible: visibleTypes, }, tplExploreOrganizations) } diff --git a/routers/web/explore/repo.go b/routers/web/explore/repo.go index dfc6261b33ed1..78035037e5105 100644 --- a/routers/web/explore/repo.go +++ b/routers/web/explore/repo.go @@ -8,6 +8,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" @@ -77,7 +78,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { ctx.Data["TopicOnly"] = topicOnly repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ - ListOptions: models.ListOptions{ + ListOptions: db.ListOptions{ Page: page, PageSize: opts.PageSize, }, diff --git a/routers/web/explore/user.go b/routers/web/explore/user.go index aeaaf92c12216..4ddb90132d165 100644 --- a/routers/web/explore/user.go +++ b/routers/web/explore/user.go @@ -9,6 +9,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" @@ -99,7 +100,7 @@ func Users(ctx *context.Context) { RenderUserSearch(ctx, &models.SearchUserOptions{ Actor: ctx.User, Type: models.UserTypeIndividual, - ListOptions: models.ListOptions{PageSize: setting.UI.ExplorePagingNum}, + ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum}, IsActive: util.OptionalBoolTrue, Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate}, }, tplExploreUsers) diff --git a/routers/web/nodeinfo.go b/routers/web/nodeinfo.go new file mode 100644 index 0000000000000..2ee94e35e86b9 --- /dev/null +++ b/routers/web/nodeinfo.go @@ -0,0 +1,33 @@ +// Copyright 2021 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 web + +import ( + "fmt" + "net/http" + + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/setting" +) + +type nodeInfoLinks struct { + Links []nodeInfoLink `json:"links"` +} + +type nodeInfoLink struct { + Href string `json:"href"` + Rel string `json:"rel"` +} + +// NodeInfoLinks returns links to the node info endpoint +func NodeInfoLinks(ctx *context.Context) { + nodeinfolinks := &nodeInfoLinks{ + Links: []nodeInfoLink{{ + fmt.Sprintf("%sapi/v1/nodeinfo", setting.AppURL), + "http://nodeinfo.diaspora.software/ns/schema/2.1", + }}, + } + ctx.JSON(http.StatusOK, nodeinfolinks) +} diff --git a/routers/web/org/home.go b/routers/web/org/home.go index f682dc5cb697f..89bd12a18f78f 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -8,6 +8,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/markup" @@ -91,7 +92,7 @@ func Home(ctx *context.Context) { err error ) repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ - ListOptions: models.ListOptions{ + ListOptions: db.ListOptions{ PageSize: setting.UI.User.RepoPagingNum, Page: page, }, @@ -110,7 +111,7 @@ func Home(ctx *context.Context) { var opts = &models.FindOrgMembersOpts{ OrgID: org.ID, PublicOnly: true, - ListOptions: models.ListOptions{Page: 1, PageSize: 25}, + ListOptions: db.ListOptions{Page: 1, PageSize: 25}, } if ctx.User != nil { diff --git a/routers/web/org/org_labels.go b/routers/web/org/org_labels.go index 97a5437c61ee8..5079d9baa71d7 100644 --- a/routers/web/org/org_labels.go +++ b/routers/web/org/org_labels.go @@ -8,6 +8,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/forms" @@ -15,7 +16,7 @@ import ( // RetrieveLabels find all the labels of an organization func RetrieveLabels(ctx *context.Context) { - labels, err := models.GetLabelsByOrgID(ctx.Org.Organization.ID, ctx.FormString("sort"), models.ListOptions{}) + labels, err := models.GetLabelsByOrgID(ctx.Org.Organization.ID, ctx.FormString("sort"), db.ListOptions{}) if err != nil { ctx.ServerError("RetrieveLabels.GetLabels", err) return @@ -98,7 +99,7 @@ func InitializeLabels(ctx *context.Context) { return } - if err := models.InitializeLabels(models.DefaultDBContext(), ctx.Org.Organization.ID, form.TemplateName, true); err != nil { + if err := models.InitializeLabels(db.DefaultContext, ctx.Org.Organization.ID, form.TemplateName, true); err != nil { if models.IsErrIssueLabelTemplateLoad(err) { originalErr := err.(models.ErrIssueLabelTemplateLoad).OriginalError ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr)) diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index 7e6fc5bf4cd95..277ff9d97359b 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -10,6 +10,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" @@ -103,7 +104,7 @@ func SettingsPost(ctx *context.Context) { // update forks visibility if visibilityChanged { - if err := org.GetRepositories(models.ListOptions{Page: 1, PageSize: org.NumRepos}); err != nil { + if err := org.GetRepositories(db.ListOptions{Page: 1, PageSize: org.NumRepos}); err != nil { ctx.ServerError("GetRepositories", err) return } diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 810581640cc27..c5f3f70c1c854 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -12,6 +12,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/context" @@ -287,7 +288,7 @@ func Diff(ctx *context.Context) { commitID = commit.ID.String() } - statuses, err := models.GetLatestCommitStatus(ctx.Repo.Repository.ID, commitID, models.ListOptions{}) + statuses, err := models.GetLatestCommitStatus(ctx.Repo.Repository.ID, commitID, db.ListOptions{}) if err != nil { log.Error("GetLatestCommitStatus: %v", err) } @@ -298,7 +299,8 @@ func Diff(ctx *context.Context) { diff, err := gitdiff.GetDiffCommitWithWhitespaceBehavior(gitRepo, commitID, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, - gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string))) + gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)), + false) if err != nil { ctx.NotFound("GetDiffCommitWithWhitespaceBehavior", err) return diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index eb6c37a1a6363..556a6218a6b84 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -145,9 +145,21 @@ func setCsvCompareContext(ctx *context.Context) { } } +// CompareInfo represents the collected results from ParseCompareInfo +type CompareInfo struct { + HeadUser *models.User + HeadRepo *models.Repository + HeadGitRepo *git.Repository + CompareInfo *git.CompareInfo + BaseBranch string + HeadBranch string + DirectComparison bool +} + // ParseCompareInfo parse compare info between two commit for preparing comparing references -func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, *git.Repository, *git.CompareInfo, string, string) { +func ParseCompareInfo(ctx *context.Context) *CompareInfo { baseRepo := ctx.Repo.Repository + ci := &CompareInfo{} // Get compared branches information // A full compare url is of the form: @@ -173,92 +185,97 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, * // same repo: master...feature var ( - headUser *models.User - headRepo *models.Repository - headBranch string isSameRepo bool infoPath string err error ) + infoPath = ctx.Params("*") infos := strings.SplitN(infoPath, "...", 2) + + if len(infos) != 2 { + infos = strings.SplitN(infoPath, "..", 2) + ci.DirectComparison = true + ctx.Data["PageIsComparePull"] = false + } + if len(infos) != 2 { log.Trace("ParseCompareInfo[%d]: not enough compared branches information %s", baseRepo.ID, infos) ctx.NotFound("CompareAndPullRequest", nil) - return nil, nil, nil, nil, "", "" + return nil } ctx.Data["BaseName"] = baseRepo.OwnerName - baseBranch := infos[0] - ctx.Data["BaseBranch"] = baseBranch + ci.BaseBranch = infos[0] + ctx.Data["BaseBranch"] = ci.BaseBranch // If there is no head repository, it means compare between same repository. headInfos := strings.Split(infos[1], ":") if len(headInfos) == 1 { isSameRepo = true - headUser = ctx.Repo.Owner - headBranch = headInfos[0] + ci.HeadUser = ctx.Repo.Owner + ci.HeadBranch = headInfos[0] } else if len(headInfos) == 2 { headInfosSplit := strings.Split(headInfos[0], "/") if len(headInfosSplit) == 1 { - headUser, err = models.GetUserByName(headInfos[0]) + ci.HeadUser, err = models.GetUserByName(headInfos[0]) if err != nil { if models.IsErrUserNotExist(err) { ctx.NotFound("GetUserByName", nil) } else { ctx.ServerError("GetUserByName", err) } - return nil, nil, nil, nil, "", "" + return nil } - headBranch = headInfos[1] - isSameRepo = headUser.ID == ctx.Repo.Owner.ID + ci.HeadBranch = headInfos[1] + isSameRepo = ci.HeadUser.ID == ctx.Repo.Owner.ID if isSameRepo { - headRepo = baseRepo + ci.HeadRepo = baseRepo } } else { - headRepo, err = models.GetRepositoryByOwnerAndName(headInfosSplit[0], headInfosSplit[1]) + ci.HeadRepo, err = models.GetRepositoryByOwnerAndName(headInfosSplit[0], headInfosSplit[1]) if err != nil { if models.IsErrRepoNotExist(err) { ctx.NotFound("GetRepositoryByOwnerAndName", nil) } else { ctx.ServerError("GetRepositoryByOwnerAndName", err) } - return nil, nil, nil, nil, "", "" + return nil } - if err := headRepo.GetOwner(); err != nil { + if err := ci.HeadRepo.GetOwner(); err != nil { if models.IsErrUserNotExist(err) { ctx.NotFound("GetUserByName", nil) } else { ctx.ServerError("GetUserByName", err) } - return nil, nil, nil, nil, "", "" + return nil } - headBranch = headInfos[1] - headUser = headRepo.Owner - isSameRepo = headRepo.ID == ctx.Repo.Repository.ID + ci.HeadBranch = headInfos[1] + ci.HeadUser = ci.HeadRepo.Owner + isSameRepo = ci.HeadRepo.ID == ctx.Repo.Repository.ID } } else { ctx.NotFound("CompareAndPullRequest", nil) - return nil, nil, nil, nil, "", "" + return nil } - ctx.Data["HeadUser"] = headUser - ctx.Data["HeadBranch"] = headBranch + ctx.Data["HeadUser"] = ci.HeadUser + ctx.Data["HeadBranch"] = ci.HeadBranch ctx.Repo.PullRequest.SameRepo = isSameRepo // Check if base branch is valid. - baseIsCommit := ctx.Repo.GitRepo.IsCommitExist(baseBranch) - baseIsBranch := ctx.Repo.GitRepo.IsBranchExist(baseBranch) - baseIsTag := ctx.Repo.GitRepo.IsTagExist(baseBranch) + baseIsCommit := ctx.Repo.GitRepo.IsCommitExist(ci.BaseBranch) + baseIsBranch := ctx.Repo.GitRepo.IsBranchExist(ci.BaseBranch) + baseIsTag := ctx.Repo.GitRepo.IsTagExist(ci.BaseBranch) if !baseIsCommit && !baseIsBranch && !baseIsTag { // Check if baseBranch is short sha commit hash - if baseCommit, _ := ctx.Repo.GitRepo.GetCommit(baseBranch); baseCommit != nil { - baseBranch = baseCommit.ID.String() - ctx.Data["BaseBranch"] = baseBranch + if baseCommit, _ := ctx.Repo.GitRepo.GetCommit(ci.BaseBranch); baseCommit != nil { + ci.BaseBranch = baseCommit.ID.String() + ctx.Data["BaseBranch"] = ci.BaseBranch baseIsCommit = true } else { ctx.NotFound("IsRefExist", nil) - return nil, nil, nil, nil, "", "" + return nil } } ctx.Data["BaseIsCommit"] = baseIsCommit @@ -284,7 +301,7 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, * if err != nil { if !models.IsErrRepoNotExist(err) { ctx.ServerError("Unable to find root repo", err) - return nil, nil, nil, nil, "", "" + return nil } } else { rootRepo = baseRepo.BaseRepo @@ -303,29 +320,29 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, * } } - has := headRepo != nil + has := ci.HeadRepo != nil // 3. If the base is a forked from "RootRepo" and the owner of // the "RootRepo" is the :headUser - set headRepo to that - if !has && rootRepo != nil && rootRepo.OwnerID == headUser.ID { - headRepo = rootRepo + if !has && rootRepo != nil && rootRepo.OwnerID == ci.HeadUser.ID { + ci.HeadRepo = rootRepo has = true } // 4. If the ctx.User has their own fork of the baseRepo and the headUser is the ctx.User // set the headRepo to the ownFork - if !has && ownForkRepo != nil && ownForkRepo.OwnerID == headUser.ID { - headRepo = ownForkRepo + if !has && ownForkRepo != nil && ownForkRepo.OwnerID == ci.HeadUser.ID { + ci.HeadRepo = ownForkRepo has = true } // 5. If the headOwner has a fork of the baseRepo - use that if !has { - headRepo, has = models.HasForkedRepo(headUser.ID, baseRepo.ID) + ci.HeadRepo, has = models.HasForkedRepo(ci.HeadUser.ID, baseRepo.ID) } // 6. If the baseRepo is a fork and the headUser has a fork of that use that if !has && baseRepo.IsFork { - headRepo, has = models.HasForkedRepo(headUser.ID, baseRepo.ForkID) + ci.HeadRepo, has = models.HasForkedRepo(ci.HeadUser.ID, baseRepo.ForkID) } // 7. Otherwise if we're not the same repo and haven't found a repo give up @@ -334,20 +351,19 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, * } // 8. Finally open the git repo - var headGitRepo *git.Repository if isSameRepo { - headRepo = ctx.Repo.Repository - headGitRepo = ctx.Repo.GitRepo + ci.HeadRepo = ctx.Repo.Repository + ci.HeadGitRepo = ctx.Repo.GitRepo } else if has { - headGitRepo, err = git.OpenRepository(headRepo.RepoPath()) + ci.HeadGitRepo, err = git.OpenRepository(ci.HeadRepo.RepoPath()) if err != nil { ctx.ServerError("OpenRepository", err) - return nil, nil, nil, nil, "", "" + return nil } - defer headGitRepo.Close() + defer ci.HeadGitRepo.Close() } - ctx.Data["HeadRepo"] = headRepo + ctx.Data["HeadRepo"] = ci.HeadRepo // Now we need to assert that the ctx.User has permission to read // the baseRepo's code and pulls @@ -355,7 +371,7 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, * permBase, err := models.GetUserRepoPermission(baseRepo, ctx.User) if err != nil { ctx.ServerError("GetUserRepoPermission", err) - return nil, nil, nil, nil, "", "" + return nil } if !permBase.CanRead(models.UnitTypeCode) { if log.IsTrace() { @@ -365,26 +381,26 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, * permBase) } ctx.NotFound("ParseCompareInfo", nil) - return nil, nil, nil, nil, "", "" + return nil } // If we're not merging from the same repo: if !isSameRepo { // Assert ctx.User has permission to read headRepo's codes - permHead, err := models.GetUserRepoPermission(headRepo, ctx.User) + permHead, err := models.GetUserRepoPermission(ci.HeadRepo, ctx.User) if err != nil { ctx.ServerError("GetUserRepoPermission", err) - return nil, nil, nil, nil, "", "" + return nil } if !permHead.CanRead(models.UnitTypeCode) { if log.IsTrace() { log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v", ctx.User, - headRepo, + ci.HeadRepo, permHead) } ctx.NotFound("ParseCompareInfo", nil) - return nil, nil, nil, nil, "", "" + return nil } } @@ -393,12 +409,12 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, * // 2. the computed head // then get the branches of it if rootRepo != nil && - rootRepo.ID != headRepo.ID && + rootRepo.ID != ci.HeadRepo.ID && rootRepo.ID != baseRepo.ID { perm, branches, tags, err := getBranchesAndTagsForRepo(ctx.User, rootRepo) if err != nil { ctx.ServerError("GetBranchesForRepo", err) - return nil, nil, nil, nil, "", "" + return nil } if perm { ctx.Data["RootRepo"] = rootRepo @@ -413,13 +429,13 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, * // 3. The rootRepo (if we have one) // then get the branches from it. if ownForkRepo != nil && - ownForkRepo.ID != headRepo.ID && + ownForkRepo.ID != ci.HeadRepo.ID && ownForkRepo.ID != baseRepo.ID && (rootRepo == nil || ownForkRepo.ID != rootRepo.ID) { perm, branches, tags, err := getBranchesAndTagsForRepo(ctx.User, ownForkRepo) if err != nil { ctx.ServerError("GetBranchesForRepo", err) - return nil, nil, nil, nil, "", "" + return nil } if perm { ctx.Data["OwnForkRepo"] = ownForkRepo @@ -429,18 +445,18 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, * } // Check if head branch is valid. - headIsCommit := headGitRepo.IsCommitExist(headBranch) - headIsBranch := headGitRepo.IsBranchExist(headBranch) - headIsTag := headGitRepo.IsTagExist(headBranch) + headIsCommit := ci.HeadGitRepo.IsCommitExist(ci.HeadBranch) + headIsBranch := ci.HeadGitRepo.IsBranchExist(ci.HeadBranch) + headIsTag := ci.HeadGitRepo.IsTagExist(ci.HeadBranch) if !headIsCommit && !headIsBranch && !headIsTag { // Check if headBranch is short sha commit hash - if headCommit, _ := headGitRepo.GetCommit(headBranch); headCommit != nil { - headBranch = headCommit.ID.String() - ctx.Data["HeadBranch"] = headBranch + if headCommit, _ := ci.HeadGitRepo.GetCommit(ci.HeadBranch); headCommit != nil { + ci.HeadBranch = headCommit.ID.String() + ctx.Data["HeadBranch"] = ci.HeadBranch headIsCommit = true } else { ctx.NotFound("IsRefExist", nil) - return nil, nil, nil, nil, "", "" + return nil } } ctx.Data["HeadIsCommit"] = headIsCommit @@ -460,40 +476,36 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, * permBase) } ctx.NotFound("ParseCompareInfo", nil) - return nil, nil, nil, nil, "", "" + return nil } - baseBranchRef := baseBranch + baseBranchRef := ci.BaseBranch if baseIsBranch { - baseBranchRef = git.BranchPrefix + baseBranch + baseBranchRef = git.BranchPrefix + ci.BaseBranch } else if baseIsTag { - baseBranchRef = git.TagPrefix + baseBranch + baseBranchRef = git.TagPrefix + ci.BaseBranch } - headBranchRef := headBranch + headBranchRef := ci.HeadBranch if headIsBranch { - headBranchRef = git.BranchPrefix + headBranch + headBranchRef = git.BranchPrefix + ci.HeadBranch } else if headIsTag { - headBranchRef = git.TagPrefix + headBranch + headBranchRef = git.TagPrefix + ci.HeadBranch } - compareInfo, err := headGitRepo.GetCompareInfo(baseRepo.RepoPath(), baseBranchRef, headBranchRef) + ci.CompareInfo, err = ci.HeadGitRepo.GetCompareInfo(baseRepo.RepoPath(), baseBranchRef, headBranchRef, ci.DirectComparison) if err != nil { ctx.ServerError("GetCompareInfo", err) - return nil, nil, nil, nil, "", "" + return nil } - ctx.Data["BeforeCommitID"] = compareInfo.MergeBase + ctx.Data["BeforeCommitID"] = ci.CompareInfo.MergeBase - return headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch + return ci } // PrepareCompareDiff renders compare diff page func PrepareCompareDiff( ctx *context.Context, - headUser *models.User, - headRepo *models.Repository, - headGitRepo *git.Repository, - compareInfo *git.CompareInfo, - baseBranch, headBranch string, + ci *CompareInfo, whitespaceBehavior string) bool { var ( @@ -503,19 +515,20 @@ func PrepareCompareDiff( ) // Get diff information. - ctx.Data["CommitRepoLink"] = headRepo.Link() + ctx.Data["CommitRepoLink"] = ci.HeadRepo.Link() - headCommitID := compareInfo.HeadCommitID + headCommitID := ci.CompareInfo.HeadCommitID ctx.Data["AfterCommitID"] = headCommitID - if headCommitID == compareInfo.MergeBase { + if (headCommitID == ci.CompareInfo.MergeBase && !ci.DirectComparison) || + headCommitID == ci.CompareInfo.BaseCommitID { ctx.Data["IsNothingToCompare"] = true if unit, err := repo.GetUnit(models.UnitTypePullRequests); err == nil { config := unit.PullRequestsConfig() if !config.AutodetectManualMerge { - allowEmptyPr := !(baseBranch == headBranch && ctx.Repo.Repository.Name == headRepo.Name) + allowEmptyPr := !(ci.BaseBranch == ci.HeadBranch && ctx.Repo.Repository.Name == ci.HeadRepo.Name) ctx.Data["AllowEmptyPr"] = allowEmptyPr return !allowEmptyPr @@ -526,9 +539,14 @@ func PrepareCompareDiff( return true } - diff, err := gitdiff.GetDiffRangeWithWhitespaceBehavior(headGitRepo, - compareInfo.MergeBase, headCommitID, setting.Git.MaxGitDiffLines, - setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, whitespaceBehavior) + beforeCommitID := ci.CompareInfo.MergeBase + if ci.DirectComparison { + beforeCommitID = ci.CompareInfo.BaseCommitID + } + + diff, err := gitdiff.GetDiffRangeWithWhitespaceBehavior(ci.HeadGitRepo, + beforeCommitID, headCommitID, setting.Git.MaxGitDiffLines, + setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, whitespaceBehavior, ci.DirectComparison) if err != nil { ctx.ServerError("GetDiffRangeWithWhitespaceBehavior", err) return false @@ -536,14 +554,14 @@ func PrepareCompareDiff( ctx.Data["Diff"] = diff ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0 - headCommit, err := headGitRepo.GetCommit(headCommitID) + headCommit, err := ci.HeadGitRepo.GetCommit(headCommitID) if err != nil { ctx.ServerError("GetCommit", err) return false } baseGitRepo := ctx.Repo.GitRepo - baseCommitID := compareInfo.BaseCommitID + baseCommitID := ci.CompareInfo.BaseCommitID baseCommit, err := baseGitRepo.GetCommit(baseCommitID) if err != nil { @@ -551,7 +569,7 @@ func PrepareCompareDiff( return false } - commits := models.ConvertFromGitCommit(compareInfo.Commits, headRepo) + commits := models.ConvertFromGitCommit(ci.CompareInfo.Commits, ci.HeadRepo) ctx.Data["Commits"] = commits ctx.Data["CommitCount"] = len(commits) @@ -564,7 +582,7 @@ func PrepareCompareDiff( ctx.Data["content"] = strings.Join(body[1:], "\n") } } else { - title = headBranch + title = ci.HeadBranch } if len(title) > 255 { var trailer string @@ -579,10 +597,10 @@ func PrepareCompareDiff( } ctx.Data["title"] = title - ctx.Data["Username"] = headUser.Name - ctx.Data["Reponame"] = headRepo.Name + ctx.Data["Username"] = ci.HeadUser.Name + ctx.Data["Reponame"] = ci.HeadRepo.Name - headTarget := path.Join(headUser.Name, repo.Name) + headTarget := path.Join(ci.HeadUser.Name, repo.Name) setCompareContext(ctx, baseCommit, headCommit, headTarget) return false @@ -606,7 +624,7 @@ func getBranchesAndTagsForRepo(user *models.User, repo *models.Repository) (bool if err != nil { return false, nil, nil, err } - tags, err := gitRepo.GetTags() + tags, err := gitRepo.GetTags(0, 0) if err != nil { return false, nil, nil, err } @@ -615,38 +633,46 @@ func getBranchesAndTagsForRepo(user *models.User, repo *models.Repository) (bool // CompareDiff show different from one commit to another commit func CompareDiff(ctx *context.Context) { - headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := ParseCompareInfo(ctx) + ci := ParseCompareInfo(ctx) defer func() { - if headGitRepo != nil { - headGitRepo.Close() + if ci != nil && ci.HeadGitRepo != nil { + ci.HeadGitRepo.Close() } }() if ctx.Written() { return } - nothingToCompare := PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch, + ctx.Data["DirectComparison"] = ci.DirectComparison + ctx.Data["OtherCompareSeparator"] = ".." + ctx.Data["CompareSeparator"] = "..." + if ci.DirectComparison { + ctx.Data["CompareSeparator"] = ".." + ctx.Data["OtherCompareSeparator"] = "..." + } + + nothingToCompare := PrepareCompareDiff(ctx, ci, gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string))) if ctx.Written() { return } baseGitRepo := ctx.Repo.GitRepo - baseTags, err := baseGitRepo.GetTags() + baseTags, err := baseGitRepo.GetTags(0, 0) if err != nil { ctx.ServerError("GetTags", err) return } ctx.Data["Tags"] = baseTags - headBranches, _, err := headGitRepo.GetBranches(0, 0) + headBranches, _, err := ci.HeadGitRepo.GetBranches(0, 0) if err != nil { ctx.ServerError("GetBranches", err) return } ctx.Data["HeadBranches"] = headBranches - headTags, err := headGitRepo.GetTags() + headTags, err := ci.HeadGitRepo.GetTags(0, 0) if err != nil { ctx.ServerError("GetTags", err) return @@ -654,7 +680,7 @@ func CompareDiff(ctx *context.Context) { ctx.Data["HeadTags"] = headTags if ctx.Data["PageIsComparePull"] == true { - pr, err := models.GetUnmergedPullRequest(headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch, models.PullRequestFlowGithub) + pr, err := models.GetUnmergedPullRequest(ci.HeadRepo.ID, ctx.Repo.Repository.ID, ci.HeadBranch, ci.BaseBranch, models.PullRequestFlowGithub) if err != nil { if !models.IsErrPullRequestNotExist(err) { ctx.ServerError("GetUnmergedPullRequest", err) @@ -678,7 +704,11 @@ func CompareDiff(ctx *context.Context) { beforeCommitID := ctx.Data["BeforeCommitID"].(string) afterCommitID := ctx.Data["AfterCommitID"].(string) - ctx.Data["Title"] = "Comparing " + base.ShortSha(beforeCommitID) + "..." + base.ShortSha(afterCommitID) + separator := "..." + if ci.DirectComparison { + separator = ".." + } + ctx.Data["Title"] = "Comparing " + base.ShortSha(beforeCommitID) + separator + base.ShortSha(afterCommitID) ctx.Data["IsRepoToolbarCommits"] = true ctx.Data["IsDiffCompare"] = true diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index a99c1c7c61b49..1d18bfe9a908a 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -6,7 +6,7 @@ package repo import ( "fmt" - "io/ioutil" + "io" "net/http" "path" "strings" @@ -127,7 +127,7 @@ func editFile(ctx *context.Context, isNewFile bool) { return } - d, _ := ioutil.ReadAll(dataRc) + d, _ := io.ReadAll(dataRc) if err := dataRc.Close(); err != nil { log.Error("Error whilst closing blob data: %v", err) } diff --git a/routers/web/repo/editor_test.go b/routers/web/repo/editor_test.go index ec7aee1e77ff9..8ab1fe1ee8924 100644 --- a/routers/web/repo/editor_test.go +++ b/routers/web/repo/editor_test.go @@ -7,7 +7,7 @@ package repo import ( "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/test" @@ -15,7 +15,7 @@ import ( ) func TestCleanUploadName(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) var kases = map[string]string{ ".git/refs/master": "", @@ -41,7 +41,7 @@ func TestCleanUploadName(t *testing.T) { } func TestGetUniquePatchBranchName(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1") ctx.SetParams(":id", "1") test.LoadRepo(t, ctx, 1) @@ -56,7 +56,7 @@ func TestGetUniquePatchBranchName(t *testing.T) { } func TestGetClosestParentWithFiles(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1") ctx.SetParams(":id", "1") test.LoadRepo(t, ctx, 1) diff --git a/routers/web/repo/http.go b/routers/web/repo/http.go index 6078d764b6fa1..162338a9597c0 100644 --- a/routers/web/repo/http.go +++ b/routers/web/repo/http.go @@ -10,7 +10,6 @@ import ( "compress/gzip" gocontext "context" "fmt" - "io/ioutil" "net/http" "os" "os/exec" @@ -22,6 +21,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" @@ -175,12 +175,12 @@ func httpBase(ctx *context.Context) (h *serviceHandler) { } if ctx.IsBasicAuth && ctx.Data["IsApiToken"] != true { - _, err = models.GetTwoFactorByUID(ctx.User.ID) + _, err = login.GetTwoFactorByUID(ctx.User.ID) if err == nil { // TODO: This response should be changed to "invalid credentials" for security reasons once the expectation behind it (creating an app token to authenticate) is properly documented ctx.HandleText(http.StatusUnauthorized, "Users with two-factor authentication enabled cannot perform HTTP/HTTPS operations via plain username and password. Please create and use a personal access token on the user settings page") return - } else if !models.IsErrTwoFactorNotEnrolled(err) { + } else if !login.IsErrTwoFactorNotEnrolled(err) { ctx.ServerError("IsErrTwoFactorNotEnrolled", err) return } @@ -308,7 +308,7 @@ var ( func dummyInfoRefs(ctx *context.Context) { infoRefsOnce.Do(func() { - tmpDir, err := ioutil.TempDir(os.TempDir(), "gitea-info-refs-cache") + tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-info-refs-cache") if err != nil { log.Error("Failed to create temp dir for git-receive-pack cache: %v", err) return diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index bb3a0c8a9c79f..1aaa27c2b00e3 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -9,13 +9,14 @@ import ( "bytes" "errors" "fmt" - "io/ioutil" + "io" "net/http" "path" "strconv" "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" @@ -216,7 +217,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti issues = []*models.Issue{} } else { issues, err = models.Issues(&models.IssuesOptions{ - ListOptions: models.ListOptions{ + ListOptions: db.ListOptions{ Page: pager.Paginater.Current(), PageSize: setting.UI.IssuePagingNum, }, @@ -278,14 +279,14 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti return } - labels, err := models.GetLabelsByRepoID(repo.ID, "", models.ListOptions{}) + labels, err := models.GetLabelsByRepoID(repo.ID, "", db.ListOptions{}) if err != nil { ctx.ServerError("GetLabelsByRepoID", err) return } if repo.Owner.IsOrganization() { - orgLabels, err := models.GetLabelsByOrgID(repo.Owner.ID, ctx.FormString("sort"), models.ListOptions{}) + orgLabels, err := models.GetLabelsByOrgID(repo.Owner.ID, ctx.FormString("sort"), db.ListOptions{}) if err != nil { ctx.ServerError("GetLabelsByOrgID", err) return @@ -326,6 +327,20 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti } return 0 } + + if ctx.Repo.CanWriteIssuesOrPulls(ctx.Params(":type") == "pulls") { + projects, _, err := models.GetProjects(models.ProjectSearchOptions{ + RepoID: repo.ID, + Type: models.ProjectTypeRepository, + IsClosed: util.OptionalBoolOf(isShowClosed), + }) + if err != nil { + ctx.ServerError("GetProjects", err) + return + } + ctx.Data["Projects"] = projects + } + ctx.Data["IssueStats"] = issueStats ctx.Data["SelLabelIDs"] = labelIDs ctx.Data["SelectLabels"] = selectLabels @@ -645,14 +660,14 @@ func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository, isPull boo return nil } - labels, err := models.GetLabelsByRepoID(repo.ID, "", models.ListOptions{}) + labels, err := models.GetLabelsByRepoID(repo.ID, "", db.ListOptions{}) if err != nil { ctx.ServerError("GetLabelsByRepoID", err) return nil } ctx.Data["Labels"] = labels if repo.Owner.IsOrganization() { - orgLabels, err := models.GetLabelsByOrgID(repo.Owner.ID, ctx.FormString("sort"), models.ListOptions{}) + orgLabels, err := models.GetLabelsByOrgID(repo.Owner.ID, ctx.FormString("sort"), db.ListOptions{}) if err != nil { return nil } @@ -707,7 +722,7 @@ func getFileContentFromDefaultBranch(ctx *context.Context, filename string) (str return "", false } defer r.Close() - bytes, err = ioutil.ReadAll(r) + bytes, err = io.ReadAll(r) if err != nil { return "", false } @@ -735,10 +750,10 @@ func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleDirs [ ctx.Data[issueTemplateTitleKey] = meta.Title ctx.Data[ctxDataKey] = templateBody labelIDs := make([]string, 0, len(meta.Labels)) - if repoLabels, err := models.GetLabelsByRepoID(ctx.Repo.Repository.ID, "", models.ListOptions{}); err == nil { + if repoLabels, err := models.GetLabelsByRepoID(ctx.Repo.Repository.ID, "", db.ListOptions{}); err == nil { ctx.Data["Labels"] = repoLabels if ctx.Repo.Owner.IsOrganization() { - if orgLabels, err := models.GetLabelsByOrgID(ctx.Repo.Owner.ID, ctx.FormString("sort"), models.ListOptions{}); err == nil { + if orgLabels, err := models.GetLabelsByOrgID(ctx.Repo.Owner.ID, ctx.FormString("sort"), db.ListOptions{}); err == nil { ctx.Data["OrgLabels"] = orgLabels repoLabels = append(repoLabels, orgLabels...) } @@ -802,6 +817,9 @@ func NewIssue(ctx *context.Context) { ctx.Data["Project"] = project } + if len(ctx.Req.URL.Query().Get("project")) > 0 { + ctx.Data["redirect_after_creation"] = "project" + } } RetrieveRepoMetas(ctx, ctx.Repo.Repository, false) @@ -989,7 +1007,11 @@ func NewIssuePost(ctx *context.Context) { } log.Trace("Issue created: %d/%d", repo.ID, issue.ID) - ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + fmt.Sprint(issue.Index)) + if ctx.FormString("redirect_after_creation") == "project" { + ctx.Redirect(ctx.Repo.RepoLink + "/projects/" + fmt.Sprint(form.ProjectID)) + } else { + ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + fmt.Sprint(issue.Index)) + } } // commentTag returns the CommentTag for a comment in/with the given repo, poster and issue @@ -1164,7 +1186,7 @@ func ViewIssue(ctx *context.Context) { for i := range issue.Labels { labelIDMark[issue.Labels[i].ID] = true } - labels, err := models.GetLabelsByRepoID(repo.ID, "", models.ListOptions{}) + labels, err := models.GetLabelsByRepoID(repo.ID, "", db.ListOptions{}) if err != nil { ctx.ServerError("GetLabelsByRepoID", err) return @@ -1172,7 +1194,7 @@ func ViewIssue(ctx *context.Context) { ctx.Data["Labels"] = labels if repo.Owner.IsOrganization() { - orgLabels, err := models.GetLabelsByOrgID(repo.Owner.ID, ctx.FormString("sort"), models.ListOptions{}) + orgLabels, err := models.GetLabelsByOrgID(repo.Owner.ID, ctx.FormString("sort"), db.ListOptions{}) if err != nil { ctx.ServerError("GetLabelsByOrgID", err) return @@ -2606,5 +2628,5 @@ func handleTeamMentions(ctx *context.Context) { ctx.Data["MentionableTeams"] = ctx.Repo.Owner.Teams ctx.Data["MentionableTeamsOrg"] = ctx.Repo.Owner.Name - ctx.Data["MentionableTeamsOrgAvatar"] = ctx.Repo.Owner.RelAvatarLink() + ctx.Data["MentionableTeamsOrgAvatar"] = ctx.Repo.Owner.AvatarLink() } diff --git a/routers/web/repo/issue_label.go b/routers/web/repo/issue_label.go index abb529649a5b9..b97f57175b4cd 100644 --- a/routers/web/repo/issue_label.go +++ b/routers/web/repo/issue_label.go @@ -8,6 +8,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" @@ -38,7 +39,7 @@ func InitializeLabels(ctx *context.Context) { return } - if err := models.InitializeLabels(models.DefaultDBContext(), ctx.Repo.Repository.ID, form.TemplateName, false); err != nil { + if err := models.InitializeLabels(db.DefaultContext, ctx.Repo.Repository.ID, form.TemplateName, false); err != nil { if models.IsErrIssueLabelTemplateLoad(err) { originalErr := err.(models.ErrIssueLabelTemplateLoad).OriginalError ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr)) @@ -53,7 +54,7 @@ func InitializeLabels(ctx *context.Context) { // RetrieveLabels find all the labels of a repository and organization func RetrieveLabels(ctx *context.Context) { - labels, err := models.GetLabelsByRepoID(ctx.Repo.Repository.ID, ctx.FormString("sort"), models.ListOptions{}) + labels, err := models.GetLabelsByRepoID(ctx.Repo.Repository.ID, ctx.FormString("sort"), db.ListOptions{}) if err != nil { ctx.ServerError("RetrieveLabels.GetLabels", err) return @@ -66,7 +67,7 @@ func RetrieveLabels(ctx *context.Context) { ctx.Data["Labels"] = labels if ctx.Repo.Owner.IsOrganization() { - orgLabels, err := models.GetLabelsByOrgID(ctx.Repo.Owner.ID, ctx.FormString("sort"), models.ListOptions{}) + orgLabels, err := models.GetLabelsByOrgID(ctx.Repo.Owner.ID, ctx.FormString("sort"), db.ListOptions{}) if err != nil { ctx.ServerError("GetLabelsByOrgID", err) return diff --git a/routers/web/repo/issue_label_test.go b/routers/web/repo/issue_label_test.go index bf9e72a6f453a..8c3caabe17cf8 100644 --- a/routers/web/repo/issue_label_test.go +++ b/routers/web/repo/issue_label_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/forms" @@ -29,14 +30,14 @@ func int64SliceToCommaSeparated(a []int64) string { } func TestInitializeLabels(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/labels/initialize") test.LoadUser(t, ctx, 2) test.LoadRepo(t, ctx, 2) web.SetForm(ctx, &forms.InitializeLabelsForm{TemplateName: "Default"}) InitializeLabels(ctx) assert.EqualValues(t, http.StatusFound, ctx.Resp.Status()) - models.AssertExistsAndLoadBean(t, &models.Label{ + db.AssertExistsAndLoadBean(t, &models.Label{ RepoID: 2, Name: "enhancement", Color: "#84b6eb", @@ -45,7 +46,7 @@ func TestInitializeLabels(t *testing.T) { } func TestRetrieveLabels(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) for _, testCase := range []struct { RepoID int64 Sort string @@ -72,7 +73,7 @@ func TestRetrieveLabels(t *testing.T) { } func TestNewLabel(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/labels/edit") test.LoadUser(t, ctx, 2) test.LoadRepo(t, ctx, 1) @@ -82,7 +83,7 @@ func TestNewLabel(t *testing.T) { }) NewLabel(ctx) assert.EqualValues(t, http.StatusFound, ctx.Resp.Status()) - models.AssertExistsAndLoadBean(t, &models.Label{ + db.AssertExistsAndLoadBean(t, &models.Label{ Name: "newlabel", Color: "#abcdef", }) @@ -90,7 +91,7 @@ func TestNewLabel(t *testing.T) { } func TestUpdateLabel(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/labels/edit") test.LoadUser(t, ctx, 2) test.LoadRepo(t, ctx, 1) @@ -101,7 +102,7 @@ func TestUpdateLabel(t *testing.T) { }) UpdateLabel(ctx) assert.EqualValues(t, http.StatusFound, ctx.Resp.Status()) - models.AssertExistsAndLoadBean(t, &models.Label{ + db.AssertExistsAndLoadBean(t, &models.Label{ ID: 2, Name: "newnameforlabel", Color: "#abcdef", @@ -110,20 +111,20 @@ func TestUpdateLabel(t *testing.T) { } func TestDeleteLabel(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/labels/delete") test.LoadUser(t, ctx, 2) test.LoadRepo(t, ctx, 1) ctx.Req.Form.Set("id", "2") DeleteLabel(ctx) assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) - models.AssertNotExistsBean(t, &models.Label{ID: 2}) - models.AssertNotExistsBean(t, &models.IssueLabel{LabelID: 2}) + db.AssertNotExistsBean(t, &models.Label{ID: 2}) + db.AssertNotExistsBean(t, &models.IssueLabel{LabelID: 2}) assert.Equal(t, ctx.Tr("repo.issues.label_deletion_success"), ctx.Flash.SuccessMsg) } func TestUpdateIssueLabel_Clear(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/issues/labels") test.LoadUser(t, ctx, 2) test.LoadRepo(t, ctx, 1) @@ -131,8 +132,8 @@ func TestUpdateIssueLabel_Clear(t *testing.T) { ctx.Req.Form.Set("action", "clear") UpdateIssueLabel(ctx) assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) - models.AssertNotExistsBean(t, &models.IssueLabel{IssueID: 1}) - models.AssertNotExistsBean(t, &models.IssueLabel{IssueID: 3}) + db.AssertNotExistsBean(t, &models.IssueLabel{IssueID: 1}) + db.AssertNotExistsBean(t, &models.IssueLabel{IssueID: 3}) models.CheckConsistencyFor(t, &models.Label{}) } @@ -148,7 +149,7 @@ func TestUpdateIssueLabel_Toggle(t *testing.T) { {"toggle", []int64{1, 3}, 1, false}, {"toggle", []int64{1, 2}, 2, true}, } { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/issues/labels") test.LoadUser(t, ctx, 2) test.LoadRepo(t, ctx, 1) @@ -158,7 +159,7 @@ func TestUpdateIssueLabel_Toggle(t *testing.T) { UpdateIssueLabel(ctx) assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) for _, issueID := range testCase.IssueIDs { - models.AssertExistsIf(t, testCase.ExpectedAdd, &models.IssueLabel{ + db.AssertExistsIf(t, testCase.ExpectedAdd, &models.IssueLabel{ IssueID: issueID, LabelID: testCase.LabelID, }) diff --git a/routers/web/repo/lfs.go b/routers/web/repo/lfs.go index e524a9209a48a..271c638553717 100644 --- a/routers/web/repo/lfs.go +++ b/routers/web/repo/lfs.go @@ -9,7 +9,6 @@ import ( "fmt" gotemplate "html/template" "io" - "io/ioutil" "net/http" "path" "strconv" @@ -300,7 +299,7 @@ func LFSFileGet(ctx *context.Context) { buf := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc)) // Building code view blocks with line number on server side. - fileContent, _ := ioutil.ReadAll(buf) + fileContent, _ := io.ReadAll(buf) var output bytes.Buffer lines := strings.Split(string(fileContent), "\n") diff --git a/routers/web/repo/main_test.go b/routers/web/repo/main_test.go index 47f266365fd7f..832256724987c 100644 --- a/routers/web/repo/main_test.go +++ b/routers/web/repo/main_test.go @@ -8,9 +8,9 @@ import ( "path/filepath" "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..", "..")) + db.MainTest(m, filepath.Join("..", "..", "..")) } diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go index 80f1eb52318a0..21e1fb2eab8d9 100644 --- a/routers/web/repo/milestone.go +++ b/routers/web/repo/milestone.go @@ -9,6 +9,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/markup" @@ -59,7 +60,7 @@ func Milestones(ctx *context.Context) { } miles, total, err := models.GetMilestones(models.GetMilestonesOption{ - ListOptions: models.ListOptions{ + ListOptions: db.ListOptions{ Page: page, PageSize: setting.UI.IssuePagingNum, }, diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go index 556656e5b506f..2490efc92319a 100644 --- a/routers/web/repo/projects.go +++ b/routers/web/repo/projects.go @@ -444,6 +444,7 @@ func AddBoardToProjectPost(ctx *context.Context) { if err := models.NewProjectBoard(&models.ProjectBoard{ ProjectID: project.ID, Title: form.Title, + Color: form.Color, CreatorID: ctx.User.ID, }); err != nil { ctx.ServerError("NewProjectBoard", err) @@ -513,6 +514,8 @@ func EditProjectBoard(ctx *context.Context) { board.Title = form.Title } + board.Color = form.Color + if form.Sorting != 0 { board.Sorting = form.Sorting } diff --git a/routers/web/repo/projects_test.go b/routers/web/repo/projects_test.go index c43cf6d952837..d3b78cc77587d 100644 --- a/routers/web/repo/projects_test.go +++ b/routers/web/repo/projects_test.go @@ -7,14 +7,14 @@ package repo import ( "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/test" "github.com/stretchr/testify/assert" ) func TestCheckProjectBoardChangePermissions(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/projects/1/2") test.LoadUser(t, ctx, 2) test.LoadRepo(t, ctx, 1) diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 6b369195de356..dde556135177d 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -16,6 +16,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" @@ -317,7 +318,7 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.C ctx.Data["HasMerged"] = true compareInfo, err := ctx.Repo.GitRepo.GetCompareInfo(ctx.Repo.Repository.RepoPath(), - pull.MergeBase, pull.GetGitRefName()) + pull.MergeBase, pull.GetGitRefName(), true) if err != nil { if strings.Contains(err.Error(), "fatal: Not a valid object name") || strings.Contains(err.Error(), "unknown revision or path not in the working tree") { ctx.Data["IsPullRequestBroken"] = true @@ -335,7 +336,7 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.C if len(compareInfo.Commits) != 0 { sha := compareInfo.Commits[0].ID.String() - commitStatuses, err := models.GetLatestCommitStatus(ctx.Repo.Repository.ID, sha, models.ListOptions{}) + commitStatuses, err := models.GetLatestCommitStatus(ctx.Repo.Repository.ID, sha, db.ListOptions{}) if err != nil { ctx.ServerError("GetLatestCommitStatus", err) return nil @@ -389,7 +390,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err) return nil } - commitStatuses, err := models.GetLatestCommitStatus(repo.ID, sha, models.ListOptions{}) + commitStatuses, err := models.GetLatestCommitStatus(repo.ID, sha, db.ListOptions{}) if err != nil { ctx.ServerError("GetLatestCommitStatus", err) return nil @@ -400,7 +401,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare } compareInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(), - pull.MergeBase, pull.GetGitRefName()) + pull.MergeBase, pull.GetGitRefName(), true) if err != nil { if strings.Contains(err.Error(), "fatal: Not a valid object name") { ctx.Data["IsPullRequestBroken"] = true @@ -478,7 +479,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare return nil } - commitStatuses, err := models.GetLatestCommitStatus(repo.ID, sha, models.ListOptions{}) + commitStatuses, err := models.GetLatestCommitStatus(repo.ID, sha, db.ListOptions{}) if err != nil { ctx.ServerError("GetLatestCommitStatus", err) return nil @@ -516,7 +517,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare } compareInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(), - git.BranchPrefix+pull.BaseBranch, pull.GetGitRefName()) + git.BranchPrefix+pull.BaseBranch, pull.GetGitRefName(), true) if err != nil { if strings.Contains(err.Error(), "fatal: Not a valid object name") { ctx.Data["IsPullRequestBroken"] = true @@ -634,7 +635,7 @@ func ViewPullFiles(ctx *context.Context) { diff, err := gitdiff.GetDiffRangeWithWhitespaceBehavior(gitRepo, startCommitID, endCommitID, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, - gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string))) + gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)), false) if err != nil { ctx.ServerError("GetDiffRangeWithWhitespaceBehavior", err) return @@ -1040,10 +1041,10 @@ func CompareAndPullRequestPost(ctx *context.Context) { attachments []string ) - headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := ParseCompareInfo(ctx) + ci := ParseCompareInfo(ctx) defer func() { - if headGitRepo != nil { - headGitRepo.Close() + if ci != nil && ci.HeadGitRepo != nil { + ci.HeadGitRepo.Close() } }() if ctx.Written() { @@ -1064,7 +1065,7 @@ func CompareAndPullRequestPost(ctx *context.Context) { // This stage is already stop creating new pull request, so it does not matter if it has // something to compare or not. - PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch, + PrepareCompareDiff(ctx, ci, gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string))) if ctx.Written() { return @@ -1083,7 +1084,7 @@ func CompareAndPullRequestPost(ctx *context.Context) { } if util.IsEmptyString(form.Title) { - PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch, + PrepareCompareDiff(ctx, ci, gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string))) if ctx.Written() { return @@ -1103,13 +1104,13 @@ func CompareAndPullRequestPost(ctx *context.Context) { Content: form.Content, } pullRequest := &models.PullRequest{ - HeadRepoID: headRepo.ID, + HeadRepoID: ci.HeadRepo.ID, BaseRepoID: repo.ID, - HeadBranch: headBranch, - BaseBranch: baseBranch, - HeadRepo: headRepo, + HeadBranch: ci.HeadBranch, + BaseBranch: ci.BaseBranch, + HeadRepo: ci.HeadRepo, BaseRepo: repo, - MergeBase: prInfo.MergeBase, + MergeBase: ci.CompareInfo.MergeBase, Type: models.PullRequestGitea, } // FIXME: check error in the case two people send pull request at almost same time, give nice error prompt @@ -1321,31 +1322,19 @@ func DownloadPullPatch(ctx *context.Context) { // DownloadPullDiffOrPatch render a pull's raw diff or patch func DownloadPullDiffOrPatch(ctx *context.Context, patch bool) { - issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) + pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { - if models.IsErrIssueNotExist(err) { - ctx.NotFound("GetIssueByIndex", err) + if models.IsErrPullRequestNotExist(err) { + ctx.NotFound("GetPullRequestByIndex", err) } else { - ctx.ServerError("GetIssueByIndex", err) + ctx.ServerError("GetPullRequestByIndex", err) } return } - // Return not found if it's not a pull request - if !issue.IsPull { - ctx.NotFound("DownloadPullDiff", - fmt.Errorf("Issue is not a pull request")) - return - } - - if err = issue.LoadPullRequest(); err != nil { - ctx.ServerError("LoadPullRequest", err) - return - } - - pr := issue.PullRequest + binary := ctx.FormBool("binary") - if err := pull_service.DownloadDiffOrPatch(pr, ctx, patch); err != nil { + if err := pull_service.DownloadDiffOrPatch(pr, ctx, patch, binary); err != nil { ctx.ServerError("DownloadDiffOrPatch", err) return } diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index ef98790f52d31..df1fd745d8677 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -11,6 +11,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" @@ -83,7 +84,18 @@ func releasesOrTags(ctx *context.Context, isTagList bool) { ctx.Data["PageIsTagList"] = false } - tags, err := ctx.Repo.GitRepo.GetTags() + listOptions := db.ListOptions{ + Page: ctx.FormInt("page"), + PageSize: ctx.FormInt("limit"), + } + if listOptions.PageSize == 0 { + listOptions.PageSize = setting.Repository.Release.DefaultPagingNum + } + if listOptions.PageSize > setting.API.MaxResponseItems { + listOptions.PageSize = setting.API.MaxResponseItems + } + + tags, err := ctx.Repo.GitRepo.GetTags(listOptions.GetStartEnd()) if err != nil { ctx.ServerError("GetTags", err) return @@ -92,19 +104,9 @@ func releasesOrTags(ctx *context.Context, isTagList bool) { writeAccess := ctx.Repo.CanWrite(models.UnitTypeReleases) ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived - limit := ctx.FormInt("limit") - if limit == 0 { - limit = setting.Repository.Release.DefaultPagingNum - } - if limit > setting.API.MaxResponseItems { - limit = setting.API.MaxResponseItems - } opts := models.FindReleasesOptions{ - ListOptions: models.ListOptions{ - Page: ctx.FormInt("page"), - PageSize: limit, - }, + ListOptions: listOptions, IncludeDrafts: writeAccess && !isTagList, IncludeTags: isTagList, } diff --git a/routers/web/repo/release_test.go b/routers/web/repo/release_test.go index 004a6ef540d1e..7ac49c012fef6 100644 --- a/routers/web/repo/release_test.go +++ b/routers/web/repo/release_test.go @@ -8,6 +8,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/forms" @@ -43,7 +44,7 @@ func TestNewReleasePost(t *testing.T) { }, }, } { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/releases/new") test.LoadUser(t, ctx, 2) @@ -51,14 +52,14 @@ func TestNewReleasePost(t *testing.T) { test.LoadGitRepo(t, ctx) web.SetForm(ctx, &testCase.Form) NewReleasePost(ctx) - models.AssertExistsAndLoadBean(t, &models.Release{ + db.AssertExistsAndLoadBean(t, &models.Release{ RepoID: 1, PublisherID: 2, TagName: testCase.Form.TagName, Target: testCase.Form.Target, Title: testCase.Form.Title, Note: testCase.Form.Content, - }, models.Cond("is_draft=?", len(testCase.Form.Draft) > 0)) + }, db.Cond("is_draft=?", len(testCase.Form.Draft) > 0)) ctx.Repo.GitRepo.Close() } } diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 98f60c6b59122..735bf4fe9f067 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -13,6 +13,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/graceful" @@ -342,7 +343,7 @@ func RedirectDownload(ctx *context.Context) { ) tagNames := []string{vTag} curRepo := ctx.Repo.Repository - releases, err := models.GetReleasesByRepoIDAndNames(models.DefaultDBContext(), curRepo.ID, tagNames) + releases, err := models.GetReleasesByRepoIDAndNames(db.DefaultContext, curRepo.ID, tagNames) if err != nil { if models.IsErrAttachmentNotExist(err) { ctx.Error(http.StatusNotFound) @@ -379,7 +380,7 @@ func Download(ctx *context.Context) { return } - archiver, err := models.GetRepoArchiver(models.DefaultDBContext(), aReq.RepoID, aReq.Type, aReq.CommitID) + archiver, err := models.GetRepoArchiver(db.DefaultContext, aReq.RepoID, aReq.Type, aReq.CommitID) if err != nil { ctx.ServerError("models.GetRepoArchiver", err) return @@ -409,7 +410,7 @@ func Download(ctx *context.Context) { return } times++ - archiver, err = models.GetRepoArchiver(models.DefaultDBContext(), aReq.RepoID, aReq.Type, aReq.CommitID) + archiver, err = models.GetRepoArchiver(db.DefaultContext, aReq.RepoID, aReq.Type, aReq.CommitID) if err != nil { ctx.ServerError("archiver_service.StartArchive", err) return @@ -465,7 +466,7 @@ func InitiateDownload(ctx *context.Context) { return } - archiver, err := models.GetRepoArchiver(models.DefaultDBContext(), aReq.RepoID, aReq.Type, aReq.CommitID) + archiver, err := models.GetRepoArchiver(db.DefaultContext, aReq.RepoID, aReq.Type, aReq.CommitID) if err != nil { ctx.ServerError("archiver_service.StartArchive", err) return diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting.go index 72bacebd2776d..e71a5bf482bbf 100644 --- a/routers/web/repo/setting.go +++ b/routers/web/repo/setting.go @@ -8,13 +8,14 @@ package repo import ( "errors" "fmt" - "io/ioutil" + "io" "net/http" "strconv" "strings" "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" @@ -555,10 +556,8 @@ func SettingsPost(ctx *context.Context) { return } - repo.IsFork = false - repo.ForkID = 0 - if err := models.UpdateRepository(repo, false); err != nil { - log.Error("Unable to update repository %-v whilst converting from fork", repo) + if err := repository.ConvertForkToNormalRepository(repo); err != nil { + log.Error("Unable to convert repository %-v from fork. Error: %v", repo, err) ctx.ServerError("Convert Fork", err) return } @@ -770,7 +769,7 @@ func Collaboration(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.settings") ctx.Data["PageIsSettingsCollaboration"] = true - users, err := ctx.Repo.Repository.GetCollaborators(models.ListOptions{}) + users, err := ctx.Repo.Repository.GetCollaborators(db.ListOptions{}) if err != nil { ctx.ServerError("GetCollaborators", err) return @@ -1128,9 +1127,9 @@ func UpdateAvatarSetting(ctx *context.Context, form forms.AvatarForm) error { return errors.New(ctx.Tr("settings.uploaded_avatar_is_too_big")) } - data, err := ioutil.ReadAll(r) + data, err := io.ReadAll(r) if err != nil { - return fmt.Errorf("ioutil.ReadAll: %v", err) + return fmt.Errorf("io.ReadAll: %v", err) } st := typesniffer.DetectContentType(data) if !(st.IsImage() && !st.IsSvgImage()) { diff --git a/routers/web/repo/setting_protected_branch.go b/routers/web/repo/setting_protected_branch.go index 30c7d81b8e782..c48ab9471a21c 100644 --- a/routers/web/repo/setting_protected_branch.go +++ b/routers/web/repo/setting_protected_branch.go @@ -253,6 +253,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) { protectBranch.DismissStaleApprovals = f.DismissStaleApprovals protectBranch.RequireSignedCommits = f.RequireSignedCommits protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns + protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns protectBranch.BlockOnOutdatedBranch = f.BlockOnOutdatedBranch err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{ diff --git a/routers/web/repo/settings_test.go b/routers/web/repo/settings_test.go index 5190f12d5d2cf..a3ed271cce6cb 100644 --- a/routers/web/repo/settings_test.go +++ b/routers/web/repo/settings_test.go @@ -5,11 +5,12 @@ package repo import ( - "io/ioutil" "net/http" + "os" "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" @@ -21,7 +22,7 @@ import ( ) func createSSHAuthorizedKeysTmpPath(t *testing.T) func() { - tmpDir, err := ioutil.TempDir("", "tmp-ssh") + tmpDir, err := os.MkdirTemp("", "tmp-ssh") if err != nil { assert.Fail(t, "Unable to create temporary directory: %v", err) return nil @@ -42,7 +43,7 @@ func TestAddReadOnlyDeployKey(t *testing.T) { } else { return } - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/settings/keys") @@ -57,7 +58,7 @@ func TestAddReadOnlyDeployKey(t *testing.T) { DeployKeysPost(ctx) assert.EqualValues(t, http.StatusFound, ctx.Resp.Status()) - models.AssertExistsAndLoadBean(t, &models.DeployKey{ + db.AssertExistsAndLoadBean(t, &models.DeployKey{ Name: addKeyForm.Title, Content: addKeyForm.Content, Mode: models.AccessModeRead, @@ -71,7 +72,7 @@ func TestAddReadWriteOnlyDeployKey(t *testing.T) { return } - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/settings/keys") @@ -87,7 +88,7 @@ func TestAddReadWriteOnlyDeployKey(t *testing.T) { DeployKeysPost(ctx) assert.EqualValues(t, http.StatusFound, ctx.Resp.Status()) - models.AssertExistsAndLoadBean(t, &models.DeployKey{ + db.AssertExistsAndLoadBean(t, &models.DeployKey{ Name: addKeyForm.Title, Content: addKeyForm.Content, Mode: models.AccessModeWrite, @@ -96,7 +97,7 @@ func TestAddReadWriteOnlyDeployKey(t *testing.T) { func TestCollaborationPost(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/issues/labels") test.LoadUser(t, ctx, 2) test.LoadUser(t, ctx, 4) @@ -132,7 +133,7 @@ func TestCollaborationPost(t *testing.T) { func TestCollaborationPost_InactiveUser(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/issues/labels") test.LoadUser(t, ctx, 2) test.LoadUser(t, ctx, 9) @@ -156,7 +157,7 @@ func TestCollaborationPost_InactiveUser(t *testing.T) { func TestCollaborationPost_AddCollaboratorTwice(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/issues/labels") test.LoadUser(t, ctx, 2) test.LoadUser(t, ctx, 4) @@ -198,7 +199,7 @@ func TestCollaborationPost_AddCollaboratorTwice(t *testing.T) { func TestCollaborationPost_NonExistentUser(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/issues/labels") test.LoadUser(t, ctx, 2) test.LoadRepo(t, ctx, 1) @@ -220,7 +221,7 @@ func TestCollaborationPost_NonExistentUser(t *testing.T) { } func TestAddTeamPost(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "org26/repo43") ctx.Req.Form.Set("team", "team11") @@ -260,7 +261,7 @@ func TestAddTeamPost(t *testing.T) { } func TestAddTeamPost_NotAllowed(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "org26/repo43") ctx.Req.Form.Set("team", "team11") @@ -301,7 +302,7 @@ func TestAddTeamPost_NotAllowed(t *testing.T) { } func TestAddTeamPost_AddTeamTwice(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "org26/repo43") ctx.Req.Form.Set("team", "team11") @@ -342,7 +343,7 @@ func TestAddTeamPost_AddTeamTwice(t *testing.T) { } func TestAddTeamPost_NonExistentTeam(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "org26/repo43") ctx.Req.Form.Set("team", "team-non-existent") @@ -375,7 +376,7 @@ func TestAddTeamPost_NonExistentTeam(t *testing.T) { } func TestDeleteTeam(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "org3/team1/repo3") ctx.Req.Form.Set("id", "2") diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 2c703fc1aff40..0777a10e7b9a6 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -7,18 +7,20 @@ package repo import ( "bytes" + gocontext "context" "encoding/base64" "fmt" gotemplate "html/template" "io" - "io/ioutil" "net/http" "net/url" "path" "strconv" "strings" + "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/charset" @@ -34,11 +36,12 @@ import ( ) const ( - tplRepoEMPTY base.TplName = "repo/empty" - tplRepoHome base.TplName = "repo/home" - tplWatchers base.TplName = "repo/watchers" - tplForks base.TplName = "repo/forks" - tplMigrating base.TplName = "repo/migrate/migrating" + tplRepoEMPTY base.TplName = "repo/empty" + tplRepoHome base.TplName = "repo/home" + tplRepoViewList base.TplName = "repo/view_list" + tplWatchers base.TplName = "repo/watchers" + tplForks base.TplName = "repo/forks" + tplMigrating base.TplName = "repo/migrate/migrating" ) type namedBlob struct { @@ -128,28 +131,8 @@ func getReadmeFileFromPath(commit *git.Commit, treePath string) (*namedBlob, err } func renderDirectory(ctx *context.Context, treeLink string) { - tree, err := ctx.Repo.Commit.SubTree(ctx.Repo.TreePath) - if err != nil { - ctx.NotFoundOrServerError("Repo.Commit.SubTree", git.IsErrNotExist, err) - return - } - - entries, err := tree.ListEntries() - if err != nil { - ctx.ServerError("ListEntries", err) - return - } - entries.CustomSort(base.NaturalSortLess) - - var c *git.LastCommitCache - if setting.CacheService.LastCommit.Enabled && ctx.Repo.CommitsCount >= setting.CacheService.LastCommit.CommitsCount { - c = git.NewLastCommitCache(ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, setting.LastCommitCacheTTLSeconds, cache.GetCache()) - } - - var latestCommit *git.Commit - ctx.Data["Files"], latestCommit, err = entries.GetCommitsInfo(ctx, ctx.Repo.Commit, ctx.Repo.TreePath, c) - if err != nil { - ctx.ServerError("GetCommitsInfo", err) + entries := renderDirectoryFiles(ctx, 1*time.Second) + if ctx.Written() { return } @@ -186,6 +169,7 @@ func renderDirectory(ctx *context.Context, treeLink string) { isSymlink := entry.IsLink() target := entry if isSymlink { + var err error target, err = entry.FollowLinks() if err != nil && !git.IsErrBadLink(err) { ctx.ServerError("FollowLinks", err) @@ -207,6 +191,7 @@ func renderDirectory(ctx *context.Context, treeLink string) { name := entry.Name() isSymlink := entry.IsLink() if isSymlink { + var err error entry, err = entry.FollowLinks() if err != nil && !git.IsErrBadLink(err) { ctx.ServerError("FollowLinks", err) @@ -237,6 +222,7 @@ func renderDirectory(ctx *context.Context, treeLink string) { if entry == nil { continue } + var err error readmeFile, err = getReadmeFileFromPath(ctx.Repo.Commit, entry.GetSubJumpablePathName()) if err != nil { ctx.ServerError("getReadmeFileFromPath", err) @@ -344,7 +330,7 @@ func renderDirectory(ctx *context.Context, treeLink string) { }, rd, &result) if err != nil { log.Error("Render failed: %v then fallback", err) - bs, _ := ioutil.ReadAll(rd) + bs, _ := io.ReadAll(rd) ctx.Data["FileContent"] = strings.ReplaceAll( gotemplate.HTMLEscapeString(string(bs)), "\n", `
`, ) @@ -353,6 +339,10 @@ func renderDirectory(ctx *context.Context, treeLink string) { } } else { ctx.Data["IsRenderedHTML"] = true + buf, err = io.ReadAll(rd) + if err != nil { + log.Error("ReadAll failed: %v", err) + } ctx.Data["FileContent"] = strings.ReplaceAll( gotemplate.HTMLEscapeString(string(buf)), "\n", `
`, ) @@ -361,34 +351,12 @@ func renderDirectory(ctx *context.Context, treeLink string) { } } - // Show latest commit info of repository in table header, - // or of directory if not in root directory. - ctx.Data["LatestCommit"] = latestCommit - verification := models.ParseCommitWithSignature(latestCommit) - - if err := models.CalculateTrustStatus(verification, ctx.Repo.Repository, nil); err != nil { - ctx.ServerError("CalculateTrustStatus", err) - return - } - ctx.Data["LatestCommitVerification"] = verification - - ctx.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit) - - statuses, err := models.GetLatestCommitStatus(ctx.Repo.Repository.ID, ctx.Repo.Commit.ID.String(), models.ListOptions{}) - if err != nil { - log.Error("GetLatestCommitStatus: %v", err) - } - - ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(statuses) - ctx.Data["LatestCommitStatuses"] = statuses - // Check permission to add or upload new file. if ctx.Repo.CanWrite(models.UnitTypeCode) && ctx.Repo.IsViewBranch { ctx.Data["CanAddFile"] = !ctx.Repo.Repository.IsArchived ctx.Data["CanUploadFile"] = setting.Repository.Upload.Enabled && !ctx.Repo.Repository.IsArchived } - ctx.Data["SSHDomain"] = setting.SSH.Domain } func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink string) { @@ -524,13 +492,13 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st } ctx.Data["FileContent"] = result.String() } else if readmeExist { - buf, _ := ioutil.ReadAll(rd) + buf, _ := io.ReadAll(rd) ctx.Data["IsRenderedHTML"] = true ctx.Data["FileContent"] = strings.ReplaceAll( gotemplate.HTMLEscapeString(string(buf)), "\n", `
`, ) } else { - buf, _ := ioutil.ReadAll(rd) + buf, _ := io.ReadAll(rd) lineNums := linesBytesCount(buf) ctx.Data["NumLines"] = strconv.Itoa(lineNums) ctx.Data["NumLinesSet"] = true @@ -610,8 +578,7 @@ func safeURL(address string) string { return u.String() } -// Home render repository home page -func Home(ctx *context.Context) { +func checkHomeCodeViewable(ctx *context.Context) { if len(ctx.Repo.Units) > 0 { if ctx.Repo.Repository.IsBeingCreated() { task, err := models.GetMigratingTask(ctx.Repo.Repository.ID) @@ -644,7 +611,6 @@ func Home(ctx *context.Context) { var firstUnit *models.Unit for _, repoUnit := range ctx.Repo.Units { if repoUnit.Type == models.UnitTypeCode { - renderCode(ctx) return } @@ -663,6 +629,145 @@ func Home(ctx *context.Context) { ctx.NotFound("Home", fmt.Errorf(ctx.Tr("units.error.no_unit_allowed_repo"))) } +// Home render repository home page +func Home(ctx *context.Context) { + checkHomeCodeViewable(ctx) + if ctx.Written() { + return + } + + renderCode(ctx) +} + +// LastCommit returns lastCommit data for the provided branch/tag/commit and directory (in url) and filenames in body +func LastCommit(ctx *context.Context) { + checkHomeCodeViewable(ctx) + if ctx.Written() { + return + } + + renderDirectoryFiles(ctx, 0) + if ctx.Written() { + return + } + + var treeNames []string + paths := make([]string, 0, 5) + if len(ctx.Repo.TreePath) > 0 { + treeNames = strings.Split(ctx.Repo.TreePath, "/") + for i := range treeNames { + paths = append(paths, strings.Join(treeNames[:i+1], "/")) + } + + ctx.Data["HasParentPath"] = true + if len(paths)-2 >= 0 { + ctx.Data["ParentPath"] = "/" + paths[len(paths)-2] + } + } + branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL() + ctx.Data["BranchLink"] = branchLink + + ctx.HTML(http.StatusOK, tplRepoViewList) +} + +func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entries { + tree, err := ctx.Repo.Commit.SubTree(ctx.Repo.TreePath) + if err != nil { + ctx.NotFoundOrServerError("Repo.Commit.SubTree", git.IsErrNotExist, err) + return nil + } + + ctx.Data["LastCommitLoaderURL"] = ctx.Repo.RepoLink + "/lastcommit/" + ctx.Repo.CommitID + "/" + ctx.Repo.TreePath + + // Get current entry user currently looking at. + entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath) + if err != nil { + ctx.NotFoundOrServerError("Repo.Commit.GetTreeEntryByPath", git.IsErrNotExist, err) + return nil + } + + if !entry.IsDir() { + ctx.NotFoundOrServerError("Repo.Commit.GetTreeEntryByPath", git.IsErrNotExist, err) + return nil + } + + allEntries, err := tree.ListEntries() + if err != nil { + ctx.ServerError("ListEntries", err) + return nil + } + allEntries.CustomSort(base.NaturalSortLess) + + commitInfoCtx := gocontext.Context(ctx) + if timeout > 0 { + var cancel gocontext.CancelFunc + commitInfoCtx, cancel = gocontext.WithTimeout(ctx, timeout) + defer cancel() + } + + var c *git.LastCommitCache + if setting.CacheService.LastCommit.Enabled && ctx.Repo.CommitsCount >= setting.CacheService.LastCommit.CommitsCount { + c = git.NewLastCommitCache(ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, setting.LastCommitCacheTTLSeconds, cache.GetCache()) + } + + selected := map[string]bool{} + for _, pth := range ctx.FormStrings("f[]") { + selected[pth] = true + } + + entries := allEntries + if len(selected) > 0 { + entries = make(git.Entries, 0, len(selected)) + for _, entry := range allEntries { + if selected[entry.Name()] { + entries = append(entries, entry) + } + } + } + + var latestCommit *git.Commit + ctx.Data["Files"], latestCommit, err = entries.GetCommitsInfo(commitInfoCtx, ctx.Repo.Commit, ctx.Repo.TreePath, c) + if err != nil { + ctx.ServerError("GetCommitsInfo", err) + return nil + } + + // Show latest commit info of repository in table header, + // or of directory if not in root directory. + ctx.Data["LatestCommit"] = latestCommit + if latestCommit != nil { + + verification := models.ParseCommitWithSignature(latestCommit) + + if err := models.CalculateTrustStatus(verification, ctx.Repo.Repository, nil); err != nil { + ctx.ServerError("CalculateTrustStatus", err) + return nil + } + ctx.Data["LatestCommitVerification"] = verification + ctx.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit) + } + + statuses, err := models.GetLatestCommitStatus(ctx.Repo.Repository.ID, ctx.Repo.Commit.ID.String(), db.ListOptions{}) + if err != nil { + log.Error("GetLatestCommitStatus: %v", err) + } + + ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(statuses) + ctx.Data["LatestCommitStatuses"] = statuses + + branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL() + treeLink := branchLink + + if len(ctx.Repo.TreePath) > 0 { + treeLink += "/" + ctx.Repo.TreePath + } + + ctx.Data["TreeLink"] = treeLink + ctx.Data["SSHDomain"] = setting.SSH.Domain + + return allEntries +} + func renderLanguageStats(ctx *context.Context) { langs, err := ctx.Repo.Repository.GetTopLanguageStats(5) if err != nil { @@ -755,7 +860,7 @@ func renderCode(ctx *context.Context) { } // RenderUserCards render a page show users according the input template -func RenderUserCards(ctx *context.Context, total int, getter func(opts models.ListOptions) ([]*models.User, error), tpl base.TplName) { +func RenderUserCards(ctx *context.Context, total int, getter func(opts db.ListOptions) ([]*models.User, error), tpl base.TplName) { page := ctx.FormInt("page") if page <= 0 { page = 1 @@ -763,7 +868,7 @@ func RenderUserCards(ctx *context.Context, total int, getter func(opts models.Li pager := context.NewPagination(total, models.ItemsPerPage, page, 5) ctx.Data["Page"] = pager - items, err := getter(models.ListOptions{ + items, err := getter(db.ListOptions{ Page: pager.Paginater.Current(), PageSize: models.ItemsPerPage, }) @@ -798,7 +903,7 @@ func Forks(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repos.forks") // TODO: need pagination - forks, err := ctx.Repo.Repository.GetForks(models.ListOptions{}) + forks, err := ctx.Repo.Repository.GetForks(db.ListOptions{}) if err != nil { ctx.ServerError("GetForks", err) return diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index f0b91aae995f4..a571c46fd73e4 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -8,7 +8,7 @@ package repo import ( "bytes" "fmt" - "io/ioutil" + "io" "net/http" "net/url" "path/filepath" @@ -110,7 +110,7 @@ func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte { return nil } defer reader.Close() - content, err := ioutil.ReadAll(reader) + content, err := io.ReadAll(reader) if err != nil { ctx.ServerError("ReadAll", err) return nil diff --git a/routers/web/repo/wiki_test.go b/routers/web/repo/wiki_test.go index bcdb8023acff3..14cb893d46d7a 100644 --- a/routers/web/repo/wiki_test.go +++ b/routers/web/repo/wiki_test.go @@ -5,11 +5,12 @@ package repo import ( - "io/ioutil" + "io" "net/http" "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/web" @@ -46,7 +47,7 @@ func wikiContent(t *testing.T, repo *models.Repository, wikiName string) string reader, err := entry.Blob().DataAsync() assert.NoError(t, err) defer reader.Close() - bytes, err := ioutil.ReadAll(reader) + bytes, err := io.ReadAll(reader) assert.NoError(t, err) return string(bytes) } @@ -73,7 +74,7 @@ func assertPagesMetas(t *testing.T, expectedNames []string, metas interface{}) { } func TestWiki(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/wiki/_pages") ctx.SetParams(":page", "Home") @@ -85,7 +86,7 @@ func TestWiki(t *testing.T) { } func TestWikiPages(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/wiki/_pages") test.LoadRepo(t, ctx, 1) @@ -95,7 +96,7 @@ func TestWikiPages(t *testing.T) { } func TestNewWiki(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/wiki/_new") test.LoadUser(t, ctx, 2) @@ -110,7 +111,7 @@ func TestNewWikiPost(t *testing.T) { "New page", "&&&&", } { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/wiki/_new") test.LoadUser(t, ctx, 2) @@ -128,7 +129,7 @@ func TestNewWikiPost(t *testing.T) { } func TestNewWikiPost_ReservedName(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/wiki/_new") test.LoadUser(t, ctx, 2) @@ -145,7 +146,7 @@ func TestNewWikiPost_ReservedName(t *testing.T) { } func TestEditWiki(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/wiki/_edit/Home") ctx.SetParams(":page", "Home") @@ -162,7 +163,7 @@ func TestEditWikiPost(t *testing.T) { "Home", "New/", } { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/wiki/_new/Home") ctx.SetParams(":page", "Home") test.LoadUser(t, ctx, 2) @@ -183,7 +184,7 @@ func TestEditWikiPost(t *testing.T) { } func TestDeleteWikiPagePost(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/wiki/Home/delete") test.LoadUser(t, ctx, 2) @@ -202,7 +203,7 @@ func TestWikiRaw(t *testing.T) { "Page With Spaced Name.md": "text/plain; charset=utf-8", "Page-With-Spaced-Name.md": "text/plain; charset=utf-8", } { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1/wiki/raw/"+filepath) ctx.SetParams("*", filepath) diff --git a/routers/web/user/auth.go b/routers/web/user/auth.go index 313a583004a57..12328e46a1657 100644 --- a/routers/web/user/auth.go +++ b/routers/web/user/auth.go @@ -9,11 +9,12 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net/http" "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/eventsource" @@ -55,7 +56,7 @@ const ( // AutoSignIn reads cookie and try to auto-login. func AutoSignIn(ctx *context.Context) (bool, error) { - if !models.HasEngine { + if !db.HasEngine { return false, nil } @@ -147,7 +148,7 @@ func SignIn(ctx *context.Context) { ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" ctx.Data["PageIsSignIn"] = true ctx.Data["PageIsLogin"] = true - ctx.Data["EnableSSPI"] = models.IsSSPIEnabled() + ctx.Data["EnableSSPI"] = login.IsSSPIEnabled() ctx.HTML(http.StatusOK, tplSignIn) } @@ -167,7 +168,7 @@ func SignInPost(ctx *context.Context) { ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" ctx.Data["PageIsSignIn"] = true ctx.Data["PageIsLogin"] = true - ctx.Data["EnableSSPI"] = models.IsSSPIEnabled() + ctx.Data["EnableSSPI"] = login.IsSSPIEnabled() if ctx.HasError() { ctx.HTML(http.StatusOK, tplSignIn) @@ -175,7 +176,7 @@ func SignInPost(ctx *context.Context) { } form := web.GetForm(ctx).(*forms.SignInForm) - u, err := auth.UserSignIn(form.UserName, form.Password) + u, source, err := auth.UserSignIn(form.UserName, form.Password) if err != nil { if models.IsErrUserNotExist(err) { ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form) @@ -201,11 +202,20 @@ func SignInPost(ctx *context.Context) { } return } + + // Now handle 2FA: + + // First of all if the source can skip local two fa we're done + if skipper, ok := source.Cfg.(auth.LocalTwoFASkipper); ok && skipper.IsSkipLocalTwoFA() { + handleSignIn(ctx, u, form.Remember) + return + } + // If this user is enrolled in 2FA, we can't sign the user in just yet. // Instead, redirect them to the 2FA authentication page. - _, err = models.GetTwoFactorByUID(u.ID) + _, err = login.GetTwoFactorByUID(u.ID) if err != nil { - if models.IsErrTwoFactorNotEnrolled(err) { + if login.IsErrTwoFactorNotEnrolled(err) { handleSignIn(ctx, u, form.Remember) } else { ctx.ServerError("UserSignIn", err) @@ -227,7 +237,7 @@ func SignInPost(ctx *context.Context) { return } - regs, err := models.GetU2FRegistrationsByUID(u.ID) + regs, err := login.GetU2FRegistrationsByUID(u.ID) if err == nil && len(regs) > 0 { ctx.Redirect(setting.AppSubURL + "/user/u2f") return @@ -267,7 +277,7 @@ func TwoFactorPost(ctx *context.Context) { } id := idSess.(int64) - twofa, err := models.GetTwoFactorByUID(id) + twofa, err := login.GetTwoFactorByUID(id) if err != nil { ctx.ServerError("UserSignIn", err) return @@ -303,7 +313,7 @@ func TwoFactorPost(ctx *context.Context) { } twofa.LastUsedPasscode = form.Passcode - if err = models.UpdateTwoFactor(twofa); err != nil { + if err = login.UpdateTwoFactor(twofa); err != nil { ctx.ServerError("UserSignIn", err) return } @@ -346,7 +356,7 @@ func TwoFactorScratchPost(ctx *context.Context) { } id := idSess.(int64) - twofa, err := models.GetTwoFactorByUID(id) + twofa, err := login.GetTwoFactorByUID(id) if err != nil { ctx.ServerError("UserSignIn", err) return @@ -360,7 +370,7 @@ func TwoFactorScratchPost(ctx *context.Context) { ctx.ServerError("UserSignIn", err) return } - if err = models.UpdateTwoFactor(twofa); err != nil { + if err = login.UpdateTwoFactor(twofa); err != nil { ctx.ServerError("UserSignIn", err) return } @@ -408,7 +418,7 @@ func U2FChallenge(ctx *context.Context) { return } id := idSess.(int64) - regs, err := models.GetU2FRegistrationsByUID(id) + regs, err := login.GetU2FRegistrationsByUID(id) if err != nil { ctx.ServerError("UserSignIn", err) return @@ -444,7 +454,7 @@ func U2FSign(ctx *context.Context) { } challenge := challSess.(*u2f.Challenge) id := idSess.(int64) - regs, err := models.GetU2FRegistrationsByUID(id) + regs, err := login.GetU2FRegistrationsByUID(id) if err != nil { ctx.ServerError("UserSignIn", err) return @@ -564,7 +574,7 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR func SignInOAuth(ctx *context.Context) { provider := ctx.Params(":provider") - loginSource, err := models.GetActiveOAuth2LoginSourceByName(provider) + loginSource, err := login.GetActiveOAuth2LoginSourceByName(provider) if err != nil { ctx.ServerError("SignIn", err) return @@ -574,7 +584,7 @@ func SignInOAuth(ctx *context.Context) { user, gothUser, err := oAuth2UserLoginCallback(loginSource, ctx.Req, ctx.Resp) if err == nil && user != nil { // we got the user without going through the whole OAuth2 authentication flow again - handleOAuth2SignIn(ctx, user, gothUser) + handleOAuth2SignIn(ctx, loginSource, user, gothUser) return } @@ -599,7 +609,7 @@ func SignInOAuthCallback(ctx *context.Context) { provider := ctx.Params(":provider") // first look if the provider is still active - loginSource, err := models.GetActiveOAuth2LoginSourceByName(provider) + loginSource, err := login.GetActiveOAuth2LoginSourceByName(provider) if err != nil { ctx.ServerError("SignIn", err) return @@ -644,7 +654,7 @@ func SignInOAuthCallback(ctx *context.Context) { FullName: gothUser.Name, Email: gothUser.Email, IsActive: !setting.OAuth2Client.RegisterEmailConfirm, - LoginType: models.LoginOAuth2, + LoginType: login.OAuth2, LoginSource: loginSource.ID, LoginName: gothUser.UserID, } @@ -660,7 +670,7 @@ func SignInOAuthCallback(ctx *context.Context) { } } - handleOAuth2SignIn(ctx, u, gothUser) + handleOAuth2SignIn(ctx, loginSource, u, gothUser) } func getUserName(gothUser *goth.User) string { @@ -694,7 +704,7 @@ func updateAvatarIfNeed(url string, u *models.User) { } // ignore any error if err == nil && resp.StatusCode == http.StatusOK { - data, err := ioutil.ReadAll(io.LimitReader(resp.Body, setting.Avatar.MaxFileSize+1)) + data, err := io.ReadAll(io.LimitReader(resp.Body, setting.Avatar.MaxFileSize+1)) if err == nil && int64(len(data)) <= setting.Avatar.MaxFileSize { _ = u.UploadAvatar(data) } @@ -702,18 +712,22 @@ func updateAvatarIfNeed(url string, u *models.User) { } } -func handleOAuth2SignIn(ctx *context.Context, u *models.User, gothUser goth.User) { +func handleOAuth2SignIn(ctx *context.Context, source *login.Source, u *models.User, gothUser goth.User) { updateAvatarIfNeed(gothUser.AvatarURL, u) - // If this user is enrolled in 2FA, we can't sign the user in just yet. - // Instead, redirect them to the 2FA authentication page. - _, err := models.GetTwoFactorByUID(u.ID) - if err != nil { - if !models.IsErrTwoFactorNotEnrolled(err) { + needs2FA := false + if !source.Cfg.(*oauth2.Source).SkipLocalTwoFA { + _, err := login.GetTwoFactorByUID(u.ID) + if err != nil && !login.IsErrTwoFactorNotEnrolled(err) { ctx.ServerError("UserSignIn", err) return } + needs2FA = err == nil + } + // If this user is enrolled in 2FA and this source doesn't override it, + // we can't sign the user in just yet. Instead, redirect them to the 2FA authentication page. + if !needs2FA { if err := ctx.Session.Set("uid", u.ID); err != nil { log.Error("Error setting uid in session: %v", err) } @@ -761,7 +775,7 @@ func handleOAuth2SignIn(ctx *context.Context, u *models.User, gothUser goth.User } // If U2F is enrolled -> Redirect to U2F instead - regs, err := models.GetU2FRegistrationsByUID(u.ID) + regs, err := login.GetU2FRegistrationsByUID(u.ID) if err == nil && len(regs) > 0 { ctx.Redirect(setting.AppSubURL + "/user/u2f") return @@ -772,7 +786,7 @@ func handleOAuth2SignIn(ctx *context.Context, u *models.User, gothUser goth.User // OAuth2UserLoginCallback attempts to handle the callback from the OAuth2 provider and if successful // login the user -func oAuth2UserLoginCallback(loginSource *models.LoginSource, request *http.Request, response http.ResponseWriter) (*models.User, goth.User, error) { +func oAuth2UserLoginCallback(loginSource *login.Source, request *http.Request, response http.ResponseWriter) (*models.User, goth.User, error) { gothUser, err := loginSource.Cfg.(*oauth2.Source).Callback(request, response) if err != nil { if err.Error() == "securecookie: the value is too long" { @@ -784,7 +798,7 @@ func oAuth2UserLoginCallback(loginSource *models.LoginSource, request *http.Requ user := &models.User{ LoginName: gothUser.UserID, - LoginType: models.LoginOAuth2, + LoginType: login.OAuth2, LoginSource: loginSource.ID, } @@ -901,7 +915,7 @@ func LinkAccountPostSignIn(ctx *context.Context) { return } - u, err := auth.UserSignIn(signInForm.UserName, signInForm.Password) + u, _, err := auth.UserSignIn(signInForm.UserName, signInForm.Password) if err != nil { if models.IsErrUserNotExist(err) { ctx.Data["user_exists"] = true @@ -920,9 +934,10 @@ func linkAccount(ctx *context.Context, u *models.User, gothUser goth.User, remem // If this user is enrolled in 2FA, we can't sign the user in just yet. // Instead, redirect them to the 2FA authentication page. - _, err := models.GetTwoFactorByUID(u.ID) + // We deliberately ignore the skip local 2fa setting here because we are linking to a previous user here + _, err := login.GetTwoFactorByUID(u.ID) if err != nil { - if !models.IsErrTwoFactorNotEnrolled(err) { + if !login.IsErrTwoFactorNotEnrolled(err) { ctx.ServerError("UserLinkAccount", err) return } @@ -952,7 +967,7 @@ func linkAccount(ctx *context.Context, u *models.User, gothUser goth.User, remem } // If U2F is enrolled -> Redirect to U2F instead - regs, err := models.GetU2FRegistrationsByUID(u.ID) + regs, err := login.GetU2FRegistrationsByUID(u.ID) if err == nil && len(regs) > 0 { ctx.Redirect(setting.AppSubURL + "/user/u2f") return @@ -1054,7 +1069,7 @@ func LinkAccountPostRegister(ctx *context.Context) { } } - loginSource, err := models.GetActiveOAuth2LoginSourceByName(gothUser.Provider) + loginSource, err := login.GetActiveOAuth2LoginSourceByName(gothUser.Provider) if err != nil { ctx.ServerError("CreateUser", err) } @@ -1064,7 +1079,7 @@ func LinkAccountPostRegister(ctx *context.Context) { Email: form.Email, Passwd: form.Password, IsActive: !(setting.Service.RegisterEmailConfirm || setting.Service.RegisterManualConfirm), - LoginType: models.LoginOAuth2, + LoginType: login.OAuth2, LoginSource: loginSource.ID, LoginName: gothUser.UserID, } @@ -1546,7 +1561,7 @@ func ForgotPasswdPost(ctx *context.Context) { ctx.HTML(http.StatusOK, tplForgotPassword) } -func commonResetPassword(ctx *context.Context) (*models.User, *models.TwoFactor) { +func commonResetPassword(ctx *context.Context) (*models.User, *login.TwoFactor) { code := ctx.FormString("code") ctx.Data["Title"] = ctx.Tr("auth.reset_password") @@ -1568,9 +1583,9 @@ func commonResetPassword(ctx *context.Context) (*models.User, *models.TwoFactor) return nil, nil } - twofa, err := models.GetTwoFactorByUID(u.ID) + twofa, err := login.GetTwoFactorByUID(u.ID) if err != nil { - if !models.IsErrTwoFactorNotEnrolled(err) { + if !login.IsErrTwoFactorNotEnrolled(err) { ctx.Error(http.StatusInternalServerError, "CommonResetPassword", err.Error()) return nil, nil } @@ -1665,7 +1680,7 @@ func ResetPasswdPost(ctx *context.Context) { } twofa.LastUsedPasscode = passcode - if err = models.UpdateTwoFactor(twofa); err != nil { + if err = login.UpdateTwoFactor(twofa); err != nil { ctx.ServerError("ResetPasswdPost: UpdateTwoFactor", err) return } @@ -1697,7 +1712,7 @@ func ResetPasswdPost(ctx *context.Context) { ctx.ServerError("UserSignIn", err) return } - if err = models.UpdateTwoFactor(twofa); err != nil { + if err = login.UpdateTwoFactor(twofa); err != nil { ctx.ServerError("UserSignIn", err) return } diff --git a/routers/web/user/auth_openid.go b/routers/web/user/auth_openid.go index fc419a7f6ea63..e6ad6fef4c413 100644 --- a/routers/web/user/auth_openid.go +++ b/routers/web/user/auth_openid.go @@ -291,7 +291,7 @@ func ConnectOpenIDPost(ctx *context.Context) { ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp ctx.Data["OpenID"] = oid - u, err := auth.UserSignIn(form.UserName, form.Password) + u, _, err := auth.UserSignIn(form.UserName, form.Password) if err != nil { if models.IsErrUserNotExist(err) { ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplConnectOID, &form) diff --git a/routers/web/user/avatar.go b/routers/web/user/avatar.go index 2df5c148f7904..f39bcc36d343b 100644 --- a/routers/web/user/avatar.go +++ b/routers/web/user/avatar.go @@ -5,100 +5,50 @@ package user import ( - "errors" - "net/url" - "path" - "strconv" "strings" + "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/avatars" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/httpcache" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" ) func cacheableRedirect(ctx *context.Context, location string) { - ctx.Resp.Header().Set("Cache-Control", httpcache.GetCacheControl()) + // here we should not use `setting.StaticCacheTime`, it is pretty long (default: 6 hours) + // we must make sure the redirection cache time is short enough, otherwise a user won't see the updated avatar in 6 hours + // it's OK to make the cache time short, it is only a redirection, and doesn't cost much to make a new request + httpcache.AddCacheControlToHeader(ctx.Resp.Header(), 5*time.Minute) ctx.Redirect(location) } -// Avatar redirect browser to user avatar of requested size -func Avatar(ctx *context.Context) { +// AvatarByUserName redirect browser to user avatar of requested size +func AvatarByUserName(ctx *context.Context) { userName := ctx.Params(":username") - size, err := strconv.Atoi(ctx.Params(":size")) - if err != nil { - ctx.ServerError("Invalid avatar size", err) - return - } - - log.Debug("Asked avatar for user %v and size %v", userName, size) + size := int(ctx.ParamsInt64(":size")) var user *models.User if strings.ToLower(userName) != "ghost" { - user, err = models.GetUserByName(userName) - if err != nil { - if models.IsErrUserNotExist(err) { - ctx.ServerError("Requested avatar for invalid user", err) - } else { - ctx.ServerError("Retrieving user by name", err) - } + var err error + if user, err = models.GetUserByName(userName); err != nil { + ctx.ServerError("Invalid user: "+userName, err) return } } else { user = models.NewGhostUser() } - cacheableRedirect(ctx, user.RealSizedAvatarLink(size)) + cacheableRedirect(ctx, user.AvatarLinkWithSize(size)) } -// AvatarByEmailHash redirects the browser to the appropriate Avatar link +// AvatarByEmailHash redirects the browser to the email avatar link func AvatarByEmailHash(ctx *context.Context) { - var err error - hash := ctx.Params(":hash") - if len(hash) == 0 { - ctx.ServerError("invalid avatar hash", errors.New("hash cannot be empty")) - return - } - - var email string - email, err = models.GetEmailForHash(hash) + email, err := avatars.GetEmailForHash(hash) if err != nil { - ctx.ServerError("invalid avatar hash", err) - return - } - if len(email) == 0 { - cacheableRedirect(ctx, models.DefaultAvatarLink()) + ctx.ServerError("invalid avatar hash: "+hash, err) return } size := ctx.FormInt("size") - if size == 0 { - size = models.DefaultAvatarSize - } - - var avatarURL *url.URL - - if setting.EnableFederatedAvatar && setting.LibravatarService != nil { - avatarURL, err = models.LibravatarURL(email) - if err != nil { - avatarURL, err = url.Parse(models.DefaultAvatarLink()) - if err != nil { - ctx.ServerError("invalid default avatar url", err) - return - } - } - } else if !setting.DisableGravatar { - copyOfGravatarSourceURL := *setting.GravatarSourceURL - avatarURL = ©OfGravatarSourceURL - avatarURL.Path = path.Join(avatarURL.Path, hash) - } else { - avatarURL, err = url.Parse(models.DefaultAvatarLink()) - if err != nil { - ctx.ServerError("invalid default avatar url", err) - return - } - } - - cacheableRedirect(ctx, models.MakeFinalAvatarURL(avatarURL, size)) + cacheableRedirect(ctx, avatars.GenerateEmailAvatarFinalLink(email, size)) } diff --git a/routers/web/user/home.go b/routers/web/user/home.go index bb75558dc85a2..2f1fca4527115 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -15,6 +15,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" issue_indexer "code.gitea.io/gitea/modules/indexer/issues" @@ -846,7 +847,7 @@ func repoIDMap(ctxUser *models.User, issueCountByRepo map[int64]int64, unitType // ShowSSHKeys output all the ssh keys of user by uid func ShowSSHKeys(ctx *context.Context, uid int64) { - keys, err := models.ListPublicKeys(uid, models.ListOptions{}) + keys, err := models.ListPublicKeys(uid, db.ListOptions{}) if err != nil { ctx.ServerError("ListPublicKeys", err) return @@ -862,7 +863,7 @@ func ShowSSHKeys(ctx *context.Context, uid int64) { // ShowGPGKeys output all the public GPG keys of user by uid func ShowGPGKeys(ctx *context.Context, uid int64) { - keys, err := models.ListGPGKeys(uid, models.ListOptions{}) + keys, err := models.ListGPGKeys(uid, db.ListOptions{}) if err != nil { ctx.ServerError("ListGPGKeys", err) return diff --git a/routers/web/user/home_test.go b/routers/web/user/home_test.go index b0109c354f436..daf473b270c00 100644 --- a/routers/web/user/home_test.go +++ b/routers/web/user/home_test.go @@ -9,6 +9,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" @@ -18,7 +19,7 @@ import ( func TestArchivedIssues(t *testing.T) { // Arrange setting.UI.IssuePagingNum = 1 - assert.NoError(t, models.LoadFixtures()) + assert.NoError(t, db.LoadFixtures()) ctx := test.MockContext(t, "issues") test.LoadUser(t, ctx, 30) @@ -51,7 +52,7 @@ func TestArchivedIssues(t *testing.T) { func TestIssues(t *testing.T) { setting.UI.IssuePagingNum = 1 - assert.NoError(t, models.LoadFixtures()) + assert.NoError(t, db.LoadFixtures()) ctx := test.MockContext(t, "issues") test.LoadUser(t, ctx, 2) @@ -67,7 +68,7 @@ func TestIssues(t *testing.T) { func TestPulls(t *testing.T) { setting.UI.IssuePagingNum = 20 - assert.NoError(t, models.LoadFixtures()) + assert.NoError(t, db.LoadFixtures()) ctx := test.MockContext(t, "pulls") test.LoadUser(t, ctx, 2) @@ -80,7 +81,7 @@ func TestPulls(t *testing.T) { func TestMilestones(t *testing.T) { setting.UI.IssuePagingNum = 1 - assert.NoError(t, models.LoadFixtures()) + assert.NoError(t, db.LoadFixtures()) ctx := test.MockContext(t, "milestones") test.LoadUser(t, ctx, 2) @@ -99,7 +100,7 @@ func TestMilestones(t *testing.T) { func TestMilestonesForSpecificRepo(t *testing.T) { setting.UI.IssuePagingNum = 1 - assert.NoError(t, models.LoadFixtures()) + assert.NoError(t, db.LoadFixtures()) ctx := test.MockContext(t, "milestones") test.LoadUser(t, ctx, 2) diff --git a/routers/web/user/main_test.go b/routers/web/user/main_test.go index be17dd1f3135f..272e4b8b2111b 100644 --- a/routers/web/user/main_test.go +++ b/routers/web/user/main_test.go @@ -8,9 +8,9 @@ import ( "path/filepath" "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..", "..")) + db.MainTest(m, filepath.Join("..", "..", "..")) } diff --git a/routers/web/user/notification.go b/routers/web/user/notification.go index a444669b742ab..ec3395cbc17a8 100644 --- a/routers/web/user/notification.go +++ b/routers/web/user/notification.go @@ -160,7 +160,7 @@ func NotificationStatusPost(c *context.Context) { return } - if err := models.SetNotificationStatus(notificationID, c.User, status); err != nil { + if _, err := models.SetNotificationStatus(notificationID, c.User, status); err != nil { c.ServerError("SetNotificationStatus", err) return } diff --git a/routers/web/user/oauth.go b/routers/web/user/oauth.go index cec6a92bbea45..d9fc5eeaf9232 100644 --- a/routers/web/user/oauth.go +++ b/routers/web/user/oauth.go @@ -13,6 +13,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/json" @@ -115,7 +116,7 @@ type AccessTokenResponse struct { IDToken string `json:"id_token,omitempty"` } -func newAccessTokenResponse(grant *models.OAuth2Grant, serverKey, clientKey oauth2.JWTSigningKey) (*AccessTokenResponse, *AccessTokenError) { +func newAccessTokenResponse(grant *login.OAuth2Grant, serverKey, clientKey oauth2.JWTSigningKey) (*AccessTokenResponse, *AccessTokenError) { if setting.OAuth2.InvalidateRefreshTokens { if err := grant.IncreaseCounter(); err != nil { return nil, &AccessTokenError{ @@ -162,7 +163,7 @@ func newAccessTokenResponse(grant *models.OAuth2Grant, serverKey, clientKey oaut // generate OpenID Connect id_token signedIDToken := "" if grant.ScopeContains("openid") { - app, err := models.GetOAuth2ApplicationByID(grant.ApplicationID) + app, err := login.GetOAuth2ApplicationByID(grant.ApplicationID) if err != nil { return nil, &AccessTokenError{ ErrorCode: AccessTokenErrorCodeInvalidRequest, @@ -268,9 +269,9 @@ func IntrospectOAuth(ctx *context.Context) { token, err := oauth2.ParseToken(form.Token, oauth2.DefaultSigningKey) if err == nil { if token.Valid() == nil { - grant, err := models.GetOAuth2GrantByID(token.GrantID) + grant, err := login.GetOAuth2GrantByID(token.GrantID) if err == nil && grant != nil { - app, err := models.GetOAuth2ApplicationByID(grant.ApplicationID) + app, err := login.GetOAuth2ApplicationByID(grant.ApplicationID) if err == nil && app != nil { response.Active = true response.Scope = grant.Scope @@ -299,9 +300,9 @@ func AuthorizeOAuth(ctx *context.Context) { return } - app, err := models.GetOAuth2ApplicationByClientID(form.ClientID) + app, err := login.GetOAuth2ApplicationByClientID(form.ClientID) if err != nil { - if models.IsErrOauthClientIDInvalid(err) { + if login.IsErrOauthClientIDInvalid(err) { handleAuthorizeError(ctx, AuthorizeError{ ErrorCode: ErrorCodeUnauthorizedClient, ErrorDescription: "Client ID not registered", @@ -312,8 +313,10 @@ func AuthorizeOAuth(ctx *context.Context) { ctx.ServerError("GetOAuth2ApplicationByClientID", err) return } - if err := app.LoadUser(); err != nil { - ctx.ServerError("LoadUser", err) + + user, err := models.GetUserByID(app.UID) + if err != nil { + ctx.ServerError("GetUserByID", err) return } @@ -406,7 +409,7 @@ func AuthorizeOAuth(ctx *context.Context) { ctx.Data["State"] = form.State ctx.Data["Scope"] = form.Scope ctx.Data["Nonce"] = form.Nonce - ctx.Data["ApplicationUserLink"] = "@" + html.EscapeString(app.User.Name) + "" + ctx.Data["ApplicationUserLink"] = "@" + html.EscapeString(user.Name) + "" ctx.Data["ApplicationRedirectDomainHTML"] = "" + html.EscapeString(form.RedirectURI) + "" // TODO document SESSION <=> FORM err = ctx.Session.Set("client_id", app.ClientID) @@ -443,7 +446,7 @@ func GrantApplicationOAuth(ctx *context.Context) { ctx.Error(http.StatusBadRequest) return } - app, err := models.GetOAuth2ApplicationByClientID(form.ClientID) + app, err := login.GetOAuth2ApplicationByClientID(form.ClientID) if err != nil { ctx.ServerError("GetOAuth2ApplicationByClientID", err) return @@ -581,7 +584,7 @@ func handleRefreshToken(ctx *context.Context, form forms.AccessTokenForm, server return } // get grant before increasing counter - grant, err := models.GetOAuth2GrantByID(token.GrantID) + grant, err := login.GetOAuth2GrantByID(token.GrantID) if err != nil || grant == nil { handleAccessTokenError(ctx, AccessTokenError{ ErrorCode: AccessTokenErrorCodeInvalidGrant, @@ -608,7 +611,7 @@ func handleRefreshToken(ctx *context.Context, form forms.AccessTokenForm, server } func handleAuthorizationCode(ctx *context.Context, form forms.AccessTokenForm, serverKey, clientKey oauth2.JWTSigningKey) { - app, err := models.GetOAuth2ApplicationByClientID(form.ClientID) + app, err := login.GetOAuth2ApplicationByClientID(form.ClientID) if err != nil { handleAccessTokenError(ctx, AccessTokenError{ ErrorCode: AccessTokenErrorCodeInvalidClient, @@ -630,7 +633,7 @@ func handleAuthorizationCode(ctx *context.Context, form forms.AccessTokenForm, s }) return } - authorizationCode, err := models.GetOAuth2AuthorizationByCode(form.Code) + authorizationCode, err := login.GetOAuth2AuthorizationByCode(form.Code) if err != nil || authorizationCode == nil { handleAccessTokenError(ctx, AccessTokenError{ ErrorCode: AccessTokenErrorCodeUnauthorizedClient, diff --git a/routers/web/user/oauth_test.go b/routers/web/user/oauth_test.go index 40116d3c12979..27d339b778ea3 100644 --- a/routers/web/user/oauth_test.go +++ b/routers/web/user/oauth_test.go @@ -8,13 +8,15 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/services/auth/source/oauth2" "github.com/golang-jwt/jwt" "github.com/stretchr/testify/assert" ) -func createAndParseToken(t *testing.T, grant *models.OAuth2Grant) *oauth2.OIDCToken { +func createAndParseToken(t *testing.T, grant *login.OAuth2Grant) *oauth2.OIDCToken { signingKey, err := oauth2.CreateJWTSigningKey("HS256", make([]byte, 32)) assert.NoError(t, err) assert.NotNil(t, signingKey) @@ -39,9 +41,9 @@ func createAndParseToken(t *testing.T, grant *models.OAuth2Grant) *oauth2.OIDCTo } func TestNewAccessTokenResponse_OIDCToken(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - grants, err := models.GetOAuth2GrantsByUserID(3) + grants, err := login.GetOAuth2GrantsByUserID(3) assert.NoError(t, err) assert.Len(t, grants, 1) @@ -56,8 +58,8 @@ func TestNewAccessTokenResponse_OIDCToken(t *testing.T) { assert.Empty(t, oidcToken.Email) assert.False(t, oidcToken.EmailVerified) - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 5}).(*models.User) - grants, err = models.GetOAuth2GrantsByUserID(user.ID) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 5}).(*models.User) + grants, err = login.GetOAuth2GrantsByUserID(user.ID) assert.NoError(t, err) assert.Len(t, grants, 1) diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 9ecdc2345c5ea..d64d5621dead2 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -12,6 +12,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" @@ -192,7 +193,7 @@ func Profile(ctx *context.Context) { ctx.Data["Keyword"] = keyword switch tab { case "followers": - items, err := ctxUser.GetFollowers(models.ListOptions{ + items, err := ctxUser.GetFollowers(db.ListOptions{ PageSize: setting.UI.User.RepoPagingNum, Page: page, }) @@ -204,7 +205,7 @@ func Profile(ctx *context.Context) { total = ctxUser.NumFollowers case "following": - items, err := ctxUser.GetFollowing(models.ListOptions{ + items, err := ctxUser.GetFollowing(db.ListOptions{ PageSize: setting.UI.User.RepoPagingNum, Page: page, }) @@ -229,7 +230,7 @@ func Profile(ctx *context.Context) { case "stars": ctx.Data["PageIsProfileStarList"] = true repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ - ListOptions: models.ListOptions{ + ListOptions: db.ListOptions{ PageSize: setting.UI.User.RepoPagingNum, Page: page, }, @@ -260,7 +261,7 @@ func Profile(ctx *context.Context) { } case "watching": repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ - ListOptions: models.ListOptions{ + ListOptions: db.ListOptions{ PageSize: setting.UI.User.RepoPagingNum, Page: page, }, @@ -281,7 +282,7 @@ func Profile(ctx *context.Context) { total = int(count) default: repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ - ListOptions: models.ListOptions{ + ListOptions: db.ListOptions{ PageSize: setting.UI.User.RepoPagingNum, Page: page, }, diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index 6201078954412..249793578a1a9 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -229,7 +229,7 @@ func DeleteAccount(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsAccount"] = true - if _, err := auth.UserSignIn(ctx.User.Name, ctx.FormString("password")); err != nil { + if _, _, err := auth.UserSignIn(ctx.User.Name, ctx.FormString("password")); err != nil { if models.IsErrUserNotExist(err) { loadAccountData(ctx) diff --git a/routers/web/user/setting/account_test.go b/routers/web/user/setting/account_test.go index 25b68da762714..bfb7ac48726f7 100644 --- a/routers/web/user/setting/account_test.go +++ b/routers/web/user/setting/account_test.go @@ -8,7 +8,7 @@ import ( "net/http" "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/web" @@ -81,7 +81,7 @@ func TestChangePassword(t *testing.T) { PasswordComplexity: pcLU, }, } { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) ctx := test.MockContext(t, "user/settings/security") test.LoadUser(t, ctx, 2) test.LoadRepo(t, ctx, 1) diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index 5e208afafea3d..9976337bfab90 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -9,6 +9,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" @@ -92,12 +93,12 @@ func loadApplicationsData(ctx *context.Context) { ctx.Data["Tokens"] = tokens ctx.Data["EnableOAuth2"] = setting.OAuth2.Enable if setting.OAuth2.Enable { - ctx.Data["Applications"], err = models.GetOAuth2ApplicationsByUserID(ctx.User.ID) + ctx.Data["Applications"], err = login.GetOAuth2ApplicationsByUserID(ctx.User.ID) if err != nil { ctx.ServerError("GetOAuth2ApplicationsByUserID", err) return } - ctx.Data["Grants"], err = models.GetOAuth2GrantsByUserID(ctx.User.ID) + ctx.Data["Grants"], err = login.GetOAuth2GrantsByUserID(ctx.User.ID) if err != nil { ctx.ServerError("GetOAuth2GrantsByUserID", err) return diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go index 24b9a9e205a0c..22a0fe474171f 100644 --- a/routers/web/user/setting/keys.go +++ b/routers/web/user/setting/keys.go @@ -9,6 +9,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" @@ -208,7 +209,7 @@ func DeleteKey(ctx *context.Context) { return } if external { - ctx.Flash.Error(ctx.Tr("setting.ssh_externally_managed")) + ctx.Flash.Error(ctx.Tr("settings.ssh_externally_managed")) ctx.Redirect(setting.AppSubURL + "/user/settings/keys") return } @@ -233,7 +234,7 @@ func DeleteKey(ctx *context.Context) { } func loadKeysData(ctx *context.Context) { - keys, err := models.ListPublicKeys(ctx.User.ID, models.ListOptions{}) + keys, err := models.ListPublicKeys(ctx.User.ID, db.ListOptions{}) if err != nil { ctx.ServerError("ListPublicKeys", err) return @@ -247,7 +248,7 @@ func loadKeysData(ctx *context.Context) { } ctx.Data["ExternalKeys"] = externalKeys - gpgkeys, err := models.ListGPGKeys(ctx.User.ID, models.ListOptions{}) + gpgkeys, err := models.ListGPGKeys(ctx.User.ID, db.ListOptions{}) if err != nil { ctx.ServerError("ListGPGKeys", err) return @@ -258,7 +259,7 @@ func loadKeysData(ctx *context.Context) { // generate a new aes cipher using the csrfToken ctx.Data["TokenToSign"] = tokenToSign - principals, err := models.ListPrincipalKeys(ctx.User.ID, models.ListOptions{}) + principals, err := models.ListPrincipalKeys(ctx.User.ID, db.ListOptions{}) if err != nil { ctx.ServerError("ListPrincipalKeys", err) return diff --git a/routers/web/user/setting/main_test.go b/routers/web/user/setting/main_test.go index daa3f7fe5bf16..a0fbe55ee17ee 100644 --- a/routers/web/user/setting/main_test.go +++ b/routers/web/user/setting/main_test.go @@ -8,9 +8,9 @@ import ( "path/filepath" "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..", "..", "..")) + db.MainTest(m, filepath.Join("..", "..", "..", "..")) } diff --git a/routers/web/user/setting/oauth2.go b/routers/web/user/setting/oauth2.go index 8de0720b51057..0f338ab5d1cbb 100644 --- a/routers/web/user/setting/oauth2.go +++ b/routers/web/user/setting/oauth2.go @@ -8,7 +8,7 @@ import ( "fmt" "net/http" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" @@ -34,7 +34,7 @@ func OAuthApplicationsPost(ctx *context.Context) { return } // TODO validate redirect URI - app, err := models.CreateOAuth2Application(models.CreateOAuth2ApplicationOptions{ + app, err := login.CreateOAuth2Application(login.CreateOAuth2ApplicationOptions{ Name: form.Name, RedirectURIs: []string{form.RedirectURI}, UserID: ctx.User.ID, @@ -67,7 +67,7 @@ func OAuthApplicationsEdit(ctx *context.Context) { } // TODO validate redirect URI var err error - if ctx.Data["App"], err = models.UpdateOAuth2Application(models.UpdateOAuth2ApplicationOptions{ + if ctx.Data["App"], err = login.UpdateOAuth2Application(login.UpdateOAuth2ApplicationOptions{ ID: ctx.ParamsInt64("id"), Name: form.Name, RedirectURIs: []string{form.RedirectURI}, @@ -85,9 +85,9 @@ func OAuthApplicationsRegenerateSecret(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsApplications"] = true - app, err := models.GetOAuth2ApplicationByID(ctx.ParamsInt64("id")) + app, err := login.GetOAuth2ApplicationByID(ctx.ParamsInt64("id")) if err != nil { - if models.IsErrOAuthApplicationNotFound(err) { + if login.IsErrOAuthApplicationNotFound(err) { ctx.NotFound("Application not found", err) return } @@ -110,9 +110,9 @@ func OAuthApplicationsRegenerateSecret(ctx *context.Context) { // OAuth2ApplicationShow displays the given application func OAuth2ApplicationShow(ctx *context.Context) { - app, err := models.GetOAuth2ApplicationByID(ctx.ParamsInt64("id")) + app, err := login.GetOAuth2ApplicationByID(ctx.ParamsInt64("id")) if err != nil { - if models.IsErrOAuthApplicationNotFound(err) { + if login.IsErrOAuthApplicationNotFound(err) { ctx.NotFound("Application not found", err) return } @@ -129,7 +129,7 @@ func OAuth2ApplicationShow(ctx *context.Context) { // DeleteOAuth2Application deletes the given oauth2 application func DeleteOAuth2Application(ctx *context.Context) { - if err := models.DeleteOAuth2Application(ctx.FormInt64("id"), ctx.User.ID); err != nil { + if err := login.DeleteOAuth2Application(ctx.FormInt64("id"), ctx.User.ID); err != nil { ctx.ServerError("DeleteOAuth2Application", err) return } @@ -147,7 +147,7 @@ func RevokeOAuth2Grant(ctx *context.Context) { ctx.ServerError("RevokeOAuth2Grant", fmt.Errorf("user id or grant id is zero")) return } - if err := models.RevokeOAuth2Grant(ctx.FormInt64("id"), ctx.User.ID); err != nil { + if err := login.RevokeOAuth2Grant(ctx.FormInt64("id"), ctx.User.ID); err != nil { ctx.ServerError("RevokeOAuth2Grant", err) return } diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index 15c08856b4604..d75149b8fc717 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -8,13 +8,14 @@ package setting import ( "errors" "fmt" - "io/ioutil" + "io" "net/http" "os" "path/filepath" "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" @@ -167,9 +168,9 @@ func UpdateAvatarSetting(ctx *context.Context, form *forms.AvatarForm, ctxUser * return errors.New(ctx.Tr("settings.uploaded_avatar_is_too_big")) } - data, err := ioutil.ReadAll(fr) + data, err := io.ReadAll(fr) if err != nil { - return fmt.Errorf("ioutil.ReadAll: %v", err) + return fmt.Errorf("io.ReadAll: %v", err) } st := typesniffer.DetectContentType(data) @@ -235,7 +236,7 @@ func Repos(ctx *context.Context) { ctx.Data["allowAdopt"] = ctx.IsUserSiteAdmin() || setting.Repository.AllowAdoptionOfUnadoptedRepositories ctx.Data["allowDelete"] = ctx.IsUserSiteAdmin() || setting.Repository.AllowDeleteOfUnadoptedRepositories - opts := models.ListOptions{ + opts := db.ListOptions{ PageSize: setting.UI.Admin.UserPagingNum, Page: ctx.FormInt("page"), } @@ -284,7 +285,7 @@ func Repos(ctx *context.Context) { return } - if err := ctxUser.GetRepositories(models.ListOptions{Page: 1, PageSize: setting.UI.Admin.UserPagingNum}, repoNames...); err != nil { + if err := ctxUser.GetRepositories(db.ListOptions{Page: 1, PageSize: setting.UI.Admin.UserPagingNum}, repoNames...); err != nil { ctx.ServerError("GetRepositories", err) return } diff --git a/routers/web/user/setting/security.go b/routers/web/user/setting/security.go index 3406194015f65..53f672282d1a2 100644 --- a/routers/web/user/setting/security.go +++ b/routers/web/user/setting/security.go @@ -9,6 +9,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" @@ -55,9 +56,9 @@ func DeleteAccountLink(ctx *context.Context) { func loadSecurityData(ctx *context.Context) { enrolled := true - _, err := models.GetTwoFactorByUID(ctx.User.ID) + _, err := login.GetTwoFactorByUID(ctx.User.ID) if err != nil { - if models.IsErrTwoFactorNotEnrolled(err) { + if login.IsErrTwoFactorNotEnrolled(err) { enrolled = false } else { ctx.ServerError("SettingsTwoFactor", err) @@ -66,7 +67,7 @@ func loadSecurityData(ctx *context.Context) { } ctx.Data["TwofaEnrolled"] = enrolled if enrolled { - ctx.Data["U2FRegistrations"], err = models.GetU2FRegistrationsByUID(ctx.User.ID) + ctx.Data["U2FRegistrations"], err = login.GetU2FRegistrationsByUID(ctx.User.ID) if err != nil { ctx.ServerError("GetU2FRegistrationsByUID", err) return @@ -87,9 +88,9 @@ func loadSecurityData(ctx *context.Context) { } // map the provider display name with the LoginSource - sources := make(map[*models.LoginSource]string) + sources := make(map[*login.Source]string) for _, externalAccount := range accountLinks { - if loginSource, err := models.GetLoginSourceByID(externalAccount.LoginSourceID); err == nil { + if loginSource, err := login.GetSourceByID(externalAccount.LoginSourceID); err == nil { var providerDisplayName string type DisplayNamed interface { diff --git a/routers/web/user/setting/security_twofa.go b/routers/web/user/setting/security_twofa.go index 7b08a05939b30..94f975f9fe6f8 100644 --- a/routers/web/user/setting/security_twofa.go +++ b/routers/web/user/setting/security_twofa.go @@ -13,7 +13,7 @@ import ( "net/http" "strings" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -29,10 +29,10 @@ func RegenerateScratchTwoFactor(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsSecurity"] = true - t, err := models.GetTwoFactorByUID(ctx.User.ID) + t, err := login.GetTwoFactorByUID(ctx.User.ID) if err != nil { - if models.IsErrTwoFactorNotEnrolled(err) { - ctx.Flash.Error(ctx.Tr("setting.twofa_not_enrolled")) + if login.IsErrTwoFactorNotEnrolled(err) { + ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") } ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err) @@ -45,7 +45,7 @@ func RegenerateScratchTwoFactor(ctx *context.Context) { return } - if err = models.UpdateTwoFactor(t); err != nil { + if err = login.UpdateTwoFactor(t); err != nil { ctx.ServerError("SettingsTwoFactor: Failed to UpdateTwoFactor", err) return } @@ -59,18 +59,18 @@ func DisableTwoFactor(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsSecurity"] = true - t, err := models.GetTwoFactorByUID(ctx.User.ID) + t, err := login.GetTwoFactorByUID(ctx.User.ID) if err != nil { - if models.IsErrTwoFactorNotEnrolled(err) { - ctx.Flash.Error(ctx.Tr("setting.twofa_not_enrolled")) + if login.IsErrTwoFactorNotEnrolled(err) { + ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") } ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err) return } - if err = models.DeleteTwoFactorByID(t.ID, ctx.User.ID); err != nil { - if models.IsErrTwoFactorNotEnrolled(err) { + if err = login.DeleteTwoFactorByID(t.ID, ctx.User.ID); err != nil { + if login.IsErrTwoFactorNotEnrolled(err) { // There is a potential DB race here - we must have been disabled by another request in the intervening period ctx.Flash.Success(ctx.Tr("settings.twofa_disabled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") @@ -146,15 +146,15 @@ func EnrollTwoFactor(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsSecurity"] = true - t, err := models.GetTwoFactorByUID(ctx.User.ID) + t, err := login.GetTwoFactorByUID(ctx.User.ID) if t != nil { // already enrolled - we should redirect back! log.Warn("Trying to re-enroll %-v in twofa when already enrolled", ctx.User) - ctx.Flash.Error(ctx.Tr("setting.twofa_is_enrolled")) + ctx.Flash.Error(ctx.Tr("settings.twofa_is_enrolled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") return } - if err != nil && !models.IsErrTwoFactorNotEnrolled(err) { + if err != nil && !login.IsErrTwoFactorNotEnrolled(err) { ctx.ServerError("SettingsTwoFactor: GetTwoFactorByUID", err) return } @@ -172,14 +172,14 @@ func EnrollTwoFactorPost(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsSecurity"] = true - t, err := models.GetTwoFactorByUID(ctx.User.ID) + t, err := login.GetTwoFactorByUID(ctx.User.ID) if t != nil { // already enrolled - ctx.Flash.Error(ctx.Tr("setting.twofa_is_enrolled")) + ctx.Flash.Error(ctx.Tr("settings.twofa_is_enrolled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") return } - if err != nil && !models.IsErrTwoFactorNotEnrolled(err) { + if err != nil && !login.IsErrTwoFactorNotEnrolled(err) { ctx.ServerError("SettingsTwoFactor: Failed to check if already enrolled with GetTwoFactorByUID", err) return } @@ -209,7 +209,7 @@ func EnrollTwoFactorPost(ctx *context.Context) { return } - t = &models.TwoFactor{ + t = &login.TwoFactor{ UID: ctx.User.ID, } err = t.SetSecret(secret) @@ -238,7 +238,7 @@ func EnrollTwoFactorPost(ctx *context.Context) { log.Error("Unable to save changes to the session: %v", err) } - if err = models.NewTwoFactor(t); err != nil { + if err = login.NewTwoFactor(t); err != nil { // FIXME: We need to handle a unique constraint fail here it's entirely possible that another request has beaten us. // If there is a unique constraint fail we should just tolerate the error ctx.ServerError("SettingsTwoFactor: Failed to save two factor", err) diff --git a/routers/web/user/setting/security_u2f.go b/routers/web/user/setting/security_u2f.go index f9e35549fbfa3..d1d6d1e8cad89 100644 --- a/routers/web/user/setting/security_u2f.go +++ b/routers/web/user/setting/security_u2f.go @@ -8,7 +8,7 @@ import ( "errors" "net/http" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -34,7 +34,7 @@ func U2FRegister(ctx *context.Context) { ctx.ServerError("Unable to set session key for u2fChallenge", err) return } - regs, err := models.GetU2FRegistrationsByUID(ctx.User.ID) + regs, err := login.GetU2FRegistrationsByUID(ctx.User.ID) if err != nil { ctx.ServerError("GetU2FRegistrationsByUID", err) return @@ -78,7 +78,7 @@ func U2FRegisterPost(ctx *context.Context) { ctx.ServerError("u2f.Register", err) return } - if _, err = models.CreateRegistration(ctx.User, name, reg); err != nil { + if _, err = login.CreateRegistration(ctx.User.ID, name, reg); err != nil { ctx.ServerError("u2f.Register", err) return } @@ -88,9 +88,9 @@ func U2FRegisterPost(ctx *context.Context) { // U2FDelete deletes an security key by id func U2FDelete(ctx *context.Context) { form := web.GetForm(ctx).(*forms.U2FDeleteForm) - reg, err := models.GetU2FRegistrationByID(form.ID) + reg, err := login.GetU2FRegistrationByID(form.ID) if err != nil { - if models.IsErrU2FRegistrationNotExist(err) { + if login.IsErrU2FRegistrationNotExist(err) { ctx.Status(200) return } @@ -101,7 +101,7 @@ func U2FDelete(ctx *context.Context) { ctx.Status(401) return } - if err := models.DeleteRegistration(reg); err != nil { + if err := login.DeleteRegistration(reg); err != nil { ctx.ServerError("DeleteRegistration", err) return } diff --git a/routers/web/web.go b/routers/web/web.go index a88b66726a9b6..01d90d206fd3b 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -40,7 +40,6 @@ import ( _ "code.gitea.io/gitea/modules/session" "gitea.com/go-chi/captcha" - "gitea.com/go-chi/session" "github.com/NYTimes/gziphandler" "github.com/go-chi/chi/middleware" "github.com/go-chi/cors" @@ -72,7 +71,7 @@ func CorsHandler() func(next http.Handler) http.Handler { } // Routes returns all web routes -func Routes() *web.Route { +func Routes(sessioner func(http.Handler) http.Handler) *web.Route { routes := web.NewRoute() routes.Use(public.AssetsHandler(&public.Options{ @@ -81,17 +80,7 @@ func Routes() *web.Route { CorsHandler: CorsHandler(), })) - routes.Use(session.Sessioner(session.Options{ - Provider: setting.SessionConfig.Provider, - ProviderConfig: setting.SessionConfig.ProviderConfig, - CookieName: setting.SessionConfig.CookieName, - CookiePath: setting.SessionConfig.CookiePath, - Gclifetime: setting.SessionConfig.Gclifetime, - Maxlifetime: setting.SessionConfig.Maxlifetime, - Secure: setting.SessionConfig.Secure, - SameSite: setting.SessionConfig.SameSite, - Domain: setting.SessionConfig.Domain, - })) + routes.Use(sessioner) routes.Use(Recovery()) @@ -245,6 +234,9 @@ func RegisterRoutes(m *web.Route) { // for health check m.Get("/", Home) m.Get("/.well-known/openid-configuration", user.OIDCWellKnown) + if setting.Federation.Enabled { + m.Get("/.well-known/nodeinfo", NodeInfoLinks) + } m.Group("/explore", func() { m.Get("", func(ctx *context.Context) { ctx.Redirect(setting.AppSubURL + "/explore/repos") @@ -374,7 +366,7 @@ func RegisterRoutes(m *web.Route) { m.Get("/activate", user.Activate, reqSignIn) m.Post("/activate", user.ActivatePost, reqSignIn) m.Any("/activate_email", user.ActivateEmail) - m.Get("/avatar/{username}/{size}", user.Avatar) + m.Get("/avatar/{username}/{size}", user.AvatarByUserName) m.Get("/email2user", user.Email2User) m.Get("/recover_account", user.ResetPasswd) m.Post("/recover_account", user.ResetPasswdPost) @@ -1003,6 +995,9 @@ func RegisterRoutes(m *web.Route) { m.Get("/commit/{sha:([a-f0-9]{7,40})}.{ext:patch|diff}", repo.MustBeNotEmpty, reqRepoCodeReader, repo.RawDiff) }, ignSignIn, context.RepoAssignment, context.UnitTypes()) + + m.Post("/{username}/{reponame}/lastcommit/*", ignSignInAndCsrf, context.RepoAssignment, context.UnitTypes(), context.RepoRefByType(context.RepoRefCommit), reqRepoCodeReader, repo.LastCommit) + m.Group("/{username}/{reponame}", func() { m.Get("/stars", repo.Stars) m.Get("/watchers", repo.Watchers) diff --git a/services/archiver/archiver.go b/services/archiver/archiver.go index 7ae0a94d7ecac..d602b9ed7fd5f 100644 --- a/services/archiver/archiver.go +++ b/services/archiver/archiver.go @@ -14,6 +14,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" @@ -97,11 +98,11 @@ func (aReq *ArchiveRequest) GetArchiveName() string { } func doArchive(r *ArchiveRequest) (*models.RepoArchiver, error) { - ctx, commiter, err := models.TxDBContext() + ctx, committer, err := db.TxContext() if err != nil { return nil, err } - defer commiter.Close() + defer committer.Close() archiver, err := models.GetRepoArchiver(ctx, r.RepoID, r.Type, r.CommitID) if err != nil { @@ -135,9 +136,11 @@ func doArchive(r *ArchiveRequest) (*models.RepoArchiver, error) { if err == nil { if archiver.Status == models.RepoArchiverGenerating { archiver.Status = models.RepoArchiverReady - return archiver, models.UpdateRepoArchiverStatus(ctx, archiver) + if err = models.UpdateRepoArchiverStatus(ctx, archiver); err != nil { + return nil, err + } } - return archiver, nil + return archiver, committer.Commit() } if !errors.Is(err, os.ErrNotExist) { @@ -206,7 +209,7 @@ func doArchive(r *ArchiveRequest) (*models.RepoArchiver, error) { } } - return archiver, commiter.Commit() + return archiver, committer.Commit() } // ArchiveRepository satisfies the ArchiveRequest being passed in. Processing diff --git a/services/archiver/archiver_test.go b/services/archiver/archiver_test.go index 3f3f369987b5b..94b0423d9bae4 100644 --- a/services/archiver/archiver_test.go +++ b/services/archiver/archiver_test.go @@ -9,14 +9,14 @@ import ( "testing" "time" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/test" "github.com/stretchr/testify/assert" ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..")) + db.MainTest(m, filepath.Join("..", "..")) } func waitForCount(t *testing.T, num int) { @@ -24,7 +24,7 @@ func waitForCount(t *testing.T, num int) { } func TestArchive_Basic(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) ctx := test.MockContext(t, "user27/repo49") firstCommit, secondCommit := "51f84af23134", "aacbdfe9e1c4" diff --git a/services/attachment/attachment.go b/services/attachment/attachment.go index 4c356cd079109..7500a8ac3a650 100644 --- a/services/attachment/attachment.go +++ b/services/attachment/attachment.go @@ -6,10 +6,12 @@ package attachment import ( "bytes" + "context" "fmt" "io" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/upload" @@ -22,7 +24,7 @@ func NewAttachment(attach *models.Attachment, file io.Reader) (*models.Attachmen return nil, fmt.Errorf("attachment %s should belong to a repository", attach.Name) } - err := models.WithTx(func(ctx models.DBContext) error { + err := db.WithTx(func(ctx context.Context) error { attach.UUID = uuid.New().String() size, err := storage.Attachments.Save(attach.RelativePath(), file, -1) if err != nil { @@ -30,7 +32,7 @@ func NewAttachment(attach *models.Attachment, file io.Reader) (*models.Attachmen } attach.Size = size - return models.Insert(ctx, attach) + return db.Insert(ctx, attach) }) return attach, err diff --git a/services/attachment/attachment_test.go b/services/attachment/attachment_test.go index c11204b22109a..3e9e55a8f2fe1 100644 --- a/services/attachment/attachment_test.go +++ b/services/attachment/attachment_test.go @@ -10,18 +10,19 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..")) + db.MainTest(m, filepath.Join("..", "..")) } func TestUploadAttachment(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) fPath := "./attachment_test.go" f, err := os.Open(fPath) diff --git a/services/auth/basic.go b/services/auth/basic.go index 244f63d2f733a..0c400b6b5329a 100644 --- a/services/auth/basic.go +++ b/services/auth/basic.go @@ -107,7 +107,7 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore } log.Trace("Basic Authorization: Attempting SignIn for %s", uname) - u, err := UserSignIn(uname, passwd) + u, source, err := UserSignIn(uname, passwd) if err != nil { if !models.IsErrUserNotExist(err) { log.Error("UserSignIn: %v", err) @@ -115,6 +115,10 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore return nil } + if skipper, ok := source.Cfg.(LocalTwoFASkipper); ok && skipper.IsSkipLocalTwoFA() { + store.GetData()["SkipLocalTwoFA"] = true + } + log.Trace("Basic Authorization: Logged in user %-v", u) return u diff --git a/services/auth/group.go b/services/auth/group.go index fb885b818ab4d..c396ae046becb 100644 --- a/services/auth/group.go +++ b/services/auth/group.go @@ -8,6 +8,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) // Ensure the struct implements the interface. @@ -60,7 +61,7 @@ func (b *Group) Free() error { // Verify extracts and validates func (b *Group) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User { - if !models.HasEngine { + if !db.HasEngine { return nil } diff --git a/services/auth/interface.go b/services/auth/interface.go index 51c7043370bb1..a198fbe5b8479 100644 --- a/services/auth/interface.go +++ b/services/auth/interface.go @@ -54,6 +54,11 @@ type PasswordAuthenticator interface { Authenticate(user *models.User, login, password string) (*models.User, error) } +// LocalTwoFASkipper represents a source of authentication that can skip local 2fa +type LocalTwoFASkipper interface { + IsSkipLocalTwoFA() bool +} + // SynchronizableSource represents a source that can synchronize users type SynchronizableSource interface { Sync(ctx context.Context, updateExisting bool) error diff --git a/services/auth/login_source.go b/services/auth/login_source.go new file mode 100644 index 0000000000000..723dd2b1a5d0e --- /dev/null +++ b/services/auth/login_source.go @@ -0,0 +1,41 @@ +// Copyright 2021 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 auth + +import ( + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" +) + +// DeleteLoginSource deletes a LoginSource record in DB. +func DeleteLoginSource(source *login.Source) error { + count, err := db.GetEngine(db.DefaultContext).Count(&models.User{LoginSource: source.ID}) + if err != nil { + return err + } else if count > 0 { + return login.ErrSourceInUse{ + ID: source.ID, + } + } + + count, err = db.GetEngine(db.DefaultContext).Count(&models.ExternalLoginUser{LoginSourceID: source.ID}) + if err != nil { + return err + } else if count > 0 { + return login.ErrSourceInUse{ + ID: source.ID, + } + } + + if registerableSource, ok := source.Cfg.(login.RegisterableSource); ok { + if err := registerableSource.UnregisterSource(); err != nil { + return err + } + } + + _, err = db.GetEngine(db.DefaultContext).ID(source.ID).Delete(new(login.Source)) + return err +} diff --git a/services/auth/oauth2.go b/services/auth/oauth2.go index 665e5232ccbed..9b342f3458f60 100644 --- a/services/auth/oauth2.go +++ b/services/auth/oauth2.go @@ -11,6 +11,8 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/web/middleware" @@ -34,8 +36,8 @@ func CheckOAuthAccessToken(accessToken string) int64 { log.Trace("oauth2.ParseToken: %v", err) return 0 } - var grant *models.OAuth2Grant - if grant, err = models.GetOAuth2GrantByID(token.GrantID); err != nil || grant == nil { + var grant *login.OAuth2Grant + if grant, err = login.GetOAuth2GrantByID(token.GrantID); err != nil || grant == nil { return 0 } if token.Type != oauth2.TypeAccessToken { @@ -109,7 +111,7 @@ func (o *OAuth2) userIDFromToken(req *http.Request, store DataStore) int64 { // If verification is successful returns an existing user object. // Returns nil if verification fails. func (o *OAuth2) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User { - if !models.HasEngine { + if !db.HasEngine { return nil } diff --git a/services/auth/signin.go b/services/auth/signin.go index 2c4bf9b35b7e3..a7ad029456dfa 100644 --- a/services/auth/signin.go +++ b/services/auth/signin.go @@ -8,6 +8,8 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/log" // Register the sources @@ -20,24 +22,24 @@ import ( ) // UserSignIn validates user name and password. -func UserSignIn(username, password string) (*models.User, error) { +func UserSignIn(username, password string) (*models.User, *login.Source, error) { var user *models.User if strings.Contains(username, "@") { user = &models.User{Email: strings.ToLower(strings.TrimSpace(username))} // check same email - cnt, err := models.Count(user) + cnt, err := db.Count(user) if err != nil { - return nil, err + return nil, nil, err } if cnt > 1 { - return nil, models.ErrEmailAlreadyUsed{ + return nil, nil, models.ErrEmailAlreadyUsed{ Email: user.Email, } } } else { trimmedUsername := strings.TrimSpace(username) if len(trimmedUsername) == 0 { - return nil, models.ErrUserNotExist{Name: username} + return nil, nil, models.ErrUserNotExist{Name: username} } user = &models.User{LowerName: strings.ToLower(trimmedUsername)} @@ -45,41 +47,41 @@ func UserSignIn(username, password string) (*models.User, error) { hasUser, err := models.GetUser(user) if err != nil { - return nil, err + return nil, nil, err } if hasUser { - source, err := models.GetLoginSourceByID(user.LoginSource) + source, err := login.GetSourceByID(user.LoginSource) if err != nil { - return nil, err + return nil, nil, err } if !source.IsActive { - return nil, models.ErrLoginSourceNotActived + return nil, nil, models.ErrLoginSourceNotActived } authenticator, ok := source.Cfg.(PasswordAuthenticator) if !ok { - return nil, models.ErrUnsupportedLoginType + return nil, nil, models.ErrUnsupportedLoginType } user, err := authenticator.Authenticate(user, username, password) if err != nil { - return nil, err + return nil, nil, err } // WARN: DON'T check user.IsActive, that will be checked on reqSign so that // user could be hint to resend confirm email. if user.ProhibitLogin { - return nil, models.ErrUserProhibitLogin{UID: user.ID, Name: user.Name} + return nil, nil, models.ErrUserProhibitLogin{UID: user.ID, Name: user.Name} } - return user, nil + return user, source, nil } - sources, err := models.AllActiveLoginSources() + sources, err := login.AllActiveSources() if err != nil { - return nil, err + return nil, nil, err } for _, source := range sources { @@ -97,7 +99,7 @@ func UserSignIn(username, password string) (*models.User, error) { if err == nil { if !authUser.ProhibitLogin { - return authUser, nil + return authUser, source, nil } err = models.ErrUserProhibitLogin{UID: authUser.ID, Name: authUser.Name} } @@ -109,5 +111,5 @@ func UserSignIn(username, password string) (*models.User, error) { } } - return nil, models.ErrUserNotExist{Name: username} + return nil, nil, models.ErrUserNotExist{Name: username} } diff --git a/services/auth/source/db/assert_interface_test.go b/services/auth/source/db/assert_interface_test.go index 2e0fa9ba2247a..a8b137ec48176 100644 --- a/services/auth/source/db/assert_interface_test.go +++ b/services/auth/source/db/assert_interface_test.go @@ -5,7 +5,7 @@ package db_test import ( - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth/source/db" ) @@ -15,7 +15,7 @@ import ( type sourceInterface interface { auth.PasswordAuthenticator - models.LoginConfig + login.Config } var _ (sourceInterface) = &db.Source{} diff --git a/services/auth/source/db/source.go b/services/auth/source/db/source.go index 182c05f0dfcc5..2fedff3a7ea85 100644 --- a/services/auth/source/db/source.go +++ b/services/auth/source/db/source.go @@ -4,7 +4,10 @@ package db -import "code.gitea.io/gitea/models" +import ( + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" +) // Source is a password authentication service type Source struct{} @@ -26,6 +29,6 @@ func (source *Source) Authenticate(user *models.User, login, password string) (* } func init() { - models.RegisterLoginTypeConfig(models.LoginNoType, &Source{}) - models.RegisterLoginTypeConfig(models.LoginPlain, &Source{}) + login.RegisterTypeConfig(login.NoType, &Source{}) + login.RegisterTypeConfig(login.Plain, &Source{}) } diff --git a/services/auth/source/ldap/assert_interface_test.go b/services/auth/source/ldap/assert_interface_test.go index 4cf3eafe76358..c480119cd3fe6 100644 --- a/services/auth/source/ldap/assert_interface_test.go +++ b/services/auth/source/ldap/assert_interface_test.go @@ -5,7 +5,7 @@ package ldap_test import ( - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth/source/ldap" ) @@ -16,12 +16,13 @@ import ( type sourceInterface interface { auth.PasswordAuthenticator auth.SynchronizableSource - models.SSHKeyProvider - models.LoginConfig - models.SkipVerifiable - models.HasTLSer - models.UseTLSer - models.LoginSourceSettable + auth.LocalTwoFASkipper + login.SSHKeyProvider + login.Config + login.SkipVerifiable + login.HasTLSer + login.UseTLSer + login.SourceSettable } var _ (sourceInterface) = &ldap.Source{} diff --git a/services/auth/source/ldap/source.go b/services/auth/source/ldap/source.go index 79f118f784654..3e751f512b12e 100644 --- a/services/auth/source/ldap/source.go +++ b/services/auth/source/ldap/source.go @@ -8,6 +8,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/secret" "code.gitea.io/gitea/modules/setting" @@ -41,6 +42,7 @@ type Source struct { AttributeMail string // E-mail attribute AttributesInBind bool // fetch attributes in bind context (not user) AttributeSSHPublicKey string // LDAP SSH Public Key attribute + AttributeAvatar string SearchPageSize uint32 // Search with paging page size Filter string // Query filter to validate entry AdminFilter string // Query filter to check if user is admin @@ -52,9 +54,10 @@ type Source struct { GroupFilter string // Group Name Filter GroupMemberUID string // Group Attribute containing array of UserUID UserUID string // User Attribute listed in Group + SkipLocalTwoFA bool `json:",omitempty"` // Skip Local 2fa for users authenticated with this source // reference to the loginSource - loginSource *models.LoginSource + loginSource *login.Source } // FromDB fills up a LDAPConfig from serialized format. @@ -108,11 +111,11 @@ func (source *Source) ProvidesSSHKeys() bool { } // SetLoginSource sets the related LoginSource -func (source *Source) SetLoginSource(loginSource *models.LoginSource) { +func (source *Source) SetLoginSource(loginSource *login.Source) { source.loginSource = loginSource } func init() { - models.RegisterLoginTypeConfig(models.LoginLDAP, &Source{}) - models.RegisterLoginTypeConfig(models.LoginDLDAP, &Source{}) + login.RegisterTypeConfig(login.LDAP, &Source{}) + login.RegisterTypeConfig(login.DLDAP, &Source{}) } diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index ecc95fbd56eff..2719b5b7154f6 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -9,16 +9,17 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/services/mailer" ) // Authenticate queries if login/password is valid against the LDAP directory pool, // and create a local user if success when enabled. -func (source *Source) Authenticate(user *models.User, login, password string) (*models.User, error) { - sr := source.SearchEntry(login, password, source.loginSource.Type == models.LoginDLDAP) +func (source *Source) Authenticate(user *models.User, userName, password string) (*models.User, error) { + sr := source.SearchEntry(userName, password, source.loginSource.Type == login.DLDAP) if sr == nil { // User not in LDAP, do nothing - return nil, models.ErrUserNotExist{Name: login} + return nil, models.ErrUserNotExist{Name: userName} } isAttributeSSHPublicKeySet := len(strings.TrimSpace(source.AttributeSSHPublicKey)) > 0 @@ -64,7 +65,7 @@ func (source *Source) Authenticate(user *models.User, login, password string) (* // Fallback. if len(sr.Username) == 0 { - sr.Username = login + sr.Username = userName } if len(sr.Mail) == 0 { @@ -78,7 +79,7 @@ func (source *Source) Authenticate(user *models.User, login, password string) (* Email: sr.Mail, LoginType: source.loginSource.Type, LoginSource: source.loginSource.ID, - LoginName: login, + LoginName: userName, IsActive: true, IsAdmin: sr.IsAdmin, IsRestricted: sr.IsRestricted, @@ -95,5 +96,14 @@ func (source *Source) Authenticate(user *models.User, login, password string) (* err = models.RewriteAllPublicKeys() } + if err == nil && len(source.AttributeAvatar) > 0 { + _ = user.UploadAvatar(sr.Avatar) + } + return user, err } + +// IsSkipLocalTwoFA returns if this source should skip local 2fa for password authentication +func (source *Source) IsSkipLocalTwoFA() bool { + return source.SkipLocalTwoFA +} diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go index f2acbb0d4b894..1f1cca270d40a 100644 --- a/services/auth/source/ldap/source_search.go +++ b/services/auth/source/ldap/source_search.go @@ -26,6 +26,8 @@ type SearchResult struct { SSHPublicKey []string // SSH Public Key IsAdmin bool // if user is administrator IsRestricted bool // if user is restricted + LowerName string // Lowername + Avatar []byte } func (ls *Source) sanitizedUserQuery(username string) (string, bool) { @@ -156,7 +158,7 @@ func checkAdmin(l *ldap.Conn, ls *Source, userDN string) bool { sr, err := l.Search(search) if err != nil { - log.Error("LDAP Admin Search failed unexpectedly! (%v)", err) + log.Error("LDAP Admin Search with filter %s for %s failed unexpectedly! (%v)", ls.AdminFilter, userDN, err) } else if len(sr.Entries) < 1 { log.Trace("LDAP Admin Search found no matching entries.") } else { @@ -181,7 +183,7 @@ func checkRestricted(l *ldap.Conn, ls *Source, userDN string) bool { sr, err := l.Search(search) if err != nil { - log.Error("LDAP Restrictred Search failed unexpectedly! (%v)", err) + log.Error("LDAP Restrictred Search with filter %s for %s failed unexpectedly! (%v)", ls.RestrictedFilter, userDN, err) } else if len(sr.Entries) < 1 { log.Trace("LDAP Restricted Search found no matching entries.") } else { @@ -265,7 +267,8 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul return nil } - var isAttributeSSHPublicKeySet = len(strings.TrimSpace(ls.AttributeSSHPublicKey)) > 0 + isAttributeSSHPublicKeySet := len(strings.TrimSpace(ls.AttributeSSHPublicKey)) > 0 + isAtributeAvatarSet := len(strings.TrimSpace(ls.AttributeAvatar)) > 0 attribs := []string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail} if len(strings.TrimSpace(ls.UserUID)) > 0 { @@ -274,8 +277,11 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul if isAttributeSSHPublicKeySet { attribs = append(attribs, ls.AttributeSSHPublicKey) } + if isAtributeAvatarSet { + attribs = append(attribs, ls.AttributeAvatar) + } - log.Trace("Fetching attributes '%v', '%v', '%v', '%v', '%v', '%v' with filter '%s' and base '%s'", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, ls.AttributeSSHPublicKey, ls.UserUID, userFilter, userDN) + log.Trace("Fetching attributes '%v', '%v', '%v', '%v', '%v', '%v', '%v' with filter '%s' and base '%s'", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, ls.AttributeSSHPublicKey, ls.AttributeAvatar, ls.UserUID, userFilter, userDN) search := ldap.NewSearchRequest( userDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, userFilter, attribs, nil) @@ -295,6 +301,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul } var sshPublicKey []string + var Avatar []byte username := sr.Entries[0].GetAttributeValue(ls.AttributeUsername) firstname := sr.Entries[0].GetAttributeValue(ls.AttributeName) @@ -362,7 +369,12 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul } } + if isAtributeAvatarSet { + Avatar = sr.Entries[0].GetRawAttributeValue(ls.AttributeAvatar) + } + return &SearchResult{ + LowerName: strings.ToLower(username), Username: username, Name: firstname, Surname: surname, @@ -370,6 +382,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul SSHPublicKey: sshPublicKey, IsAdmin: isAdmin, IsRestricted: isRestricted, + Avatar: Avatar, } } @@ -401,14 +414,18 @@ func (ls *Source) SearchEntries() ([]*SearchResult, error) { userFilter := fmt.Sprintf(ls.Filter, "*") - var isAttributeSSHPublicKeySet = len(strings.TrimSpace(ls.AttributeSSHPublicKey)) > 0 + isAttributeSSHPublicKeySet := len(strings.TrimSpace(ls.AttributeSSHPublicKey)) > 0 + isAtributeAvatarSet := len(strings.TrimSpace(ls.AttributeAvatar)) > 0 attribs := []string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail} if isAttributeSSHPublicKeySet { attribs = append(attribs, ls.AttributeSSHPublicKey) } + if isAtributeAvatarSet { + attribs = append(attribs, ls.AttributeAvatar) + } - log.Trace("Fetching attributes '%v', '%v', '%v', '%v', '%v' with filter %s and base %s", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, ls.AttributeSSHPublicKey, userFilter, ls.UserBase) + log.Trace("Fetching attributes '%v', '%v', '%v', '%v', '%v', '%v' with filter %s and base %s", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, ls.AttributeSSHPublicKey, ls.AttributeAvatar, userFilter, ls.UserBase) search := ldap.NewSearchRequest( ls.UserBase, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, userFilter, attribs, nil) @@ -440,6 +457,10 @@ func (ls *Source) SearchEntries() ([]*SearchResult, error) { if isAttributeSSHPublicKeySet { result[i].SSHPublicKey = v.GetAttributeValues(ls.AttributeSSHPublicKey) } + if isAtributeAvatarSet { + result[i].Avatar = v.GetRawAttributeValue(ls.AttributeAvatar) + } + result[i].LowerName = strings.ToLower(result[i].Username) } return result, nil diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index 7e4088e571738..2df97aabd38c3 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -7,6 +7,7 @@ package ldap import ( "context" "fmt" + "sort" "strings" "code.gitea.io/gitea/models" @@ -17,7 +18,7 @@ import ( func (source *Source) Sync(ctx context.Context, updateExisting bool) error { log.Trace("Doing: SyncExternalUsers[%s]", source.loginSource.Name) - var existingUsers []int64 + var existingUsers []int isAttributeSSHPublicKeySet := len(strings.TrimSpace(source.AttributeSSHPublicKey)) > 0 var sshKeysNeedUpdate bool @@ -34,6 +35,10 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { default: } + sort.Slice(users, func(i, j int) bool { + return users[i].LowerName < users[j].LowerName + }) + sr, err := source.SearchEntries() if err != nil { log.Error("SyncExternalUsers LDAP source failure [%s], skipped", source.loginSource.Name) @@ -48,6 +53,12 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { log.Warn("LDAP search found no entries but did not report an error. All users will be deactivated as per settings") } + sort.Slice(sr, func(i, j int) bool { + return sr[i].LowerName < sr[j].LowerName + }) + + userPos := 0 + for _, su := range sr { select { case <-ctx.Done(): @@ -71,12 +82,12 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { } var usr *models.User - // Search for existing user - for _, du := range users { - if du.LowerName == strings.ToLower(su.Username) { - usr = du - break - } + for userPos < len(users) && users[userPos].LowerName < su.LowerName { + userPos++ + } + if userPos < len(users) && users[userPos].LowerName == su.LowerName { + usr = users[userPos] + existingUsers = append(existingUsers, userPos) } fullName := composeFullName(su.Name, su.Surname, su.Username) @@ -85,7 +96,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { log.Trace("SyncExternalUsers[%s]: Creating user %s", source.loginSource.Name, su.Username) usr = &models.User{ - LowerName: strings.ToLower(su.Username), + LowerName: su.LowerName, Name: su.Username, FullName: fullName, LoginType: source.loginSource.Type, @@ -101,15 +112,19 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { if err != nil { log.Error("SyncExternalUsers[%s]: Error creating user %s: %v", source.loginSource.Name, su.Username, err) - } else if isAttributeSSHPublicKeySet { + } + + if err == nil && isAttributeSSHPublicKeySet { log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", source.loginSource.Name, usr.Name) if models.AddPublicKeysBySource(usr, source.loginSource, su.SSHPublicKey) { sshKeysNeedUpdate = true } } - } else if updateExisting { - existingUsers = append(existingUsers, usr.ID) + if err == nil && len(source.AttributeAvatar) > 0 { + _ = usr.UploadAvatar(su.Avatar) + } + } else if updateExisting { // Synchronize SSH Public Key if that attribute is set if isAttributeSSHPublicKeySet && models.SynchronizePublicKeys(usr, source.loginSource, su.SSHPublicKey) { sshKeysNeedUpdate = true @@ -141,6 +156,13 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { log.Error("SyncExternalUsers[%s]: Error updating user %s: %v", source.loginSource.Name, usr.Name, err) } } + + if usr.IsUploadAvatarChanged(su.Avatar) { + if err == nil && len(source.AttributeAvatar) > 0 { + _ = usr.UploadAvatar(su.Avatar) + } + + } } } @@ -161,15 +183,12 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { // Deactivate users not present in LDAP if updateExisting { - for _, usr := range users { - found := false - for _, uid := range existingUsers { - if usr.ID == uid { - found = true - break - } + existPos := 0 + for i, usr := range users { + for existPos < len(existingUsers) && i > existingUsers[existPos] { + existPos++ } - if !found { + if usr.IsActive && (existPos >= len(existingUsers) || i < existingUsers[existPos]) { log.Trace("SyncExternalUsers[%s]: Deactivating user %s", source.loginSource.Name, usr.Name) usr.IsActive = false diff --git a/services/auth/source/oauth2/assert_interface_test.go b/services/auth/source/oauth2/assert_interface_test.go index 4157427ff2f76..0a1986a3b29bc 100644 --- a/services/auth/source/oauth2/assert_interface_test.go +++ b/services/auth/source/oauth2/assert_interface_test.go @@ -5,7 +5,7 @@ package oauth2_test import ( - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth/source/oauth2" ) @@ -14,9 +14,9 @@ import ( // It tightly binds the interfaces and implementation without breaking go import cycles type sourceInterface interface { - models.LoginConfig - models.LoginSourceSettable - models.RegisterableSource + login.Config + login.SourceSettable + login.RegisterableSource auth.PasswordAuthenticator } diff --git a/services/auth/source/oauth2/init.go b/services/auth/source/oauth2/init.go index 64328aa381789..343b24cf6f587 100644 --- a/services/auth/source/oauth2/init.go +++ b/services/auth/source/oauth2/init.go @@ -8,7 +8,8 @@ import ( "net/http" "sync" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -33,7 +34,7 @@ func Init() error { return err } - store, err := models.CreateStore(SessionTableName, UsersStoreKey) + store, err := db.CreateStore(SessionTableName, UsersStoreKey) if err != nil { return err } @@ -73,7 +74,7 @@ func ResetOAuth2() error { // initOAuth2LoginSources is used to load and register all active OAuth2 providers func initOAuth2LoginSources() error { - loginSources, _ := models.GetActiveOAuth2ProviderLoginSources() + loginSources, _ := login.GetActiveOAuth2ProviderLoginSources() for _, source := range loginSources { oauth2Source, ok := source.Cfg.(*Source) if !ok { diff --git a/services/auth/source/oauth2/jwtsigningkey.go b/services/auth/source/oauth2/jwtsigningkey.go index 720a9a33f7aa4..3102be5f143a2 100644 --- a/services/auth/source/oauth2/jwtsigningkey.go +++ b/services/auth/source/oauth2/jwtsigningkey.go @@ -15,7 +15,6 @@ import ( "encoding/base64" "encoding/pem" "fmt" - "io/ioutil" "math/big" "os" "path/filepath" @@ -429,7 +428,7 @@ func loadOrCreateAsymmetricKey() (interface{}, error) { } } - bytes, err := ioutil.ReadFile(keyPath) + bytes, err := os.ReadFile(keyPath) if err != nil { return nil, err } diff --git a/services/auth/source/oauth2/providers.go b/services/auth/source/oauth2/providers.go index 2196e304928e8..0fd57a8dbd5a0 100644 --- a/services/auth/source/oauth2/providers.go +++ b/services/auth/source/oauth2/providers.go @@ -9,6 +9,7 @@ import ( "sort" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -87,7 +88,7 @@ func GetOAuth2Providers() []Provider { func GetActiveOAuth2Providers() ([]string, map[string]Provider, error) { // Maybe also separate used and unused providers so we can force the registration of only 1 active provider for each type - loginSources, err := models.GetActiveOAuth2ProviderLoginSources() + loginSources, err := login.GetActiveOAuth2ProviderLoginSources() if err != nil { return nil, nil, err } diff --git a/services/auth/source/oauth2/source.go b/services/auth/source/oauth2/source.go index 40d8973b4b2ce..60845e3b0f83e 100644 --- a/services/auth/source/oauth2/source.go +++ b/services/auth/source/oauth2/source.go @@ -6,6 +6,7 @@ package oauth2 import ( "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/json" ) @@ -24,9 +25,10 @@ type Source struct { OpenIDConnectAutoDiscoveryURL string CustomURLMapping *CustomURLMapping IconURL string + SkipLocalTwoFA bool `json:",omitempty"` // reference to the loginSource - loginSource *models.LoginSource + loginSource *login.Source } // FromDB fills up an OAuth2Config from serialized format. @@ -40,10 +42,10 @@ func (source *Source) ToDB() ([]byte, error) { } // SetLoginSource sets the related LoginSource -func (source *Source) SetLoginSource(loginSource *models.LoginSource) { +func (source *Source) SetLoginSource(loginSource *login.Source) { source.loginSource = loginSource } func init() { - models.RegisterLoginTypeConfig(models.LoginOAuth2, &Source{}) + login.RegisterTypeConfig(login.OAuth2, &Source{}) } diff --git a/services/auth/source/oauth2/source_authenticate.go b/services/auth/source/oauth2/source_authenticate.go index 2e39f245dff07..be2ff05356cd4 100644 --- a/services/auth/source/oauth2/source_authenticate.go +++ b/services/auth/source/oauth2/source_authenticate.go @@ -13,3 +13,6 @@ import ( func (source *Source) Authenticate(user *models.User, login, password string) (*models.User, error) { return db.Authenticate(user, login, password) } + +// NB: Oauth2 does not implement LocalTwoFASkipper for password authentication +// as its password authentication drops to db authentication diff --git a/services/auth/source/pam/assert_interface_test.go b/services/auth/source/pam/assert_interface_test.go index a0bebdf9c6799..a151c2f52e6e6 100644 --- a/services/auth/source/pam/assert_interface_test.go +++ b/services/auth/source/pam/assert_interface_test.go @@ -5,7 +5,7 @@ package pam_test import ( - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth/source/pam" ) @@ -15,8 +15,8 @@ import ( type sourceInterface interface { auth.PasswordAuthenticator - models.LoginConfig - models.LoginSourceSettable + login.Config + login.SourceSettable } var _ (sourceInterface) = &pam.Source{} diff --git a/services/auth/source/pam/source.go b/services/auth/source/pam/source.go index 75aa99e45fd48..73850cd9a280a 100644 --- a/services/auth/source/pam/source.go +++ b/services/auth/source/pam/source.go @@ -6,6 +6,7 @@ package pam import ( "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/json" ) @@ -18,11 +19,12 @@ import ( // Source holds configuration for the PAM login source. type Source struct { - ServiceName string // pam service (e.g. system-auth) - EmailDomain string + ServiceName string // pam service (e.g. system-auth) + EmailDomain string + SkipLocalTwoFA bool `json:",omitempty"` // Skip Local 2fa for users authenticated with this source // reference to the loginSource - loginSource *models.LoginSource + loginSource *login.Source } // FromDB fills up a PAMConfig from serialized format. @@ -36,10 +38,10 @@ func (source *Source) ToDB() ([]byte, error) { } // SetLoginSource sets the related LoginSource -func (source *Source) SetLoginSource(loginSource *models.LoginSource) { +func (source *Source) SetLoginSource(loginSource *login.Source) { source.loginSource = loginSource } func init() { - models.RegisterLoginTypeConfig(models.LoginPAM, &Source{}) + login.RegisterTypeConfig(login.PAM, &Source{}) } diff --git a/services/auth/source/pam/source_authenticate.go b/services/auth/source/pam/source_authenticate.go index 8241aed7256f4..cb5ffc2861f33 100644 --- a/services/auth/source/pam/source_authenticate.go +++ b/services/auth/source/pam/source_authenticate.go @@ -9,6 +9,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/auth/pam" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/mailer" @@ -18,11 +19,11 @@ import ( // Authenticate queries if login/password is valid against the PAM, // and create a local user if success when enabled. -func (source *Source) Authenticate(user *models.User, login, password string) (*models.User, error) { - pamLogin, err := pam.Auth(source.ServiceName, login, password) +func (source *Source) Authenticate(user *models.User, userName, password string) (*models.User, error) { + pamLogin, err := pam.Auth(source.ServiceName, userName, password) if err != nil { if strings.Contains(err.Error(), "Authentication failure") { - return nil, models.ErrUserNotExist{Name: login} + return nil, models.ErrUserNotExist{Name: userName} } return nil, err } @@ -54,9 +55,9 @@ func (source *Source) Authenticate(user *models.User, login, password string) (* Name: username, Email: email, Passwd: password, - LoginType: models.LoginPAM, + LoginType: login.PAM, LoginSource: source.loginSource.ID, - LoginName: login, // This is what the user typed in + LoginName: userName, // This is what the user typed in IsActive: true, } @@ -68,3 +69,8 @@ func (source *Source) Authenticate(user *models.User, login, password string) (* return user, nil } + +// IsSkipLocalTwoFA returns if this source should skip local 2fa for password authentication +func (source *Source) IsSkipLocalTwoFA() bool { + return source.SkipLocalTwoFA +} diff --git a/services/auth/source/smtp/assert_interface_test.go b/services/auth/source/smtp/assert_interface_test.go index bc2042e069965..d1c982472fc63 100644 --- a/services/auth/source/smtp/assert_interface_test.go +++ b/services/auth/source/smtp/assert_interface_test.go @@ -5,7 +5,7 @@ package smtp_test import ( - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth/source/smtp" ) @@ -15,11 +15,11 @@ import ( type sourceInterface interface { auth.PasswordAuthenticator - models.LoginConfig - models.SkipVerifiable - models.HasTLSer - models.UseTLSer - models.LoginSourceSettable + login.Config + login.SkipVerifiable + login.HasTLSer + login.UseTLSer + login.SourceSettable } var _ (sourceInterface) = &smtp.Source{} diff --git a/services/auth/source/smtp/source.go b/services/auth/source/smtp/source.go index 39c9851ede233..52e2505670b5f 100644 --- a/services/auth/source/smtp/source.go +++ b/services/auth/source/smtp/source.go @@ -6,6 +6,7 @@ package smtp import ( "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/json" ) @@ -26,9 +27,10 @@ type Source struct { SkipVerify bool HeloHostname string DisableHelo bool + SkipLocalTwoFA bool `json:",omitempty"` // reference to the loginSource - loginSource *models.LoginSource + loginSource *login.Source } // FromDB fills up an SMTPConfig from serialized format. @@ -57,10 +59,10 @@ func (source *Source) UseTLS() bool { } // SetLoginSource sets the related LoginSource -func (source *Source) SetLoginSource(loginSource *models.LoginSource) { +func (source *Source) SetLoginSource(loginSource *login.Source) { source.loginSource = loginSource } func init() { - models.RegisterLoginTypeConfig(models.LoginSMTP, &Source{}) + login.RegisterTypeConfig(login.SMTP, &Source{}) } diff --git a/services/auth/source/smtp/source_authenticate.go b/services/auth/source/smtp/source_authenticate.go index cff64c69d2f99..f51c884c3a40b 100644 --- a/services/auth/source/smtp/source_authenticate.go +++ b/services/auth/source/smtp/source_authenticate.go @@ -11,31 +11,32 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/mailer" ) // Authenticate queries if the provided login/password is authenticates against the SMTP server // Users will be autoregistered as required -func (source *Source) Authenticate(user *models.User, login, password string) (*models.User, error) { +func (source *Source) Authenticate(user *models.User, userName, password string) (*models.User, error) { // Verify allowed domains. if len(source.AllowedDomains) > 0 { - idx := strings.Index(login, "@") + idx := strings.Index(userName, "@") if idx == -1 { - return nil, models.ErrUserNotExist{Name: login} - } else if !util.IsStringInSlice(login[idx+1:], strings.Split(source.AllowedDomains, ","), true) { - return nil, models.ErrUserNotExist{Name: login} + return nil, models.ErrUserNotExist{Name: userName} + } else if !util.IsStringInSlice(userName[idx+1:], strings.Split(source.AllowedDomains, ","), true) { + return nil, models.ErrUserNotExist{Name: userName} } } var auth smtp.Auth switch source.Auth { case PlainAuthentication: - auth = smtp.PlainAuth("", login, password, source.Host) + auth = smtp.PlainAuth("", userName, password, source.Host) case LoginAuthentication: - auth = &loginAuthenticator{login, password} + auth = &loginAuthenticator{userName, password} case CRAMMD5Authentication: - auth = smtp.CRAMMD5Auth(login, password) + auth = smtp.CRAMMD5Auth(userName, password) default: return nil, errors.New("unsupported SMTP auth type") } @@ -46,11 +47,11 @@ func (source *Source) Authenticate(user *models.User, login, password string) (* tperr, ok := err.(*textproto.Error) if (ok && tperr.Code == 535) || strings.Contains(err.Error(), "Username and Password not accepted") { - return nil, models.ErrUserNotExist{Name: login} + return nil, models.ErrUserNotExist{Name: userName} } if (ok && tperr.Code == 534) || strings.Contains(err.Error(), "Application-specific password required") { - return nil, models.ErrUserNotExist{Name: login} + return nil, models.ErrUserNotExist{Name: userName} } return nil, err } @@ -59,20 +60,20 @@ func (source *Source) Authenticate(user *models.User, login, password string) (* return user, nil } - username := login - idx := strings.Index(login, "@") + username := userName + idx := strings.Index(userName, "@") if idx > -1 { - username = login[:idx] + username = userName[:idx] } user = &models.User{ LowerName: strings.ToLower(username), Name: strings.ToLower(username), - Email: login, + Email: userName, Passwd: password, - LoginType: models.LoginSMTP, + LoginType: login.SMTP, LoginSource: source.loginSource.ID, - LoginName: login, + LoginName: userName, IsActive: true, } @@ -84,3 +85,8 @@ func (source *Source) Authenticate(user *models.User, login, password string) (* return user, nil } + +// IsSkipLocalTwoFA returns if this source should skip local 2fa for password authentication +func (source *Source) IsSkipLocalTwoFA() bool { + return source.SkipLocalTwoFA +} diff --git a/services/auth/source/sspi/assert_interface_test.go b/services/auth/source/sspi/assert_interface_test.go index 605a6ec6c541a..1efa69c05ba64 100644 --- a/services/auth/source/sspi/assert_interface_test.go +++ b/services/auth/source/sspi/assert_interface_test.go @@ -5,7 +5,7 @@ package sspi_test import ( - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/services/auth/source/sspi" ) @@ -13,7 +13,7 @@ import ( // It tightly binds the interfaces and implementation without breaking go import cycles type sourceInterface interface { - models.LoginConfig + login.Config } var _ (sourceInterface) = &sspi.Source{} diff --git a/services/auth/source/sspi/source.go b/services/auth/source/sspi/source.go index 58cb10de1df91..68fd6a6079485 100644 --- a/services/auth/source/sspi/source.go +++ b/services/auth/source/sspi/source.go @@ -6,6 +6,7 @@ package sspi import ( "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/json" ) @@ -36,5 +37,5 @@ func (cfg *Source) ToDB() ([]byte, error) { } func init() { - models.RegisterLoginTypeConfig(models.LoginSSPI, &Source{}) + login.RegisterTypeConfig(login.SSPI, &Source{}) } diff --git a/services/auth/sspi_windows.go b/services/auth/sspi_windows.go index d7e0f55242aa7..346e9439884f9 100644 --- a/services/auth/sspi_windows.go +++ b/services/auth/sspi_windows.go @@ -10,6 +10,8 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/avatars" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -152,7 +154,7 @@ func (s *SSPI) Verify(req *http.Request, w http.ResponseWriter, store DataStore, // getConfig retrieves the SSPI configuration from login sources func (s *SSPI) getConfig() (*sspi.Source, error) { - sources, err := models.ActiveLoginSources(models.LoginSSPI) + sources, err := login.ActiveSources(login.SSPI) if err != nil { return nil, err } @@ -192,7 +194,7 @@ func (s *SSPI) newUser(username string, cfg *sspi.Source) (*models.User, error) IsActive: cfg.AutoActivateUsers, Language: cfg.DefaultLanguage, UseCustomAvatar: true, - Avatar: models.DefaultAvatarLink(), + Avatar: avatars.DefaultAvatarLink(), EmailNotificationsPreference: models.EmailNotificationsDisabled, } if err := models.CreateUser(user); err != nil { @@ -248,7 +250,7 @@ func sanitizeUsername(username string, cfg *sspi.Source) string { // fails (or if negotiation should continue), which would prevent other authentication methods // to execute at all. func specialInit() { - if models.IsSSPIEnabled() { + if login.IsSSPIEnabled() { Register(&SSPI{}) } } diff --git a/services/auth/sync.go b/services/auth/sync.go index a34b4d1d2694f..6d69650e5baa2 100644 --- a/services/auth/sync.go +++ b/services/auth/sync.go @@ -8,6 +8,7 @@ import ( "context" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/log" ) @@ -15,7 +16,7 @@ import ( func SyncExternalUsers(ctx context.Context, updateExisting bool) error { log.Trace("Doing: SyncExternalUsers") - ls, err := models.LoginSources() + ls, err := login.Sources() if err != nil { log.Error("SyncExternalUsers: %v", err) return err diff --git a/services/comments/comments.go b/services/comments/comments.go index f8bdc8153b474..d65c66aef26bd 100644 --- a/services/comments/comments.go +++ b/services/comments/comments.go @@ -6,6 +6,7 @@ package comments import ( "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/notification" ) @@ -22,7 +23,7 @@ func CreateIssueComment(doer *models.User, repo *models.Repository, issue *model if err != nil { return nil, err } - mentions, err := issue.FindAndUpdateIssueMentions(models.DefaultDBContext(), doer, comment.Content) + mentions, err := issue.FindAndUpdateIssueMentions(db.DefaultContext, doer, comment.Content) if err != nil { return nil, err } diff --git a/services/externalaccount/user.go b/services/externalaccount/user.go index 45773fdb127d2..e43b3ca7c5f5b 100644 --- a/services/externalaccount/user.go +++ b/services/externalaccount/user.go @@ -8,6 +8,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/modules/structs" "github.com/markbates/goth" @@ -15,7 +16,7 @@ import ( // LinkAccountToUser link the gothUser to the user func LinkAccountToUser(user *models.User, gothUser goth.User) error { - loginSource, err := models.GetActiveOAuth2LoginSourceByName(gothUser.Provider) + loginSource, err := login.GetActiveOAuth2LoginSourceByName(gothUser.Provider) if err != nil { return err } diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go index b45ea6ea124f7..2c6966d266871 100644 --- a/services/forms/auth_form.go +++ b/services/forms/auth_form.go @@ -29,6 +29,7 @@ type AuthenticationForm struct { AttributeSurname string AttributeMail string AttributeSSHPublicKey string + AttributeAvatar string AttributesInBind bool UsePagedSearch bool SearchPageSize int @@ -66,6 +67,7 @@ type AuthenticationForm struct { Oauth2EmailURL string Oauth2IconURL string Oauth2Tenant string + SkipLocalTwoFA bool SSPIAutoCreateUsers bool SSPIAutoActivateUsers bool SSPIStripDomainNames bool diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index c1c146f234668..7c61be5e2221d 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -199,6 +199,7 @@ type ProtectBranchForm struct { DismissStaleApprovals bool RequireSignedCommits bool ProtectedFilePatterns string + UnprotectedFilePatterns string } // Validate validates the fields @@ -498,6 +499,7 @@ type UserCreateProjectForm struct { type EditProjectBoardForm struct { Title string `binding:"Required;MaxSize(100)"` Sorting int8 + Color string `binding:"MaxSize(7)"` } // _____ .__.__ __ diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 65d0dab4caa33..ef66675c5bb12 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -13,7 +13,6 @@ import ( "html" "html/template" "io" - "io/ioutil" "net/url" "os" "os/exec" @@ -23,6 +22,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/analyze" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" @@ -720,7 +720,7 @@ parsingLoop: // TODO: Handle skipping first n files if len(diff.Files) >= maxFiles { diff.IsIncomplete = true - _, err := io.Copy(ioutil.Discard, reader) + _, err := io.Copy(io.Discard, reader) if err != nil { // By the definition of io.Copy this never returns io.EOF return diff, fmt.Errorf("Copy: %v", err) @@ -1122,7 +1122,7 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio oid := strings.TrimPrefix(line[1:], lfs.MetaFileOidPrefix) if len(oid) == 64 { m := &models.LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}} - count, err := models.Count(m) + count, err := db.Count(m) if err == nil && count > 0 { curFile.IsBin = true @@ -1217,7 +1217,7 @@ func readFileName(rd *strings.Reader) (string, bool) { // GetDiffRangeWithWhitespaceBehavior builds a Diff between two commits of a repository. // Passing the empty string as beforeCommitID returns a diff from the parent commit. // The whitespaceBehavior is either an empty string or a git flag -func GetDiffRangeWithWhitespaceBehavior(gitRepo *git.Repository, beforeCommitID, afterCommitID string, maxLines, maxLineCharacters, maxFiles int, whitespaceBehavior string) (*Diff, error) { +func GetDiffRangeWithWhitespaceBehavior(gitRepo *git.Repository, beforeCommitID, afterCommitID string, maxLines, maxLineCharacters, maxFiles int, whitespaceBehavior string, directComparison bool) (*Diff, error) { repoPath := gitRepo.Path commit, err := gitRepo.GetCommit(afterCommitID) @@ -1279,7 +1279,7 @@ func GetDiffRangeWithWhitespaceBehavior(gitRepo *git.Repository, beforeCommitID, indexFilename, deleteTemporaryFile, err := gitRepo.ReadTreeToTemporaryIndex(afterCommitID) if err == nil { defer deleteTemporaryFile() - workdir, err := ioutil.TempDir("", "empty-work-dir") + workdir, err := os.MkdirTemp("", "empty-work-dir") if err != nil { log.Error("Unable to create temporary directory: %v", err) return nil, err @@ -1357,7 +1357,12 @@ func GetDiffRangeWithWhitespaceBehavior(gitRepo *git.Repository, beforeCommitID, return nil, fmt.Errorf("Wait: %v", err) } - shortstatArgs := []string{beforeCommitID + "..." + afterCommitID} + separator := "..." + if directComparison { + separator = ".." + } + + shortstatArgs := []string{beforeCommitID + separator + afterCommitID} if len(beforeCommitID) == 0 || beforeCommitID == git.EmptySHA { shortstatArgs = []string{git.EmptyTreeSHA, afterCommitID} } @@ -1377,8 +1382,8 @@ func GetDiffRangeWithWhitespaceBehavior(gitRepo *git.Repository, beforeCommitID, // GetDiffCommitWithWhitespaceBehavior builds a Diff representing the given commitID. // The whitespaceBehavior is either an empty string or a git flag -func GetDiffCommitWithWhitespaceBehavior(gitRepo *git.Repository, commitID string, maxLines, maxLineCharacters, maxFiles int, whitespaceBehavior string) (*Diff, error) { - return GetDiffRangeWithWhitespaceBehavior(gitRepo, "", commitID, maxLines, maxLineCharacters, maxFiles, whitespaceBehavior) +func GetDiffCommitWithWhitespaceBehavior(gitRepo *git.Repository, commitID string, maxLines, maxLineCharacters, maxFiles int, whitespaceBehavior string, directComparison bool) (*Diff, error) { + return GetDiffRangeWithWhitespaceBehavior(gitRepo, "", commitID, maxLines, maxLineCharacters, maxFiles, whitespaceBehavior, directComparison) } // CommentAsDiff returns c.Patch as *Diff diff --git a/services/gitdiff/gitdiff_test.go b/services/gitdiff/gitdiff_test.go index 57364ace3e5a8..0c216ccb54bbc 100644 --- a/services/gitdiff/gitdiff_test.go +++ b/services/gitdiff/gitdiff_test.go @@ -13,6 +13,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/highlight" "code.gitea.io/gitea/modules/json" @@ -492,10 +493,10 @@ func setupDefaultDiff() *Diff { } } func TestDiff_LoadComments(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue) - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) + issue := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) diff := setupDefaultDiff() assert.NoError(t, diff.LoadComments(issue, user)) assert.Len(t, diff.Files[0].Sections[0].Lines[0].Comments, 2) @@ -522,7 +523,7 @@ func TestGetDiffRangeWithWhitespaceBehavior(t *testing.T) { defer gitRepo.Close() for _, behavior := range []string{"-w", "--ignore-space-at-eol", "-b", ""} { diffs, err := GetDiffRangeWithWhitespaceBehavior(gitRepo, "559c156f8e0178b71cb44355428f24001b08fc68", "bd7063cc7c04689c4d082183d32a604ed27a24f9", - setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffFiles, behavior) + setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffFiles, behavior, false) assert.NoError(t, err, fmt.Sprintf("Error when diff with %s", behavior)) for _, f := range diffs.Files { assert.True(t, len(f.Sections) > 0, fmt.Sprintf("%s should have sections", f.Name)) diff --git a/services/gitdiff/main_test.go b/services/gitdiff/main_test.go index 5ed3c75b70537..1b83cbd684a06 100644 --- a/services/gitdiff/main_test.go +++ b/services/gitdiff/main_test.go @@ -8,9 +8,9 @@ import ( "path/filepath" "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..")) + db.MainTest(m, filepath.Join("..", "..")) } diff --git a/services/issue/assignee_test.go b/services/issue/assignee_test.go index 2d96368ec705f..5684ed6d89975 100644 --- a/services/issue/assignee_test.go +++ b/services/issue/assignee_test.go @@ -8,11 +8,12 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) func TestDeleteNotPassedAssignee(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) // Fake issue with assignees issue, err := models.GetIssueWithAttrsByID(1) diff --git a/services/issue/issue.go b/services/issue/issue.go index 90f689a55cc3e..e3571bd396f69 100644 --- a/services/issue/issue.go +++ b/services/issue/issue.go @@ -6,6 +6,7 @@ package issue import ( "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/util" @@ -23,7 +24,7 @@ func NewIssue(repo *models.Repository, issue *models.Issue, labelIDs []int64, uu } } - mentions, err := issue.FindAndUpdateIssueMentions(models.DefaultDBContext(), issue.Poster, issue.Content) + mentions, err := issue.FindAndUpdateIssueMentions(db.DefaultContext, issue.Poster, issue.Content) if err != nil { return err } diff --git a/services/issue/label_test.go b/services/issue/label_test.go index 065202894320a..8a3a77ecb0a69 100644 --- a/services/issue/label_test.go +++ b/services/issue/label_test.go @@ -8,6 +8,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" ) @@ -23,16 +24,16 @@ func TestIssue_AddLabels(t *testing.T) { {2, []int64{}, 1}, // pull-request, empty } for _, test := range tests { - assert.NoError(t, models.PrepareTestDatabase()) - issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: test.issueID}).(*models.Issue) + assert.NoError(t, db.PrepareTestDatabase()) + issue := db.AssertExistsAndLoadBean(t, &models.Issue{ID: test.issueID}).(*models.Issue) labels := make([]*models.Label, len(test.labelIDs)) for i, labelID := range test.labelIDs { - labels[i] = models.AssertExistsAndLoadBean(t, &models.Label{ID: labelID}).(*models.Label) + labels[i] = db.AssertExistsAndLoadBean(t, &models.Label{ID: labelID}).(*models.Label) } - doer := models.AssertExistsAndLoadBean(t, &models.User{ID: test.doerID}).(*models.User) + doer := db.AssertExistsAndLoadBean(t, &models.User{ID: test.doerID}).(*models.User) assert.NoError(t, AddLabels(issue, doer, labels)) for _, labelID := range test.labelIDs { - models.AssertExistsAndLoadBean(t, &models.IssueLabel{IssueID: test.issueID, LabelID: labelID}) + db.AssertExistsAndLoadBean(t, &models.IssueLabel{IssueID: test.issueID, LabelID: labelID}) } } } @@ -49,11 +50,11 @@ func TestIssue_AddLabel(t *testing.T) { {2, 1, 2}, // pull-request, already-added label } for _, test := range tests { - assert.NoError(t, models.PrepareTestDatabase()) - issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: test.issueID}).(*models.Issue) - label := models.AssertExistsAndLoadBean(t, &models.Label{ID: test.labelID}).(*models.Label) - doer := models.AssertExistsAndLoadBean(t, &models.User{ID: test.doerID}).(*models.User) + assert.NoError(t, db.PrepareTestDatabase()) + issue := db.AssertExistsAndLoadBean(t, &models.Issue{ID: test.issueID}).(*models.Issue) + label := db.AssertExistsAndLoadBean(t, &models.Label{ID: test.labelID}).(*models.Label) + doer := db.AssertExistsAndLoadBean(t, &models.User{ID: test.doerID}).(*models.User) assert.NoError(t, AddLabel(issue, doer, label)) - models.AssertExistsAndLoadBean(t, &models.IssueLabel{IssueID: test.issueID, LabelID: test.labelID}) + db.AssertExistsAndLoadBean(t, &models.IssueLabel{IssueID: test.issueID, LabelID: test.labelID}) } } diff --git a/services/issue/main_test.go b/services/issue/main_test.go index b056678a42261..1349837949d0f 100644 --- a/services/issue/main_test.go +++ b/services/issue/main_test.go @@ -8,9 +8,9 @@ import ( "path/filepath" "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..")) + db.MainTest(m, filepath.Join("..", "..")) } diff --git a/services/mailer/mail.go b/services/mailer/mail.go index 979f8aa2271ee..bd8f059c5ebb8 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -305,13 +305,10 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient msg := NewMessageFrom([]string{recipient.Email}, ctx.Doer.DisplayName(), setting.MailService.FromEmail, subject, mailBody.String()) msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info) - // Set Message-ID on first message so replies know what to reference - if actName == "new" { - msg.SetHeader("Message-ID", "<"+ctx.Issue.ReplyReference()+">") - } else { - msg.SetHeader("In-Reply-To", "<"+ctx.Issue.ReplyReference()+">") - msg.SetHeader("References", "<"+ctx.Issue.ReplyReference()+">") - } + msg.SetHeader("Message-ID", "<"+createReference(ctx.Issue, ctx.Comment)+">") + reference := createReference(ctx.Issue, nil) + msg.SetHeader("In-Reply-To", "<"+reference+">") + msg.SetHeader("References", "<"+reference+">") for key, value := range generateAdditionalHeaders(ctx, actType, recipient) { msg.SetHeader(key, value) @@ -323,6 +320,22 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient return msgs, nil } +func createReference(issue *models.Issue, comment *models.Comment) string { + var path string + if issue.IsPull { + path = "pulls" + } else { + path = "issues" + } + + var extra string + if comment != nil { + extra = fmt.Sprintf("/comment/%d", comment.ID) + } + + return fmt.Sprintf("%s/%s/%d%s@%s", issue.Repo.FullName(), path, issue.Index, extra, setting.Domain) +} + func generateAdditionalHeaders(ctx *mailCommentContext, reason string, recipient *models.User) map[string]string { repo := ctx.Issue.Repo @@ -372,6 +385,12 @@ func SendIssueAssignedMail(issue *models.Issue, doer *models.User, content strin // No mail service configured return nil } + + if err := issue.LoadRepo(); err != nil { + log.Error("Unable to load repo [%d] for issue #%d [%d]. Error: %v", issue.RepoID, issue.Index, issue.ID, err) + return err + } + langMap := make(map[string][]*models.User) for _, user := range recipients { langMap[user.Language] = append(langMap[user.Language], user) diff --git a/services/mailer/mail_test.go b/services/mailer/mail_test.go index 0a9112f3be59d..cd730a13a4450 100644 --- a/services/mailer/mail_test.go +++ b/services/mailer/mail_test.go @@ -11,6 +11,7 @@ import ( texttmpl "text/template" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" @@ -40,7 +41,7 @@ const bodyTpl = ` ` func prepareMailerTest(t *testing.T) (doer *models.User, repo *models.Repository, issue *models.Issue, comment *models.Comment) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) var mailService = setting.Mailer{ From: "test@gitea.com", } @@ -48,11 +49,11 @@ func prepareMailerTest(t *testing.T) (doer *models.User, repo *models.Repository setting.MailService = &mailService setting.Domain = "localhost" - doer = models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - repo = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1, Owner: doer}).(*models.Repository) - issue = models.AssertExistsAndLoadBean(t, &models.Issue{ID: 1, Repo: repo, Poster: doer}).(*models.Issue) + doer = db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo = db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1, Owner: doer}).(*models.Repository) + issue = db.AssertExistsAndLoadBean(t, &models.Issue{ID: 1, Repo: repo, Poster: doer}).(*models.Issue) assert.NoError(t, issue.LoadRepo()) - comment = models.AssertExistsAndLoadBean(t, &models.Comment{ID: 2, Issue: issue}).(*models.Comment) + comment = db.AssertExistsAndLoadBean(t, &models.Comment{ID: 2, Issue: issue}).(*models.Comment) return } @@ -71,14 +72,16 @@ func TestComposeIssueCommentMessage(t *testing.T) { gomailMsg := msgs[0].ToMessage() mailto := gomailMsg.GetHeader("To") subject := gomailMsg.GetHeader("Subject") - inreplyTo := gomailMsg.GetHeader("In-Reply-To") + messageID := gomailMsg.GetHeader("Message-ID") + inReplyTo := gomailMsg.GetHeader("In-Reply-To") references := gomailMsg.GetHeader("References") assert.Len(t, mailto, 1, "exactly one recipient is expected in the To field") assert.Equal(t, "Re: ", subject[0][:4], "Comment reply subject should contain Re:") assert.Equal(t, "Re: [user2/repo1] @user2 #1 - issue1", subject[0]) - assert.Equal(t, inreplyTo[0], "", "In-Reply-To header doesn't match") - assert.Equal(t, references[0], "", "References header doesn't match") + assert.Equal(t, "", inReplyTo[0], "In-Reply-To header doesn't match") + assert.Equal(t, "", references[0], "References header doesn't match") + assert.Equal(t, "", messageID[0], "Message-ID header doesn't match") } func TestComposeIssueMessage(t *testing.T) { @@ -98,12 +101,14 @@ func TestComposeIssueMessage(t *testing.T) { mailto := gomailMsg.GetHeader("To") subject := gomailMsg.GetHeader("Subject") messageID := gomailMsg.GetHeader("Message-ID") + inReplyTo := gomailMsg.GetHeader("In-Reply-To") + references := gomailMsg.GetHeader("References") assert.Len(t, mailto, 1, "exactly one recipient is expected in the To field") assert.Equal(t, "[user2/repo1] @user2 #1 - issue1", subject[0]) - assert.Nil(t, gomailMsg.GetHeader("In-Reply-To")) - assert.Nil(t, gomailMsg.GetHeader("References")) - assert.Equal(t, messageID[0], "", "Message-ID header doesn't match") + assert.Equal(t, "", inReplyTo[0], "In-Reply-To header doesn't match") + assert.Equal(t, "", references[0], "References header doesn't match") + assert.Equal(t, "", messageID[0], "Message-ID header doesn't match") } func TestTemplateSelection(t *testing.T) { @@ -139,8 +144,8 @@ func TestTemplateSelection(t *testing.T) { Content: "test body", Comment: comment}, recipients, false, "TestTemplateSelection") expect(t, msg, "issue/default/subject", "issue/default/body") - pull := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 2, Repo: repo, Poster: doer}).(*models.Issue) - comment = models.AssertExistsAndLoadBean(t, &models.Comment{ID: 4, Issue: pull}).(*models.Comment) + pull := db.AssertExistsAndLoadBean(t, &models.Issue{ID: 2, Repo: repo, Poster: doer}).(*models.Issue) + comment = db.AssertExistsAndLoadBean(t, &models.Comment{ID: 4, Issue: pull}).(*models.Comment) msg = testComposeIssueCommentMessage(t, &mailCommentContext{Issue: pull, Doer: doer, ActionType: models.ActionCommentPull, Content: "test body", Comment: comment}, recipients, false, "TestTemplateSelection") expect(t, msg, "pull/comment/subject", "pull/comment/body") diff --git a/services/mailer/main_test.go b/services/mailer/main_test.go index bbd121024e3e7..2fbe9c54a93cd 100644 --- a/services/mailer/main_test.go +++ b/services/mailer/main_test.go @@ -8,9 +8,9 @@ import ( "path/filepath" "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..")) + db.MainTest(m, filepath.Join("..", "..")) } diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index 06fdaf7aa638a..2dee49f710234 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -11,6 +11,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/lfs" @@ -267,7 +268,7 @@ func runSync(ctx context.Context, m *models.Mirror) ([]*mirrorSyncResult, bool) gitRepo.Close() log.Trace("SyncMirrors [repo: %-v]: updating size of repository", m.Repo) - if err := m.Repo.UpdateSize(models.DefaultDBContext()); err != nil { + if err := m.Repo.UpdateSize(db.DefaultContext); err != nil { log.Error("Failed to update size for mirror repository: %v", err) } diff --git a/services/pull/check.go b/services/pull/check.go index 1e757ef28b229..e819b09cf3e35 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -8,7 +8,6 @@ package pull import ( "context" "fmt" - "io/ioutil" "os" "strconv" "strings" @@ -75,7 +74,7 @@ func getMergeCommit(pr *models.PullRequest) (*git.Commit, error) { } } - indexTmpPath, err := ioutil.TempDir(os.TempDir(), "gitea-"+pr.BaseRepo.Name) + indexTmpPath, err := os.MkdirTemp(os.TempDir(), "gitea-"+pr.BaseRepo.Name) if err != nil { return nil, fmt.Errorf("Failed to create temp dir for repository %s: %v", pr.BaseRepo.RepoPath(), err) } @@ -98,7 +97,7 @@ func getMergeCommit(pr *models.PullRequest) (*git.Commit, error) { return nil, fmt.Errorf("git merge-base --is-ancestor: %v", err) } - commitIDBytes, err := ioutil.ReadFile(pr.BaseRepo.RepoPath() + "/" + headFile) + commitIDBytes, err := os.ReadFile(pr.BaseRepo.RepoPath() + "/" + headFile) if err != nil { return nil, fmt.Errorf("ReadFile(%s): %v", headFile, err) } diff --git a/services/pull/check_test.go b/services/pull/check_test.go index f6614ea0ad27f..8beea3d56d90d 100644 --- a/services/pull/check_test.go +++ b/services/pull/check_test.go @@ -11,13 +11,14 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/queue" "github.com/stretchr/testify/assert" ) func TestPullRequest_AddToTaskQueue(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) idChan := make(chan int64, 10) @@ -41,11 +42,11 @@ func TestPullRequest_AddToTaskQueue(t *testing.T) { prQueue = q.(queue.UniqueQueue) - pr := models.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 2}).(*models.PullRequest) + pr := db.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 2}).(*models.PullRequest) AddToTaskQueue(pr) assert.Eventually(t, func() bool { - pr = models.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 2}).(*models.PullRequest) + pr = db.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 2}).(*models.PullRequest) return pr.Status == models.PullRequestStatusChecking }, 1*time.Second, 100*time.Millisecond) @@ -70,7 +71,7 @@ func TestPullRequest_AddToTaskQueue(t *testing.T) { assert.False(t, has) assert.NoError(t, err) - pr = models.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 2}).(*models.PullRequest) + pr = db.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 2}).(*models.PullRequest) assert.Equal(t, models.PullRequestStatusChecking, pr.Status) for _, callback := range queueShutdown { diff --git a/services/pull/commit_status.go b/services/pull/commit_status.go index c5c930ee0d8b3..f1f351138b8e1 100644 --- a/services/pull/commit_status.go +++ b/services/pull/commit_status.go @@ -7,6 +7,7 @@ package pull import ( "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/structs" @@ -129,7 +130,7 @@ func GetPullRequestCommitStatusState(pr *models.PullRequest) (structs.CommitStat return "", errors.Wrap(err, "LoadBaseRepo") } - commitStatuses, err := models.GetLatestCommitStatus(pr.BaseRepo.ID, sha, models.ListOptions{}) + commitStatuses, err := models.GetLatestCommitStatus(pr.BaseRepo.ID, sha, db.ListOptions{}) if err != nil { return "", errors.Wrap(err, "GetLatestCommitStatus") } diff --git a/services/pull/main_test.go b/services/pull/main_test.go index 6c49e8fbf240a..c8d3394e8e6ad 100644 --- a/services/pull/main_test.go +++ b/services/pull/main_test.go @@ -9,9 +9,9 @@ import ( "path/filepath" "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..")) + db.MainTest(m, filepath.Join("..", "..")) } diff --git a/services/pull/merge.go b/services/pull/merge.go index ef797e1ca477b..4d4a64e9848e6 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -9,7 +9,6 @@ import ( "bufio" "bytes" "fmt" - "io/ioutil" "os" "path/filepath" "regexp" @@ -149,7 +148,7 @@ func rawMerge(pr *models.PullRequest, doer *models.User, mergeStyle models.Merge } sparseCheckoutListPath := filepath.Join(infoPath, "sparse-checkout") - if err := ioutil.WriteFile(sparseCheckoutListPath, []byte(sparseCheckoutList), 0600); err != nil { + if err := os.WriteFile(sparseCheckoutListPath, []byte(sparseCheckoutList), 0600); err != nil { log.Error("Unable to write .git/info/sparse-checkout file in %s: %v", tmpBasePath, err) return "", fmt.Errorf("Unable to write .git/info/sparse-checkout file in tmpBasePath: %v", err) } @@ -276,7 +275,7 @@ func rawMerge(pr *models.PullRequest, doer *models.User, mergeStyle models.Merge } for _, failingCommitPath := range failingCommitPaths { if _, statErr := os.Stat(filepath.Join(failingCommitPath)); statErr == nil { - commitShaBytes, readErr := ioutil.ReadFile(filepath.Join(failingCommitPath)) + commitShaBytes, readErr := os.ReadFile(filepath.Join(failingCommitPath)) if readErr != nil { // Abandon this attempt to handle the error log.Error("git rebase staging on to base [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) diff --git a/services/pull/patch.go b/services/pull/patch.go index 72b459bf2cb3d..27d799191917e 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -10,7 +10,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "os" "strings" @@ -23,7 +22,7 @@ import ( ) // DownloadDiffOrPatch will write the patch for the pr to the writer -func DownloadDiffOrPatch(pr *models.PullRequest, w io.Writer, patch bool) error { +func DownloadDiffOrPatch(pr *models.PullRequest, w io.Writer, patch, binary bool) error { if err := pr.LoadBaseRepo(); err != nil { log.Error("Unable to load base repository ID %d for pr #%d [%d]", pr.BaseRepoID, pr.Index, pr.ID) return err @@ -34,7 +33,7 @@ func DownloadDiffOrPatch(pr *models.PullRequest, w io.Writer, patch bool) error return fmt.Errorf("OpenRepository: %v", err) } defer gitRepo.Close() - if err := gitRepo.GetDiffOrPatch(pr.MergeBase, pr.GetGitRefName(), w, patch); err != nil { + if err := gitRepo.GetDiffOrPatch(pr.MergeBase, pr.GetGitRefName(), w, patch, binary); err != nil { log.Error("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err) return fmt.Errorf("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err) } @@ -100,7 +99,7 @@ func TestPatch(pr *models.PullRequest) error { func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath string) (bool, error) { // 1. Create a plain patch from head to base - tmpPatchFile, err := ioutil.TempFile("", "patch") + tmpPatchFile, err := os.CreateTemp("", "patch") if err != nil { log.Error("Unable to create temporary patch file! Error: %v", err) return false, fmt.Errorf("Unable to create temporary patch file! Error: %v", err) @@ -109,7 +108,7 @@ func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath _ = util.Remove(tmpPatchFile.Name()) }() - if err := gitRepo.GetDiff(pr.MergeBase, "tracking", tmpPatchFile); err != nil { + if err := gitRepo.GetDiffBinary(pr.MergeBase, "tracking", tmpPatchFile); err != nil { tmpPatchFile.Close() log.Error("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err) return false, fmt.Errorf("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err) @@ -245,70 +244,59 @@ func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath // CheckFileProtection check file Protection func CheckFileProtection(oldCommitID, newCommitID string, patterns []glob.Glob, limit int, env []string, repo *git.Repository) ([]string, error) { - // 1. If there are no patterns short-circuit and just return nil if len(patterns) == 0 { return nil, nil } - - // 2. Prep the pipe - stdoutReader, stdoutWriter, err := os.Pipe() + affectedFiles, err := git.GetAffectedFiles(oldCommitID, newCommitID, env, repo) if err != nil { - log.Error("Unable to create os.Pipe for %s", repo.Path) return nil, err } - defer func() { - _ = stdoutReader.Close() - _ = stdoutWriter.Close() - }() - changedProtectedFiles := make([]string, 0, limit) - - // 3. Run `git diff --name-only` to get the names of the changed files - err = git.NewCommand("diff", "--name-only", oldCommitID, newCommitID). - RunInDirTimeoutEnvFullPipelineFunc(env, -1, repo.Path, - stdoutWriter, nil, nil, - func(ctx context.Context, cancel context.CancelFunc) error { - // Close the writer end of the pipe to begin processing - _ = stdoutWriter.Close() - defer func() { - // Close the reader on return to terminate the git command if necessary - _ = stdoutReader.Close() - }() - - // Now scan the output from the command - scanner := bufio.NewScanner(stdoutReader) - for scanner.Scan() { - path := strings.TrimSpace(scanner.Text()) - if len(path) == 0 { - continue - } - lpath := strings.ToLower(path) - for _, pat := range patterns { - if pat.Match(lpath) { - changedProtectedFiles = append(changedProtectedFiles, path) - break - } - } - if len(changedProtectedFiles) >= limit { - break - } - } - - if len(changedProtectedFiles) > 0 { - return models.ErrFilePathProtected{ - Path: changedProtectedFiles[0], - } - } - return scanner.Err() - }) - // 4. log real errors if there are any... - if err != nil && !models.IsErrFilePathProtected(err) { - log.Error("Unable to check file protection for commits from %s to %s in %s: %v", oldCommitID, newCommitID, repo.Path, err) + for _, affectedFile := range affectedFiles { + lpath := strings.ToLower(affectedFile) + for _, pat := range patterns { + if pat.Match(lpath) { + changedProtectedFiles = append(changedProtectedFiles, lpath) + break + } + } + if len(changedProtectedFiles) >= limit { + break + } + } + if len(changedProtectedFiles) > 0 { + err = models.ErrFilePathProtected{ + Path: changedProtectedFiles[0], + } } - return changedProtectedFiles, err } +// CheckUnprotectedFiles check if the commit only touches unprotected files +func CheckUnprotectedFiles(oldCommitID, newCommitID string, patterns []glob.Glob, env []string, repo *git.Repository) (bool, error) { + if len(patterns) == 0 { + return false, nil + } + affectedFiles, err := git.GetAffectedFiles(oldCommitID, newCommitID, env, repo) + if err != nil { + return false, err + } + for _, affectedFile := range affectedFiles { + lpath := strings.ToLower(affectedFile) + unprotected := false + for _, pat := range patterns { + if pat.Match(lpath) { + unprotected = true + break + } + } + if !unprotected { + return false, nil + } + } + return true, nil +} + // checkPullFilesProtection check if pr changed protected files and save results func checkPullFilesProtection(pr *models.PullRequest, gitRepo *git.Repository) error { if err := pr.LoadProtectedBranch(); err != nil { diff --git a/services/pull/pull.go b/services/pull/pull.go index 23407ea67bdb2..f7d154cfd0235 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -14,6 +14,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/json" @@ -58,7 +59,7 @@ func NewPullRequest(repo *models.Repository, pull *models.Issue, labelIDs []int6 return err } - mentions, err := pull.FindAndUpdateIssueMentions(models.DefaultDBContext(), pull.Poster, pull.Content) + mentions, err := pull.FindAndUpdateIssueMentions(db.DefaultContext, pull.Poster, pull.Content) if err != nil { return err } @@ -79,7 +80,7 @@ func NewPullRequest(repo *models.Repository, pull *models.Issue, labelIDs []int6 defer baseGitRepo.Close() compareInfo, err := baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), - git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName()) + git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName(), true) if err != nil { return err } @@ -779,7 +780,7 @@ func getLastCommitStatus(gitRepo *git.Repository, pr *models.PullRequest) (statu return nil, err } - statusList, err := models.GetLatestCommitStatus(pr.BaseRepo.ID, sha, models.ListOptions{}) + statusList, err := models.GetLatestCommitStatus(pr.BaseRepo.ID, sha, db.ListOptions{}) if err != nil { return nil, err } diff --git a/services/pull/review.go b/services/pull/review.go index 3aa45706201a4..081b17cd83e83 100644 --- a/services/pull/review.go +++ b/services/pull/review.go @@ -12,6 +12,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" @@ -58,7 +59,7 @@ func CreateCodeComment(doer *models.User, gitRepo *git.Repository, issue *models return nil, err } - mentions, err := issue.FindAndUpdateIssueMentions(models.DefaultDBContext(), doer, comment.Content) + mentions, err := issue.FindAndUpdateIssueMentions(db.DefaultContext, doer, comment.Content) if err != nil { return nil, err } @@ -137,7 +138,7 @@ func createCodeComment(doer *models.User, repo *models.Repository, issue *models Line: line, TreePath: treePath, Type: models.CommentTypeCode, - ListOptions: models.ListOptions{ + ListOptions: db.ListOptions{ PageSize: 1, Page: 1, }, @@ -245,7 +246,7 @@ func SubmitReview(doer *models.User, gitRepo *git.Repository, issue *models.Issu return nil, nil, err } - ctx := models.DefaultDBContext() + ctx := db.DefaultContext mentions, err := issue.FindAndUpdateIssueMentions(ctx, doer, comm.Content) if err != nil { return nil, nil, err diff --git a/services/release/release.go b/services/release/release.go index 4a55f73a3a5c2..f6f456e8fa373 100644 --- a/services/release/release.go +++ b/services/release/release.go @@ -10,6 +10,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" @@ -122,7 +123,7 @@ func CreateRelease(gitRepo *git.Repository, rel *models.Release, attachmentUUIDs return err } - if err = models.AddReleaseAttachments(models.DefaultDBContext(), rel.ID, attachmentUUIDs); err != nil { + if err = models.AddReleaseAttachments(db.DefaultContext, rel.ID, attachmentUUIDs); err != nil { return err } @@ -188,11 +189,11 @@ func UpdateRelease(doer *models.User, gitRepo *git.Repository, rel *models.Relea } rel.LowerTagName = strings.ToLower(rel.TagName) - ctx, commiter, err := models.TxDBContext() + ctx, committer, err := db.TxContext() if err != nil { return err } - defer commiter.Close() + defer committer.Close() if err = models.UpdateRelease(ctx, rel); err != nil { return err @@ -249,7 +250,7 @@ func UpdateRelease(doer *models.User, gitRepo *git.Repository, rel *models.Relea } } - if err = commiter.Commit(); err != nil { + if err = committer.Commit(); err != nil { return } @@ -310,7 +311,7 @@ func DeleteReleaseByID(id int64, doer *models.User, delTag bool) error { } else { rel.IsTag = true - if err = models.UpdateRelease(models.DefaultDBContext(), rel); err != nil { + if err = models.UpdateRelease(db.DefaultContext, rel); err != nil { return fmt.Errorf("Update: %v", err) } } diff --git a/services/release/release_test.go b/services/release/release_test.go index 936f2ab71c753..e53e4c935b7c5 100644 --- a/services/release/release_test.go +++ b/services/release/release_test.go @@ -11,6 +11,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/services/attachment" @@ -18,14 +19,14 @@ import ( ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..")) + db.MainTest(m, filepath.Join("..", "..")) } func TestRelease_Create(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) repoPath := models.RepoPath(user.Name, repo.Name) gitRepo, err := git.OpenRepository(repoPath) @@ -126,10 +127,10 @@ func TestRelease_Create(t *testing.T) { } func TestRelease_Update(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) repoPath := models.RepoPath(user.Name, repo.Name) gitRepo, err := git.OpenRepository(repoPath) @@ -268,10 +269,10 @@ func TestRelease_Update(t *testing.T) { } func TestRelease_createTag(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) repoPath := models.RepoPath(user.Name, repo.Name) gitRepo, err := git.OpenRepository(repoPath) @@ -351,9 +352,9 @@ func TestRelease_createTag(t *testing.T) { } func TestCreateNewTag(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) - user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + assert.NoError(t, db.PrepareTestDatabase()) + user := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) assert.NoError(t, CreateNewTag(user, repo, "master", "v2.0", "v2.0 is released \n\n BUGFIX: .... \n\n 123")) diff --git a/services/repository/generate.go b/services/repository/generate.go index 43cbb45a41f01..fe38723dea353 100644 --- a/services/repository/generate.go +++ b/services/repository/generate.go @@ -5,7 +5,10 @@ package repository import ( + "context" + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" repo_module "code.gitea.io/gitea/modules/repository" @@ -20,7 +23,7 @@ func GenerateRepository(doer, owner *models.User, templateRepo *models.Repositor } var generateRepo *models.Repository - if err = models.WithTx(func(ctx models.DBContext) error { + if err = db.WithTx(func(ctx context.Context) error { generateRepo, err = repo_module.GenerateRepository(ctx, doer, owner, templateRepo, opts) if err != nil { return err diff --git a/services/repository/main_test.go b/services/repository/main_test.go index f13f358635ba1..91d0d36ca0260 100644 --- a/services/repository/main_test.go +++ b/services/repository/main_test.go @@ -8,9 +8,9 @@ import ( "path/filepath" "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..")) + db.MainTest(m, filepath.Join("..", "..")) } diff --git a/services/repository/push.go b/services/repository/push.go index cf7060adef0e7..4d86667539c46 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -9,6 +9,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/graceful" @@ -82,7 +83,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { } defer gitRepo.Close() - if err = repo.UpdateSize(models.DefaultDBContext()); err != nil { + if err = repo.UpdateSize(db.DefaultContext); err != nil { log.Error("Failed to update size for repository: %v", err) } diff --git a/services/repository/transfer_test.go b/services/repository/transfer_test.go index c92844674c6d4..40ccfdfb52fab 100644 --- a/services/repository/transfer_test.go +++ b/services/repository/transfer_test.go @@ -9,6 +9,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/notification/action" "code.gitea.io/gitea/modules/util" @@ -27,14 +28,14 @@ func registerNotifier() { func TestTransferOwnership(t *testing.T) { registerNotifier() - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - doer := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) - repo.Owner = models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + doer := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) + repo.Owner = db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) assert.NoError(t, TransferOwnership(doer, doer, repo, nil)) - transferredRepo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) + transferredRepo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) assert.EqualValues(t, 2, transferredRepo.OwnerID) exist, err := util.IsExist(models.RepoPath("user3", "repo3")) @@ -43,7 +44,7 @@ func TestTransferOwnership(t *testing.T) { exist, err = util.IsExist(models.RepoPath("user2", "repo3")) assert.NoError(t, err) assert.True(t, exist) - models.AssertExistsAndLoadBean(t, &models.Action{ + db.AssertExistsAndLoadBean(t, &models.Action{ OpType: models.ActionTransferRepo, ActUserID: 2, RepoID: 3, @@ -54,12 +55,12 @@ func TestTransferOwnership(t *testing.T) { } func TestStartRepositoryTransferSetPermission(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - doer := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) - recipient := models.AssertExistsAndLoadBean(t, &models.User{ID: 5}).(*models.User) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) - repo.Owner = models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + doer := db.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) + recipient := db.AssertExistsAndLoadBean(t, &models.User{ID: 5}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) + repo.Owner = db.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) hasAccess, err := models.HasAccess(recipient.ID, repo) assert.NoError(t, err) diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go index d0e115b136ce9..28c3b23b2f896 100644 --- a/services/webhook/deliver.go +++ b/services/webhook/deliver.go @@ -13,7 +13,6 @@ import ( "encoding/hex" "fmt" "io" - "io/ioutil" "net" "net/http" "net/url" @@ -114,16 +113,21 @@ func Deliver(t *models.HookTask) error { signatureSHA256 = hex.EncodeToString(sig256.Sum(nil)) } + event := t.EventType.Event() + eventType := string(t.EventType) req.Header.Add("X-Gitea-Delivery", t.UUID) - req.Header.Add("X-Gitea-Event", t.EventType.Event()) + req.Header.Add("X-Gitea-Event", event) + req.Header.Add("X-Gitea-Event-Type", eventType) req.Header.Add("X-Gitea-Signature", signatureSHA256) req.Header.Add("X-Gogs-Delivery", t.UUID) - req.Header.Add("X-Gogs-Event", t.EventType.Event()) + req.Header.Add("X-Gogs-Event", event) + req.Header.Add("X-Gogs-Event-Type", eventType) req.Header.Add("X-Gogs-Signature", signatureSHA256) req.Header.Add("X-Hub-Signature", "sha1="+signatureSHA1) req.Header.Add("X-Hub-Signature-256", "sha256="+signatureSHA256) req.Header["X-GitHub-Delivery"] = []string{t.UUID} - req.Header["X-GitHub-Event"] = []string{t.EventType.Event()} + req.Header["X-GitHub-Event"] = []string{event} + req.Header["X-GitHub-Event-Type"] = []string{eventType} // Record delivery information. t.RequestInfo = &models.HookRequest{ @@ -181,7 +185,7 @@ func Deliver(t *models.HookTask) error { t.ResponseInfo.Headers[k] = strings.Join(vals, ",") } - p, err := ioutil.ReadAll(resp.Body) + p, err := io.ReadAll(resp.Body) if err != nil { t.ResponseInfo.Body = fmt.Sprintf("read body: %s", err) return err diff --git a/services/webhook/dingtalk.go b/services/webhook/dingtalk.go index 3296b1114a10f..7d352db18c69a 100644 --- a/services/webhook/dingtalk.go +++ b/services/webhook/dingtalk.go @@ -6,6 +6,7 @@ package webhook import ( "fmt" + "net/url" "strings" "code.gitea.io/gitea/models" @@ -175,7 +176,10 @@ func createDingtalkPayload(title, text, singleTitle, singleURL string) *Dingtalk Title: strings.TrimSpace(title), HideAvatar: "0", SingleTitle: singleTitle, - SingleURL: singleURL, + + // https://developers.dingtalk.com/document/app/message-link-description + // to open the link in browser, we should use this URL, otherwise the page is displayed inside DingTalk client, very difficult to visit non-public URLs. + SingleURL: "dingtalk://dingtalkclient/page/link?pc_slide=false&url=" + url.QueryEscape(singleURL), }, } } diff --git a/services/webhook/dingtalk_test.go b/services/webhook/dingtalk_test.go index 213ad1a284ec1..5c1d18752377f 100644 --- a/services/webhook/dingtalk_test.go +++ b/services/webhook/dingtalk_test.go @@ -5,6 +5,7 @@ package webhook import ( + "net/url" "testing" "code.gitea.io/gitea/models" @@ -15,6 +16,16 @@ import ( ) func TestDingTalkPayload(t *testing.T) { + parseRealSingleURL := func(singleURL string) string { + if u, err := url.Parse(singleURL); err == nil { + assert.Equal(t, "dingtalk", u.Scheme) + assert.Equal(t, "dingtalkclient", u.Host) + assert.Equal(t, "/page/link", u.Path) + return u.Query().Get("url") + } + return "" + } + t.Run("Create", func(t *testing.T) { p := createTestPayload() @@ -27,7 +38,7 @@ func TestDingTalkPayload(t *testing.T) { assert.Equal(t, "[test/repo] branch test created", pl.(*DingtalkPayload).ActionCard.Text) assert.Equal(t, "[test/repo] branch test created", pl.(*DingtalkPayload).ActionCard.Title) assert.Equal(t, "view ref test", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.(*DingtalkPayload).ActionCard.SingleURL) + assert.Equal(t, "http://localhost:3000/test/repo/src/test", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) }) t.Run("Delete", func(t *testing.T) { @@ -42,7 +53,7 @@ func TestDingTalkPayload(t *testing.T) { assert.Equal(t, "[test/repo] branch test deleted", pl.(*DingtalkPayload).ActionCard.Text) assert.Equal(t, "[test/repo] branch test deleted", pl.(*DingtalkPayload).ActionCard.Title) assert.Equal(t, "view ref test", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.(*DingtalkPayload).ActionCard.SingleURL) + assert.Equal(t, "http://localhost:3000/test/repo/src/test", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) }) t.Run("Fork", func(t *testing.T) { @@ -57,7 +68,7 @@ func TestDingTalkPayload(t *testing.T) { assert.Equal(t, "test/repo2 is forked to test/repo", pl.(*DingtalkPayload).ActionCard.Text) assert.Equal(t, "test/repo2 is forked to test/repo", pl.(*DingtalkPayload).ActionCard.Title) assert.Equal(t, "view forked repo test/repo", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo", pl.(*DingtalkPayload).ActionCard.SingleURL) + assert.Equal(t, "http://localhost:3000/test/repo", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) }) t.Run("Push", func(t *testing.T) { @@ -72,7 +83,7 @@ func TestDingTalkPayload(t *testing.T) { assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.(*DingtalkPayload).ActionCard.Text) assert.Equal(t, "[test/repo:test] 2 new commits", pl.(*DingtalkPayload).ActionCard.Title) assert.Equal(t, "view commit 2020558...2020558", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.(*DingtalkPayload).ActionCard.SingleURL) + assert.Equal(t, "http://localhost:3000/test/repo/src/test", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) }) t.Run("Issue", func(t *testing.T) { @@ -88,7 +99,7 @@ func TestDingTalkPayload(t *testing.T) { assert.Equal(t, "[test/repo] Issue opened: #2 crash by user1\r\n\r\nissue body", pl.(*DingtalkPayload).ActionCard.Text) assert.Equal(t, "#2 crash", pl.(*DingtalkPayload).ActionCard.Title) assert.Equal(t, "view issue", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.(*DingtalkPayload).ActionCard.SingleURL) + assert.Equal(t, "http://localhost:3000/test/repo/issues/2", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) p.Action = api.HookIssueClosed pl, err = d.Issue(p) @@ -99,7 +110,7 @@ func TestDingTalkPayload(t *testing.T) { assert.Equal(t, "[test/repo] Issue closed: #2 crash by user1", pl.(*DingtalkPayload).ActionCard.Text) assert.Equal(t, "#2 crash", pl.(*DingtalkPayload).ActionCard.Title) assert.Equal(t, "view issue", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.(*DingtalkPayload).ActionCard.SingleURL) + assert.Equal(t, "http://localhost:3000/test/repo/issues/2", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) }) t.Run("IssueComment", func(t *testing.T) { @@ -114,7 +125,7 @@ func TestDingTalkPayload(t *testing.T) { assert.Equal(t, "[test/repo] New comment on issue #2 crash by user1\r\n\r\nmore info needed", pl.(*DingtalkPayload).ActionCard.Text) assert.Equal(t, "#2 crash", pl.(*DingtalkPayload).ActionCard.Title) assert.Equal(t, "view issue comment", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2#issuecomment-4", pl.(*DingtalkPayload).ActionCard.SingleURL) + assert.Equal(t, "http://localhost:3000/test/repo/issues/2#issuecomment-4", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) }) t.Run("PullRequest", func(t *testing.T) { @@ -129,7 +140,7 @@ func TestDingTalkPayload(t *testing.T) { assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug by user1\r\n\r\nfixes bug #2", pl.(*DingtalkPayload).ActionCard.Text) assert.Equal(t, "#12 Fix bug", pl.(*DingtalkPayload).ActionCard.Title) assert.Equal(t, "view pull request", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.(*DingtalkPayload).ActionCard.SingleURL) + assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) }) t.Run("PullRequestComment", func(t *testing.T) { @@ -144,7 +155,7 @@ func TestDingTalkPayload(t *testing.T) { assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug by user1\r\n\r\nchanges requested", pl.(*DingtalkPayload).ActionCard.Text) assert.Equal(t, "#12 Fix bug", pl.(*DingtalkPayload).ActionCard.Title) assert.Equal(t, "view issue comment", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12#issuecomment-4", pl.(*DingtalkPayload).ActionCard.SingleURL) + assert.Equal(t, "http://localhost:3000/test/repo/pulls/12#issuecomment-4", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) }) t.Run("Review", func(t *testing.T) { @@ -160,7 +171,7 @@ func TestDingTalkPayload(t *testing.T) { assert.Equal(t, "[test/repo] Pull request review approved : #12 Fix bug\r\n\r\ngood job", pl.(*DingtalkPayload).ActionCard.Text) assert.Equal(t, "[test/repo] Pull request review approved : #12 Fix bug", pl.(*DingtalkPayload).ActionCard.Title) assert.Equal(t, "view pull request", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.(*DingtalkPayload).ActionCard.SingleURL) + assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) }) t.Run("Repository", func(t *testing.T) { @@ -175,7 +186,7 @@ func TestDingTalkPayload(t *testing.T) { assert.Equal(t, "[test/repo] Repository created", pl.(*DingtalkPayload).ActionCard.Text) assert.Equal(t, "[test/repo] Repository created", pl.(*DingtalkPayload).ActionCard.Title) assert.Equal(t, "view repository", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo", pl.(*DingtalkPayload).ActionCard.SingleURL) + assert.Equal(t, "http://localhost:3000/test/repo", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) }) t.Run("Release", func(t *testing.T) { @@ -190,7 +201,7 @@ func TestDingTalkPayload(t *testing.T) { assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.(*DingtalkPayload).ActionCard.Text) assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.(*DingtalkPayload).ActionCard.Title) assert.Equal(t, "view release", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/api/v1/repos/test/repo/releases/2", pl.(*DingtalkPayload).ActionCard.SingleURL) + assert.Equal(t, "http://localhost:3000/api/v1/repos/test/repo/releases/2", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) }) } diff --git a/services/webhook/main_test.go b/services/webhook/main_test.go index 6cb0cffe494a1..7aef4b3a51ef3 100644 --- a/services/webhook/main_test.go +++ b/services/webhook/main_test.go @@ -8,9 +8,9 @@ import ( "path/filepath" "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..")) + db.MainTest(m, filepath.Join("..", "..")) } diff --git a/services/webhook/webhook_test.go b/services/webhook/webhook_test.go index 10c32a9485be2..095b30713c8bb 100644 --- a/services/webhook/webhook_test.go +++ b/services/webhook/webhook_test.go @@ -8,6 +8,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" ) @@ -25,52 +26,52 @@ func TestWebhook_GetSlackHook(t *testing.T) { } func TestPrepareWebhooks(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) hookTasks := []*models.HookTask{ {RepoID: repo.ID, HookID: 1, EventType: models.HookEventPush}, } for _, hookTask := range hookTasks { - models.AssertNotExistsBean(t, hookTask) + db.AssertNotExistsBean(t, hookTask) } assert.NoError(t, PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{Commits: []*api.PayloadCommit{{}}})) for _, hookTask := range hookTasks { - models.AssertExistsAndLoadBean(t, hookTask) + db.AssertExistsAndLoadBean(t, hookTask) } } func TestPrepareWebhooksBranchFilterMatch(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) hookTasks := []*models.HookTask{ {RepoID: repo.ID, HookID: 4, EventType: models.HookEventPush}, } for _, hookTask := range hookTasks { - models.AssertNotExistsBean(t, hookTask) + db.AssertNotExistsBean(t, hookTask) } // this test also ensures that * doesn't handle / in any special way (like shell would) assert.NoError(t, PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{Ref: "refs/heads/feature/7791", Commits: []*api.PayloadCommit{{}}})) for _, hookTask := range hookTasks { - models.AssertExistsAndLoadBean(t, hookTask) + db.AssertExistsAndLoadBean(t, hookTask) } } func TestPrepareWebhooksBranchFilterNoMatch(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) hookTasks := []*models.HookTask{ {RepoID: repo.ID, HookID: 4, EventType: models.HookEventPush}, } for _, hookTask := range hookTasks { - models.AssertNotExistsBean(t, hookTask) + db.AssertNotExistsBean(t, hookTask) } assert.NoError(t, PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{Ref: "refs/heads/fix_weird_bug"})) for _, hookTask := range hookTasks { - models.AssertNotExistsBean(t, hookTask) + db.AssertNotExistsBean(t, hookTask) } } diff --git a/services/wiki/wiki_test.go b/services/wiki/wiki_test.go index 6c861d556a7f5..d6a65cc23a822 100644 --- a/services/wiki/wiki_test.go +++ b/services/wiki/wiki_test.go @@ -5,12 +5,12 @@ package wiki import ( - "io/ioutil" "os" "path/filepath" "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/util" @@ -18,7 +18,7 @@ import ( ) func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..")) + db.MainTest(m, filepath.Join("..", "..")) } func TestWikiNameToSubURL(t *testing.T) { @@ -110,23 +110,23 @@ func TestWikiNameToFilenameToName(t *testing.T) { } func TestRepository_InitWiki(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) // repo1 already has a wiki - repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + repo1 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) assert.NoError(t, InitWiki(repo1)) // repo2 does not already have a wiki - repo2 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) + repo2 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) assert.NoError(t, InitWiki(repo2)) assert.True(t, repo2.HasWiki()) } func TestRepository_AddWikiPage(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) const wikiContent = "This is the wiki content" const commitMsg = "Commit message" - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - doer := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + doer := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) for _, wikiName := range []string{ "Another page", "Here's a and a/slash", @@ -166,18 +166,18 @@ func TestRepository_AddWikiPage(t *testing.T) { } func TestRepository_EditWikiPage(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) + assert.NoError(t, db.PrepareTestDatabase()) const newWikiContent = "This is the new content" const commitMsg = "Commit message" - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - doer := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + doer := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) for _, newWikiName := range []string{ "Home", // same name as before "New home", "New/name/with/slashes", } { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) assert.NoError(t, EditWikiPage(doer, repo, "Home", newWikiName, newWikiContent, commitMsg)) // Now need to show that the page has been added: @@ -199,9 +199,9 @@ func TestRepository_EditWikiPage(t *testing.T) { } func TestRepository_DeleteWikiPage(t *testing.T) { - models.PrepareTestEnv(t) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - doer := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + db.PrepareTestEnv(t) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + doer := db.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) assert.NoError(t, DeleteWikiPage(doer, repo, "Home")) // Now need to show that the page has been added: @@ -216,8 +216,8 @@ func TestRepository_DeleteWikiPage(t *testing.T) { } func TestPrepareWikiFileName(t *testing.T) { - models.PrepareTestEnv(t) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + db.PrepareTestEnv(t) + repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) gitRepo, err := git.OpenRepository(repo.WikiPath()) defer gitRepo.Close() assert.NoError(t, err) @@ -267,10 +267,10 @@ func TestPrepareWikiFileName(t *testing.T) { } func TestPrepareWikiFileName_FirstPage(t *testing.T) { - models.PrepareTestEnv(t) + db.PrepareTestEnv(t) // Now create a temporaryDirectory - tmpDir, err := ioutil.TempDir("", "empty-wiki") + tmpDir, err := os.MkdirTemp("", "empty-wiki") assert.NoError(t, err) defer func() { if _, err := os.Stat(tmpDir); !os.IsNotExist(err) { diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index 109186a17821f..2f77e9bd801dc 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -104,6 +104,10 @@ +
+ + +
@@ -147,6 +151,13 @@
{{end}} +
+
+ + +

{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}

+
+
@@ -208,6 +219,13 @@

{{.i18n.Tr "admin.auths.allowed_domains_helper"}}

+
+
+ + +

{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}

+
+
{{end}} @@ -221,6 +239,13 @@
+
+
+ + +

{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}

+
+
{{end}} @@ -255,6 +280,13 @@ +
+
+ + +

{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}

+
+
diff --git a/templates/admin/auth/new.tmpl b/templates/admin/auth/new.tmpl index ba1f145a4a3bf..13e1366c874ef 100644 --- a/templates/admin/auth/new.tmpl +++ b/templates/admin/auth/new.tmpl @@ -41,6 +41,13 @@
+
+
+ + +

{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}

+
+
{{ template "admin/auth/source/oauth" . }} diff --git a/templates/admin/auth/source/ldap.tmpl b/templates/admin/auth/source/ldap.tmpl index 295e001cf4a36..b553502b94025 100644 --- a/templates/admin/auth/source/ldap.tmpl +++ b/templates/admin/auth/source/ldap.tmpl @@ -76,6 +76,10 @@
+
+ + +
@@ -111,4 +115,17 @@
+
+
+ + +

{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}

+
+
+
+
+ + +
+
diff --git a/templates/admin/auth/source/oauth.tmpl b/templates/admin/auth/source/oauth.tmpl index b19fe3d42825d..6e91da14e24f0 100644 --- a/templates/admin/auth/source/oauth.tmpl +++ b/templates/admin/auth/source/oauth.tmpl @@ -28,6 +28,13 @@ +
+
+ + +

{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}

+
+
diff --git a/templates/admin/auth/source/smtp.tmpl b/templates/admin/auth/source/smtp.tmpl index b0f643b8ca687..8572d6dc56ebe 100644 --- a/templates/admin/auth/source/smtp.tmpl +++ b/templates/admin/auth/source/smtp.tmpl @@ -49,4 +49,11 @@

{{.i18n.Tr "admin.auths.allowed_domains_helper"}}

+
+
+ + +

{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}

+
+
diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index 328661eb9dec5..15f2826abf928 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -48,11 +48,11 @@ tributeValues: Array.from(new Map([ {{ range .Participants }} ['{{.Name}}', {key: '{{.Name}} {{.FullName}}', value: '{{.Name}}', - name: '{{.Name}}', fullname: '{{.FullName}}', avatar: '{{.RelAvatarLink}}'}], + name: '{{.Name}}', fullname: '{{.FullName}}', avatar: '{{.AvatarLink}}'}], {{ end }} {{ range .Assignees }} ['{{.Name}}', {key: '{{.Name}} {{.FullName}}', value: '{{.Name}}', - name: '{{.Name}}', fullname: '{{.FullName}}', avatar: '{{.RelAvatarLink}}'}], + name: '{{.Name}}', fullname: '{{.FullName}}', avatar: '{{.AvatarLink}}'}], {{ end }} {{ range .MentionableTeams }} ['{{$.MentionableTeamsOrg}}/{{.Name}}', {key: '{{$.MentionableTeamsOrg}}/{{.Name}}', value: '{{$.MentionableTeamsOrg}}/{{.Name}}', diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index a9ca42fde60fc..c370b70f29878 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -1,6 +1,6 @@