Merge remote-tracking branch 'origin/main' into richmahn-11835-api-for-git-refs

This commit is contained in:
Richard Mahn 2022-10-24 07:42:47 -06:00
commit a54820f463
391 changed files with 8663 additions and 6408 deletions

View file

@ -626,6 +626,8 @@ steps:
commit_message: "[skip ci] Updated translations via Crowdin" commit_message: "[skip ci] Updated translations via Crowdin"
remote: "git@github.com:go-gitea/gitea.git" remote: "git@github.com:go-gitea/gitea.git"
environment: environment:
DRONE_COMMIT_AUTHOR_EMAIL: "teabot@gitea.io"
DRONE_COMMIT_AUTHOR: GiteaBot
GIT_PUSH_SSH_KEY: GIT_PUSH_SSH_KEY:
from_secret: git_push_ssh_key from_secret: git_push_ssh_key
@ -670,12 +672,14 @@ steps:
pull: always pull: always
settings: settings:
author_email: "teabot@gitea.io" author_email: "teabot@gitea.io"
author_name: GiteaBot author_name: "GiteaBot"
branch: main branch: main
commit: true commit: true
commit_message: "[skip ci] Updated licenses and gitignores " commit_message: "[skip ci] Updated licenses and gitignores"
remote: "git@github.com:go-gitea/gitea.git" remote: "git@github.com:go-gitea/gitea.git"
environment: environment:
DRONE_COMMIT_AUTHOR_EMAIL: "teabot@gitea.io"
DRONE_COMMIT_AUTHOR: "GiteaBot"
GIT_PUSH_SSH_KEY: GIT_PUSH_SSH_KEY:
from_secret: git_push_ssh_key from_secret: git_push_ssh_key

View file

@ -35,9 +35,6 @@ overrides:
rules: rules:
import/no-unresolved: [0] import/no-unresolved: [0]
import/no-extraneous-dependencies: [0] import/no-extraneous-dependencies: [0]
- files: ["*.test.js"]
env:
jest: true
- files: ["*.config.js"] - files: ["*.config.js"]
rules: rules:
import/no-unused-modules: [0] import/no-unused-modules: [0]

View file

@ -1,6 +1,7 @@
tasks: tasks:
- name: Setup - name: Setup
init: | init: |
cp -r contrib/ide/vscode .vscode
make deps make deps
make build make build
command: | command: |
@ -31,6 +32,7 @@ vscode:
- DavidAnson.vscode-markdownlint - DavidAnson.vscode-markdownlint
- johnsoncodehk.volar - johnsoncodehk.volar
- ms-azuretools.vscode-docker - ms-azuretools.vscode-docker
- zixuanchen.vitest-explorer
ports: ports:
- name: Gitea - name: Gitea

View file

@ -12,7 +12,6 @@ linters:
- dupl - dupl
#- gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time. #- gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time.
- gofmt - gofmt
- misspell
- gocritic - gocritic
- bidichk - bidichk
- ineffassign - ineffassign
@ -148,9 +147,6 @@ issues:
- path: models/issue_comment_list.go - path: models/issue_comment_list.go
linters: linters:
- dupl - dupl
- linters:
- misspell
text: '`Unknwon` is a misspelling of `Unknown`'
- path: models/update.go - path: models/update.go
linters: linters:
- unused - unused

View file

@ -4,6 +4,86 @@ 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 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). been added to each release, please refer to the [blog](https://blog.gitea.io).
## [1.17.3](https://github.com/go-gitea/gitea/releases/tag/v1.17.3) - 2022-10-15
* SECURITY
* Sanitize and Escape refs in git backend (#21464) (#21463)
* Bump `golang.org/x/text` (#21412) (#21413)
* Update bluemonday (#21281) (#21287)
* ENHANCEMENTS
* Fix empty container layer history and UI (#21251) (#21278)
* Use en-US as fallback when using other default language (#21200) (#21256)
* Make the vscode clone link respect transport protocol (#20557) (#21128)
* BUGFIXES
* Do DB update after merge in hammer context (#21401) (#21416)
* Add Num{Issues,Pulls} stats checks (#21404) (#21414)
* Stop logging CheckPath returns error: context canceled (#21064) (#21405)
* Parse OAuth Authorization header when request omits client secret (#21351) (#21374)
* Ignore port for loopback redirect URIs (#21293) (#21373)
* Set SemverCompatible to false for Conan packages (#21275) (#21366)
* Tag list should include draft releases with existing tags (#21263) (#21365)
* Fix linked account translation (#21331) (#21334)
* Make NuGet service index publicly accessible (#21242) (#21277)
* Foreign ID conflicts if ID is 0 for each item (#21271) (#21272)
* Use absolute links in feeds (#21229) (#21265)
* Prevent invalid behavior for file reviewing when loading more files (#21230) (#21234)
* Respect `REQUIRE_SIGNIN_VIEW` for packages (#20873) (#21232)
* Treat git object mode 40755 as directory (#21195) (#21218)
* Allow uppercase ASCII alphabet in PyPI package names (#21095) (#21217)
* Fix limited user cannot view himself's profile (#21212)
* Fix template bug of admin monitor (#21209)
* Fix reaction of issues (#21185) (#21196)
* Fix CSV diff for added/deleted files (#21189) (#21193)
* Fix pagination limit parameter problem (#21111)
* TESTING
* Fix missing m.Run() in TestMain (#21341)
* BUILD
* Use Go 1.19 fmt for Gitea 1.17, sync emoji data (#21239)
## [1.17.2](https://github.com/go-gitea/gitea/releases/tag/v1.17.2) - 2022-09-06
* SECURITY
* Double check CloneURL is acceptable (#20869) (#20892)
* Add more checks in migration code (#21011) (#21050)
* ENHANCEMENTS
* Fix hard-coded timeout and error panic in API archive download endpoint (#20925) (#21051)
* Improve arc-green code theme (#21039) (#21042)
* Enable contenthash in filename for dynamic assets (#20813) (#20932)
* Don't open new page for ext wiki on same repository (#20725) (#20910)
* Disable doctor logging on panic (#20847) (#20898)
* Remove calls to load Mirrors in user.Dashboard (#20855) (#20897)
* Update codemirror to 5.65.8 (#20875)
* Rework repo buttons (#20602, #20718) (#20719)
* BUGFIXES
* Ensure delete user deletes all comments (#21067) (#21068)
* Delete unreferenced packages when deleting a package version (#20977) (#21060)
* Redirect if user does not exist on admin pages (#20981) (#21059)
* Set uploadpack.allowFilter etc on gitea serv to enable partial clones with ssh (#20902) (#21058)
* Fix 500 on time in timeline API (#21052) (#21057)
* Fill the specified ref in webhook test payload (#20961) (#21055)
* Add another index for Action table on postgres (#21033) (#21054)
* Fix broken insecureskipverify handling in redis connection uris (#20967) (#21053)
* Add Dev, Peer and Optional dependencies to npm PackageMetadataVersion (#21017) (#21044)
* Do not add links to Posters or Assignees with ID < 0 (#20577) (#21037)
* Fix modified due date message (#20388) (#21032)
* Fix missed sort bug (#21006)
* Fix input.value attr for RequiredClaimName/Value (#20946) (#21001)
* Change review buttons to icons to make space for text (#20934) (#20978)
* Fix download archiver of a commit (#20962) (#20971)
* Return 404 NotFound if requested attachment does not exist (#20886) (#20941)
* Set no-tags in git fetch on compare (#20893) (#20936)
* Allow multiple metadata files for Maven packages (#20674) (#20916)
* Increase Content field size of gpg_key and public_key to MEDIUMTEXT (#20896) (#20911)
* Fix mirror address setting not working (#20850) (#20904)
* Fix push mirror address backend get error Address cause setting page display error (#20593) (#20901)
* Fix panic when an invalid oauth2 name is passed (#20820) (#20900)
* In PushMirrorsIterate and MirrorsIterate if limit is negative do not set it (#20837) (#20899)
* Ensure that graceful start-up is informed of unused SSH listener (#20877) (#20888)
* Pad GPG Key ID with preceding zeroes (#20878) (#20885)
* Fix SQL Query for `SearchTeam` (#20844) (#20872)
* Fix the mode of custom dir to 0700 in docker-rootless (#20861) (#20867)
* Fix UI mis-align for PR commit history (#20845) (#20859)
## [1.17.1](https://github.com/go-gitea/gitea/releases/tag/1.17.1) - 2022-08-17 ## [1.17.1](https://github.com/go-gitea/gitea/releases/tag/1.17.1) - 2022-08-17
* SECURITY * SECURITY

View file

@ -146,6 +146,16 @@ If your PR could cause a breaking change you must add a BREAKING section to this
To explain how this could affect users and how to mitigate these changes. To explain how this could affect users and how to mitigate these changes.
Once code review starts on your PR, do not rebase nor squash your branch as it makes it
difficult to review the new changes. Only if there is a need, sync your branch by merging
the base branch into yours. Don't worry about merge commits messing up your tree as
the final merge process squashes all commits into one, with the visible commit message (first
line) being the PR title + PR index and description being the PR's first comment.
Once your PR gets the `lgtm/done` label, don't worry about keeping it up-to-date or breaking
builds (unless there's a merge conflict or a request is made by a maintainer to make
modifications). It is the maintainer team's responsibility from this point to get it merged.
## Styleguide ## Styleguide
For imports you should use the following format (*without* the comments) For imports you should use the following format (*without* the comments)

View file

@ -49,3 +49,4 @@ silentcode <silentcode@senga.org> (@silentcodeg)
Wim <wim@42.be> (@42wim) Wim <wim@42.be> (@42wim)
xinyu <xinyu@nerv.org.cn> (@penlinux) xinyu <xinyu@nerv.org.cn> (@penlinux)
Jason Song <i@wolfogre.com> (@wolfogre) Jason Song <i@wolfogre.com> (@wolfogre)
Yarden Shoham <hrsi88@gmail.com> (@yardenshoham)

View file

@ -130,6 +130,7 @@ TEST_TAGS ?= sqlite sqlite_unlock_notify
TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMANTIC_WORK_DIR)/node_modules $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR) $(GO_LICENSE_TMP_DIR) TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMANTIC_WORK_DIR)/node_modules $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR) $(GO_LICENSE_TMP_DIR)
GO_DIRS := cmd tests models modules routers build services tools GO_DIRS := cmd tests models modules routers build services tools
WEB_DIRS := web_src/js web_src/less
GO_SOURCES := $(wildcard *.go) GO_SOURCES := $(wildcard *.go)
GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" -not -path modules/options/bindata.go -not -path modules/public/bindata.go -not -path modules/templates/bindata.go) GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" -not -path modules/options/bindata.go -not -path modules/public/bindata.go -not -path modules/templates/bindata.go)
@ -263,11 +264,24 @@ clean:
.PHONY: fmt .PHONY: fmt
fmt: fmt:
@MISSPELL_PACKAGE=$(MISSPELL_PACKAGE) GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}' GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}'
$(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl')) $(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl'))
@# strip whitespace after '{{' and before `}}` unless there is only whitespace before it @# strip whitespace after '{{' and before `}}` unless there is only whitespace before it
@$(SED_INPLACE) -e 's/{{[ ]\{1,\}/{{/g' -e '/^[ ]\{1,\}}}/! s/[ ]\{1,\}}}/}}/g' $(TEMPLATES) @$(SED_INPLACE) -e 's/{{[ ]\{1,\}/{{/g' -e '/^[ ]\{1,\}}}/! s/[ ]\{1,\}}}/}}/g' $(TEMPLATES)
.PHONY: fmt-check
fmt-check: fmt
@diff=$$(git diff $(GO_SOURCES) templates $(WEB_DIRS)); \
if [ -n "$$diff" ]; then \
echo "Please run 'make fmt' and commit the result:"; \
echo "$${diff}"; \
exit 1; \
fi
.PHONY: misspell-check
misspell-check:
go run $(MISSPELL_PACKAGE) -error $(GO_DIRS) $(WEB_DIRS)
.PHONY: vet .PHONY: vet
vet: vet:
@echo "Running go vet..." @echo "Running go vet..."
@ -311,22 +325,6 @@ errcheck:
@echo "Running errcheck..." @echo "Running errcheck..."
$(GO) run $(ERRCHECK_PACKAGE) $(GO_PACKAGES) $(GO) run $(ERRCHECK_PACKAGE) $(GO_PACKAGES)
.PHONY: fmt-check
fmt-check:
@# get all go files and run gitea-fmt (with gofmt) on them
@diff=$$(MISSPELL_PACKAGE=$(MISSPELL_PACKAGE) GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -l '{file-list}'); \
if [ -n "$$diff" ]; then \
echo "Please run 'make fmt' and commit the result:"; \
echo "$${diff}"; \
exit 1; \
fi
@diff2=$$(git diff templates); \
if [ -n "$$diff2" ]; then \
echo "Please run 'make fmt' and commit the result:"; \
echo "$${diff2}"; \
exit 1; \
fi
.PHONY: checks .PHONY: checks
checks: checks-frontend checks-backend checks: checks-frontend checks-backend
@ -334,7 +332,7 @@ checks: checks-frontend checks-backend
checks-frontend: lockfile-check svg-check checks-frontend: lockfile-check svg-check
.PHONY: checks-backend .PHONY: checks-backend
checks-backend: tidy-check swagger-check swagger-validate checks-backend: tidy-check swagger-check fmt-check misspell-check swagger-validate
.PHONY: lint .PHONY: lint
lint: lint-frontend lint-backend lint: lint-frontend lint-backend
@ -372,7 +370,7 @@ test-backend:
.PHONY: test-frontend .PHONY: test-frontend
test-frontend: node_modules test-frontend: node_modules
@NODE_OPTIONS="--experimental-vm-modules --no-warnings" npx jest --color npx vitest
.PHONY: test-check .PHONY: test-check
test-check: test-check:

View file

@ -151,6 +151,7 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
<a href="https://opencollective.com/gitea/sponsor/7/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/7/avatar.svg"></a> <a href="https://opencollective.com/gitea/sponsor/7/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/8/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/8/avatar.svg"></a> <a href="https://opencollective.com/gitea/sponsor/8/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/9/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/9/avatar.svg"></a> <a href="https://opencollective.com/gitea/sponsor/9/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/9/avatar.svg"></a>
<a href="https://cynkra.com/" target="_blank"><img src="https://images.opencollective.com/cynkra/logo/square/64/192.png"></a>
## FAQ ## FAQ

View file

@ -20,7 +20,7 @@ import (
) )
// Windows has a limitation for command line arguments, the size can not exceed 32KB. // Windows has a limitation for command line arguments, the size can not exceed 32KB.
// So we have to feed the files to some tools (like gofmt/misspell) batch by batch // So we have to feed the files to some tools (like gofmt) batch by batch
// We also introduce a `gitea-fmt` command, it does better import formatting than gofmt/goimports. `gitea-fmt` calls `gofmt` internally. // We also introduce a `gitea-fmt` command, it does better import formatting than gofmt/goimports. `gitea-fmt` calls `gofmt` internally.
@ -195,7 +195,6 @@ Options:
Commands: Commands:
%[1]s gofmt ... %[1]s gofmt ...
%[1]s misspell ...
Arguments: Arguments:
{file-list} the file list {file-list} the file list
@ -206,6 +205,17 @@ Example:
`, "file-batch-exec") `, "file-batch-exec")
} }
func getGoVersion() string {
goModFile, err := os.ReadFile("go.mod")
if err != nil {
log.Fatalf(`Faild to read "go.mod": %v`, err)
os.Exit(1)
}
goModVersionRegex := regexp.MustCompile(`go \d+\.\d+`)
goModVersionLine := goModVersionRegex.Find(goModFile)
return string(goModVersionLine[3:])
}
func newFileCollectorFromMainOptions(mainOptions map[string]string) (fc *fileCollector, err error) { func newFileCollectorFromMainOptions(mainOptions map[string]string) (fc *fileCollector, err error) {
fileFilter := mainOptions["file-filter"] fileFilter := mainOptions["file-filter"]
if fileFilter == "" { if fileFilter == "" {
@ -228,9 +238,9 @@ func containsString(a []string, s string) bool {
return false return false
} }
func giteaFormatGoImports(files []string, hasChangedFiles, doWriteFile bool) error { func giteaFormatGoImports(files []string, doWriteFile bool) error {
for _, file := range files { for _, file := range files {
if err := codeformat.FormatGoImports(file, hasChangedFiles, doWriteFile); err != nil { if err := codeformat.FormatGoImports(file, doWriteFile); err != nil {
log.Printf("failed to format go imports: %s, err=%v", file, err) log.Printf("failed to format go imports: %s, err=%v", file, err)
return err return err
} }
@ -269,10 +279,8 @@ func main() {
if containsString(subArgs, "-d") { if containsString(subArgs, "-d") {
log.Print("the -d option is not supported by gitea-fmt") log.Print("the -d option is not supported by gitea-fmt")
} }
cmdErrors = append(cmdErrors, giteaFormatGoImports(files, containsString(subArgs, "-l"), containsString(subArgs, "-w"))) cmdErrors = append(cmdErrors, giteaFormatGoImports(files, containsString(subArgs, "-w")))
cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("GOFUMPT_PACKAGE"), "-extra", "-lang", "1.17"}, substArgs...))) cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("GOFUMPT_PACKAGE"), "-extra", "-lang", getGoVersion()}, substArgs...)))
case "misspell":
cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("MISSPELL_PACKAGE")}, substArgs...)))
default: default:
log.Fatalf("unknown cmd: %s %v", subCmd, subArgs) log.Fatalf("unknown cmd: %s %v", subCmd, subArgs)
} }

View file

@ -7,7 +7,6 @@ package codeformat
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"io" "io"
"os" "os"
"sort" "sort"
@ -159,7 +158,7 @@ func formatGoImports(contentBytes []byte) ([]byte, error) {
} }
// FormatGoImports format the imports by our rules (see unit tests) // FormatGoImports format the imports by our rules (see unit tests)
func FormatGoImports(file string, doChangedFiles, doWriteFile bool) error { func FormatGoImports(file string, doWriteFile bool) error {
f, err := os.Open(file) f, err := os.Open(file)
if err != nil { if err != nil {
return err return err
@ -183,10 +182,6 @@ func FormatGoImports(file string, doChangedFiles, doWriteFile bool) error {
return nil return nil
} }
if doChangedFiles {
fmt.Println(file)
}
if doWriteFile { if doWriteFile {
f, err = os.OpenFile(file, os.O_TRUNC|os.O_WRONLY, 0o644) f, err = os.OpenFile(file, os.O_TRUNC|os.O_WRONLY, 0o644)
if err != nil { if err != nil {

View file

@ -146,6 +146,10 @@ It can be used for backup and capture Gitea server image to send to maintainer`,
Name: "skip-package-data", Name: "skip-package-data",
Usage: "Skip package data", Usage: "Skip package data",
}, },
cli.BoolFlag{
Name: "skip-index",
Usage: "Skip bleve index data",
},
cli.GenericFlag{ cli.GenericFlag{
Name: "type", Name: "type",
Value: outputTypeEnum, Value: outputTypeEnum,
@ -327,6 +331,11 @@ func runDump(ctx *cli.Context) error {
excludes = append(excludes, opts.ProviderConfig) excludes = append(excludes, opts.ProviderConfig)
} }
if ctx.IsSet("skip-index") && ctx.Bool("skip-index") {
excludes = append(excludes, setting.Indexer.RepoPath)
excludes = append(excludes, setting.Indexer.IssuePath)
}
excludes = append(excludes, setting.RepoRootPath) excludes = append(excludes, setting.RepoRootPath)
excludes = append(excludes, setting.LFS.Path) excludes = append(excludes, setting.LFS.Path)
excludes = append(excludes, setting.Attachment.Path) excludes = append(excludes, setting.Attachment.Path)

View file

@ -2,14 +2,43 @@
DIR=/var/lib/gitea DIR=/var/lib/gitea
USER=git USER=git
HOME=/home/${USER}
GITEA_WORK_DIR=${DIR}
EXECUTABLE=/usr/local/bin/gitea
export USER
export HOME
export GITEA_WORK_DIR
name=$RC_SVCNAME
cfgfile="/etc/$RC_SVCNAME/app.ini"
command="${EXECUTABLE}"
command_user="${USER}"
command_args="web -c /etc/$RC_SVCNAME/app.ini"
command_background="yes"
pidfile="/run/$RC_SVCNAME/$RC_SVCNAME.pid"
start_stop_daemon_args="--user ${USER} --chdir ${DIR}" start_stop_daemon_args="--user ${USER} --chdir ${DIR}"
command="/usr/local/bin/gitea"
command_args="web -c /etc/gitea/app.ini"
command_background=yes
pidfile=/run/gitea.pid
depend() depend()
{ {
need net need net
###
# Don't forget to add the database service requirements
###
#after postgresql
#after mysql
#after mariadb
#after memcached
#after redis
}
start_pre()
{
checkpath --directory --owner $command_user:$command_user --mode 0750 \
/run/$RC_SVCNAME /var/log/$RC_SVCNAME
##
# If you want to bind Gitea to a port below 1024, uncomment
# the value below
##
#setcap cap_net_bind_service=+ep "${EXECUTABLE}"
} }

View file

@ -14,7 +14,6 @@ import (
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"net/url"
"os" "os"
"os/exec" "os/exec"
"os/user" "os/user"
@ -34,6 +33,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers"
markup_service "code.gitea.io/gitea/services/markup"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/config"
@ -62,11 +62,7 @@ func runPR() {
} }
setting.AppWorkPath = curDir setting.AppWorkPath = curDir
setting.StaticRootPath = curDir setting.StaticRootPath = curDir
setting.GravatarSourceURL, err = url.Parse("https://secure.gravatar.com/avatar/") setting.GravatarSource = "https://secure.gravatar.com/avatar/"
if err != nil {
log.Fatalf("url.Parse: %v\n", err)
}
setting.AppURL = "http://localhost:8080/" setting.AppURL = "http://localhost:8080/"
setting.HTTPPort = "8080" setting.HTTPPort = "8080"
setting.SSH.Domain = "localhost" setting.SSH.Domain = "localhost"
@ -117,7 +113,7 @@ func runPR() {
log.Printf("[PR] Setting up router\n") log.Printf("[PR] Setting up router\n")
// routers.GlobalInit() // routers.GlobalInit()
external.RegisterRenderers() external.RegisterRenderers()
markup.Init() markup.Init(markup_service.ProcessorHelper())
c := routers.NormalRoutes(graceful.GetManager().HammerContext()) c := routers.NormalRoutes(graceful.GetManager().HammerContext())
log.Printf("[PR] Ready for testing !\n") log.Printf("[PR] Ready for testing !\n")

View file

@ -18,7 +18,7 @@ params:
description: Git with a cup of tea description: Git with a cup of tea
author: The Gitea Authors author: The Gitea Authors
website: https://docs.gitea.io website: https://docs.gitea.io
version: 1.17.1 version: 1.17.3
minGoVersion: 1.18 minGoVersion: 1.18
goVersion: 1.19 goVersion: 1.19
minNodeVersion: 14 minNodeVersion: 14

View file

@ -23,7 +23,7 @@ menu:
To get a quick working development environment you could use Gitpod. To get a quick working development environment you could use Gitpod.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/go-gitea/gitea) [![Open in Gitpod](/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/go-gitea/gitea)
## Installing go ## Installing go

View file

@ -44,6 +44,12 @@ To use the Authorization Code Grant as a third party application it is required
Currently Gitea does not support scopes (see [#4300](https://github.com/go-gitea/gitea/issues/4300)) and all third party applications will be granted access to all resources of the user and their organizations. Currently Gitea does not support scopes (see [#4300](https://github.com/go-gitea/gitea/issues/4300)) and all third party applications will be granted access to all resources of the user and their organizations.
## Client types
Gitea supports both confidential and public client types, [as defined by RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749#section-2.1).
For public clients, a redirect URI of a loopback IP address such as `http://127.0.0.1/` allows any port. Avoid using `localhost`, [as recommended by RFC 8252](https://datatracker.ietf.org/doc/html/rfc8252#section-8.3).
## Example ## Example
**Note:** This example does not use PKCE. **Note:** This example does not use PKCE.

View file

@ -21,7 +21,7 @@ menu:
To help decide if Gitea is suited for your needs, here is how it compares to other Git self hosted options. To help decide if Gitea is suited for your needs, here is how it compares to other Git self hosted options.
Be warned that we don't regularly check for feature changes in other products, so this list may be outdated. If you find anything that needs to be updated in the table below, please report it in an [issue on GitHub](https://github.com/go-gitea/gitea/issues). Be warned that we don't regularly check for feature changes in other products, so this list may be outdated. If you find anything that needs to be updated in the table below, please [open an issue](https://github.com/go-gitea/gitea/issues/new/choose).
_Symbols used in table:_ _Symbols used in table:_
@ -34,102 +34,110 @@ _Symbols used in table:_
## General Features ## General Features
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | | Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
| ----------------------------------- | ---------------------------------------------------| ---- | --------- | --------- | --------- | -------------- | ------------ | | ------------------------------------------------ | --------------------------------------------------- | ---- | --------- | --------- | --------- | --------- | ------------ |
| Open source and free | ✓ | ✓ | ✘ | ✓ | ✘ | ✘ | ✓ | | Open source and free | ✓ | ✓ | ✘ | ✓ | ✘ | ✘ | ✓ |
| Low resource usage (RAM/CPU) | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ | | Low RAM/ CPU usage | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ |
| Multiple database support | ✓ | ✓ | ✘ | | | ✓ | ✓ | | Multiple database support | ✓ | ✓ | ✘ | | | ✓ | ✓ |
| Multiple OS support | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ | | Multiple OS support | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ |
| Easy upgrade process | ✓ | ✓ | ✘ | ✓ | ✓ | ✘ | ✓ | | Easy upgrades | ✓ | ✓ | ✘ | ✓ | ✓ | ✘ | ✓ |
| Markdown support | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Telemetry | **✘** | ✘ | ✓ | ✓ | ✓ | ✓ | ? |
| Orgmode support | ✓ | ✘ | ✓ | ✘ | ✘ | ✘ | ? |
| CSV support | ✓ | ✘ | ✓ | ✘ | ✘ | ✓ | ? |
| Third-party render tool support | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ | ? | | Third-party render tool support | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ | ? |
| Static Git-powered pages | [](https://github.com/go-gitea/gitea/issues/302) | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | WebAuthn (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ? |
| Integrated Git-powered wiki | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ (cloud only) | ✘ | | Extensive API | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Built-in Package/Container Registry | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Sync commits to an external repo (push mirror) | ✓ | ✓ | ✘ | ✓ | ✓ | ✘ | ✓ |
| Sync commits from an external repo (pull mirror) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ? |
| Light and Dark Theme | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ? |
| Custom Theme Support | ✓ | ✓ | ✘ | ✘ | ✘ | ✓ | ✘ |
| Markdown support | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| CSV support | ✓ | ✘ | ✓ | ✘ | ✘ | ✓ | ? |
| 'GitHub / GitLab pages' | [](https://github.com/go-gitea/gitea/issues/302) | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Repo-specific wiki (as a repo itself) | ✓ | ✓ | ✓ | ✓ | ✓ | / | ✘ |
| Deploy Tokens | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Deploy Tokens | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Repository Tokens with write rights | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | Repository Tokens with write rights | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Built-in Package/Container Registry | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | RSS Feeds | ✓ | ✘ | ✓ | ✘ | ✘ | ✘ | ✘ |
| External git mirroring | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ | | Built-in CI/CD | [](https://github.com/go-gitea/gitea/issues/13539) | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| WebAuthn (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ? |
| Built-in CI/CD | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Subgroups: groups within groups | [](https://github.com/go-gitea/gitea/issues/1872) | ✘ | ✘ | ✓ | ✓ | ✘ | ✓ | | Subgroups: groups within groups | [](https://github.com/go-gitea/gitea/issues/1872) | ✘ | ✘ | ✓ | ✓ | ✘ | ✓ |
| Interaction with other instances | [/](https://github.com/go-gitea/gitea/issues/18240) | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ |
| Mermaid diagrams in Markdown | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Mermaid diagrams in Markdown | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Math syntax in Markdown | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Math syntax in Markdown | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
## Code management ## Code management
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | | Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
| -------------------------------------------- | ------------------------------------------------ | ---- | --------- | --------- | --------- | --------- | ------------ | | ------------------------------------------- | --------------------------------------------------- | ---- | --------- | --------- | --------- | --------- | ------------ |
| Repository topics | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Repository topics | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Repository code search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | Repository code search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Global code search | ✓ | ✘ | ✓ | ✘ | ✓ | ✓ | ✓ | | Global code search | ✓ | ✘ | ✓ | ✘ | ✓ | ✓ | ✓ |
| Git LFS 2.0 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | Git LFS 2.0 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Group Milestones | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | Group Milestones | [](https://github.com/go-gitea/gitea/issues/14622) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Granular user roles (Code, Issues, Wiki etc) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | Granular user roles (Code, Issues, Wiki, …) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Verified Committer | | ✘ | ? | ✓ | ✓ | ✓ | ✘ | | Verified Committer | | ✘ | ? | ✓ | ✓ | ✓ | ✘ |
| GPG Signed Commits | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | GPG Signed Commits | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| SSH Signed Commits | ✓ | ✘ | ✘ | ✘ | ✘ | ? | ? | | SSH Signed Commits | ✓ | ✘ | ✓ | ✘ | ✘ | ? | ? |
| Reject unsigned commits | [](https://github.com/go-gitea/gitea/pull/9708) | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | Reject unsigned commits | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Migrating repos from other services | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Repository Activity page | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | Repository Activity page | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Branch manager | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | Branch manager | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Create new branches | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Create new branches | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Web code editor | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Web code editor | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Commit graph | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | Commit graph | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Template Repositories | [](https://github.com/go-gitea/gitea/pull/8768) | ✘ | ✓ | ✘ | ✓ | ✓ | ✘ | | Template Repositories | ✓ | ✘ | ✓ | ✘ | ✓ | ✓ | ✘ |
| Git Blame | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Visual comparison of image changes | ✓ | ✘ | ✓ | ? | ? | ? | ? |
## Issue Tracker ## Issue Tracker
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | | Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
| ------------------------------- | -------------------------------------------------- | --------------------------------------------- | --------- | ----------------------------------------------------------------------- | --------- | -------------- | ------------ | | ----------------------------- | --------------------------------------------------- | ---- | --------- | --------- | --------- | --------- | ------------ |
| Issue tracker | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ (cloud only) | ✘ | | Issue tracker | ✓ | ✓ | ✓ | ✓ | ✓ | / | ✘ |
| Issue templates | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | | Issue templates | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Labels | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | | Labels | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Time tracking | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Time tracking | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Multiple assignees for issues | ✓ | ✘ | ✓ | ✘ | ✓ | ✘ | ✘ | | Multiple assignees for issues | ✓ | ✘ | ✓ | ✘ | ✓ | ✘ | ✘ |
| Related issues | ✘ | ✘ | | [](https://docs.gitlab.com/ce/user/project/issues/related_issues.html) | ✓ | ✘ | ✘ | | Related issues | ✘ | ✘ | | ✓ | ✓ | ✘ | ✘ |
| Confidential issues | [](https://github.com/go-gitea/gitea/issues/3217) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | Confidential issues | [](https://github.com/go-gitea/gitea/issues/3217) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Comment reactions | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Comment reactions | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Lock Discussion | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Lock Discussion | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Batch issue handling | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Batch issue handling | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Issue Boards (Kanban) | [](https://github.com/go-gitea/gitea/pull/8346) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | Issue Boards (Kanban) | [/](https://github.com/go-gitea/gitea/issues/14710) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Create new branches from issues | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | Create branch from issue | [](https://github.com/go-gitea/gitea/issues/20226) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Convert comment to new issue | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Issue search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | | Issue search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Global issue search | [](https://github.com/go-gitea/gitea/issues/2434) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | | Global issue search | [/](https://github.com/go-gitea/gitea/issues/2434) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Issue dependency | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | | Issue dependency | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ |
| Create issue via email | [](https://github.com/go-gitea/gitea/issues/6226) | [](https://github.com/gogs/gogs/issues/2602) | ✘ | ✘ | ✓ | ✓ | ✘ | | Create issue via email | [](https://github.com/go-gitea/gitea/issues/6226) | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ |
| Service Desk | [](https://github.com/go-gitea/gitea/issues/6219) | ✘ | ✘ | [](https://gitlab.com/groups/gitlab-org/-/epics/3103) | ✓ | ✘ | ✘ | | Service Desk | [](https://github.com/go-gitea/gitea/issues/6219) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
## Pull/Merge requests ## Pull/Merge requests
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | | Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
| ----------------------------------------------- | -------------------------------------------------- | ---- | --------- | --------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------ | ------------ | | ----------------------------------------------- | -------------------------------------------------- | ---- | --------- | --------- | --------- | --------- | ------------ |
| Pull/Merge requests | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Pull/Merge requests | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Squash merging | ✓ | ✘ | ✓ | [](https://docs.gitlab.com/ce/user/project/merge_requests/squash_and_merge.html) | ✓ | ✓ | ✓ | | Squash merging | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Rebase merging | ✓ | ✓ | ✓ | ✘ | | ✘ | ✓ | | Rebase merging | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Pull/Merge request inline comments | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | Pull/Merge request inline comments | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Pull/Merge request approval | ✓ | ✘ | | ✓ | ✓ | ✓ | ✓ | | Pull/Merge request approval | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Merge conflict resolution | [](https://github.com/go-gitea/gitea/issues/5158) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | | Merge conflict resolution | [](https://github.com/go-gitea/gitea/issues/9014) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Restrict push and merge access to certain users | ✓ | ✘ | ✓ | | ✓ | ✓ | ✓ | | Restrict push and merge access to certain users | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Revert specific commits or a merge request | [](https://github.com/go-gitea/gitea/issues/5158) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | | Revert specific commits | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Pull/Merge requests templates | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | | Pull/Merge requests templates | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Cherry-picking changes | [](https://github.com/go-gitea/gitea/issues/5158) | ✘ | ✘ | | ✓ | ✘ | ✘ | | Cherry-picking changes | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Download Patch | ✓ | ✘ | ✓ | ✓ | ✓ | [/](https://jira.atlassian.com/plugins/servlet/mobile#issue/BCLOUD-8323) | ✘ | | Download Patch | ✓ | ✘ | ✓ | ✓ | ✓ | / | ✘ |
## 3rd-party integrations ## 3rd-party integrations
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | | Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
| ---------------------------------------------- | ------------------------------------------------ | ---- | --------- | --------- | --------- | --------- | ------------ | | ---------------------------------------------- | ------------------------------------------------ | ---- | --------- | --------- | --------- | --------- | ------------ |
| Webhook support | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Webhooks | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Custom Git Hooks | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Git Hooks | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| AD / LDAP integration | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | AD / LDAP integration | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Multiple LDAP / AD server support | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ | | Multiple LDAP / AD server support | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ |
| LDAP user synchronization | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | LDAP user synchronization | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| SAML 2.0 service provider | [](https://github.com/go-gitea/gitea/issues/5512) | [](https://github.com/gogs/gogs/issues/1221) | ✓ | ✓ | ✓ | ✓ | ✘ | | SAML 2.0 service provider | [](https://github.com/go-gitea/gitea/issues/5512) | | ✓ | ✓ | ✓ | ✓ | ✘ |
| OpenId Connect support | ✓ | ✘ | ✓ | ✓ | ✓ | ? | ✘ | | OpenID Connect support | ✓ | ✘ | ✓ | ✓ | ✓ | ? | ✘ |
| OAuth 2.0 integration (external authorization) | ✓ | ✘ | | ✓ | ✓ | ? | ✓ | | OAuth 2.0 integration (external authorization) | ✓ | ✘ | | ✓ | ✓ | ? | ✓ |
| Act as OAuth 2.0 provider | [](https://github.com/go-gitea/gitea/pull/5378) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | | Act as OAuth 2.0 provider | | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Two factor authentication (2FA) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | | Two factor authentication (2FA) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Mattermost/Slack integration | ✓ | ✓ | | ✓ | ✓ | | ✓ | | Integration with the most common services | ✓ | / | | ✓ | ✓ | | ✓ |
| Discord integration | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | | Incorporate external CI/CD | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Microsoft Teams integration | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| External CI/CD status display | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |

View file

@ -71,7 +71,7 @@ choco install gitea
macOS 平台下当前我们仅支持通过 `brew` 来安装。如果你没有安装 [Homebrew](http://brew.sh/),你也可以查看 [从二进制安装]({{< relref "from-binary.zh-cn.md" >}})。在你安装了 `brew` 之后, 你可以执行以下命令: macOS 平台下当前我们仅支持通过 `brew` 来安装。如果你没有安装 [Homebrew](http://brew.sh/),你也可以查看 [从二进制安装]({{< relref "from-binary.zh-cn.md" >}})。在你安装了 `brew` 之后, 你可以执行以下命令:
``` ```
brew tap go-gitea/gitea brew tap gitea/tap https://gitea.com/gitea/homebrew-gitea
brew install gitea brew install gitea
``` ```

View file

@ -14,7 +14,7 @@ menu:
# NuGet Packages Repository # NuGet Packages Repository
Publish [NuGet](https://www.nuget.org/) packages for your user or organization. The package registry supports [NuGet Symbol Packages](https://docs.microsoft.com/en-us/nuget/create-packages/symbol-packages-snupkg) too. Publish [NuGet](https://www.nuget.org/) packages for your user or organization. The package registry supports the V2 and V3 API protocol and you can work with [NuGet Symbol Packages](https://docs.microsoft.com/en-us/nuget/create-packages/symbol-packages-snupkg) too.
**Table of Contents** **Table of Contents**

View file

@ -13,6 +13,12 @@ menu:
identifier: "reverse-proxies" identifier: "reverse-proxies"
--- ---
# 反向代理
**目录**
{{< toc >}}
## 使用 Nginx 作为反向代理服务 ## 使用 Nginx 作为反向代理服务
如果您想使用 Nginx 作为 Gitea 的反向代理服务,您可以参照以下 `nginx.conf` 配置中 `server``http` 部分: 如果您想使用 Nginx 作为 Gitea 的反向代理服务,您可以参照以下 `nginx.conf` 配置中 `server``http` 部分:
@ -24,6 +30,10 @@ server {
location / { location / {
proxy_pass http://localhost:3000; proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
} }
} }
``` ```
@ -41,6 +51,10 @@ server {
location /git/ { location /git/ {
# 注意: 反向代理后端 URL 的最后需要有一个路径符号 # 注意: 反向代理后端 URL 的最后需要有一个路径符号
proxy_pass http://localhost:3000/; proxy_pass http://localhost:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
} }
} }
``` ```

23
docs/static/open-in-gitpod.svg vendored Normal file
View file

@ -0,0 +1,23 @@
<svg width="160" height="45" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d)">
<rect x="2" y="2" width="156" height="40" rx="16" fill="#F9F9F9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.425 11.174c.604 1.114.233 2.53-.83 3.164l-6.986 4.166a.378.378 0 00-.18.325v6.748c0 .134.069.258.18.325l5.714 3.407c.11.066.244.066.354 0l5.714-3.407a.378.378 0 00.18-.325V21.29l-4.986 2.936c-1.067.628-2.416.231-3.015-.886-.6-1.118-.22-2.532.846-3.16l7.008-4.127c2.048-1.206 4.576.345 4.576 2.806v6.718c0 1.803-.924 3.467-2.42 4.36l-5.713 3.407a4.596 4.596 0 01-4.734 0l-5.714-3.408C18.924 29.044 18 27.38 18 25.576V18.83c0-1.803.924-3.467 2.42-4.36l6.985-4.165c1.063-.634 2.415-.245 3.02.87z" fill="url(#paint0_linear)"/>
<path fill="#F9F9F9" d="M47 12.5h95v-1H47z"/>
<path d="M52.538 27.752c2.744 0 4.844-1.876 4.844-5.152 0-3.108-2.1-5.152-4.844-5.152s-4.802 2.002-4.802 5.152c0 3.29 2.058 5.152 4.802 5.152zm0-1.554c-1.736 0-2.912-1.316-2.912-3.598 0-2.226 1.162-3.598 2.912-3.598s2.954 1.4 2.954 3.598c0 2.31-1.218 3.598-2.954 3.598zm7.89 4.158V27.22c0-.196-.013-.378-.055-.658.434.7 1.022 1.19 2.17 1.19 1.736 0 3.066-1.414 3.066-3.626 0-2.17-1.19-3.682-2.996-3.682-1.078 0-1.806.476-2.24 1.204.042-.28.056-.462.056-.672v-.308H58.72v9.688h1.708zm1.695-3.948c-1.036 0-1.764-.938-1.764-2.31 0-1.414.742-2.296 1.764-2.296 1.092 0 1.75.952 1.75 2.296 0 1.372-.7 2.31-1.75 2.31zm7.866 1.344c1.848 0 3.052-1.078 3.192-2.478h-1.736c-.112.714-.714 1.134-1.456 1.134-1.036 0-1.722-.826-1.736-1.904h4.97v-.378c0-2.226-1.204-3.682-3.29-3.682-1.988 0-3.43 1.47-3.43 3.626 0 2.366 1.442 3.682 3.486 3.682zm-1.75-4.48c.098-.896.756-1.554 1.68-1.554.924 0 1.526.63 1.554 1.554H68.24zm8.006 4.228v-4.004c0-.952.616-1.694 1.456-1.694.798 0 1.288.63 1.288 1.638v4.06h1.708v-4.312c0-1.68-.896-2.744-2.408-2.744-1.078 0-1.722.518-2.1 1.204.042-.266.056-.476.056-.672v-.308h-1.708V27.5h1.708zm8.911-7.868h1.792V17.84h-1.792v1.792zm.042 1.036V27.5h1.708v-6.832h-1.708zm5.097 6.832v-4.004c0-.952.616-1.694 1.456-1.694.798 0 1.288.63 1.288 1.638v4.06h1.708v-4.312c0-1.68-.896-2.744-2.408-2.744-1.078 0-1.722.518-2.1 1.204.042-.266.056-.476.056-.672v-.308h-1.708V27.5h1.708zm13.238.252c1.526 0 2.52-.658 2.982-1.54-.07.322-.098.644-.098.98v.308h1.68v-5.222h-4.34v1.554h2.66v.07c0 1.4-1.134 2.296-2.59 2.296-1.792 0-3.024-1.372-3.024-3.598s1.26-3.598 3.066-3.598c1.302 0 2.17.756 2.296 1.736h1.89c-.182-1.904-1.764-3.29-4.214-3.29-2.954 0-4.928 2.128-4.928 5.152 0 3.136 1.848 5.152 4.62 5.152zm6.063-8.12h1.792V17.84h-1.792v1.792zm.042 1.036V27.5h1.708v-6.832h-1.708zm6.413 6.958c.434 0 .84-.07 1.008-.126v-1.288c-.168.028-.35.042-.518.042-.728 0-1.008-.42-1.008-1.134v-3.094h1.68v-1.358h-1.68v-2.464h-1.708v2.464h-1.554v1.358h1.554v3.346c0 1.526.77 2.254 2.226 2.254zm3.961 2.73V27.22c0-.196-.014-.378-.056-.658.434.7 1.022 1.19 2.17 1.19 1.736 0 3.066-1.414 3.066-3.626 0-2.17-1.19-3.682-2.996-3.682-1.078 0-1.806.476-2.24 1.204.042-.28.056-.462.056-.672v-.308h-1.708v9.688h1.708zm1.694-3.948c-1.036 0-1.764-.938-1.764-2.31 0-1.414.742-2.296 1.764-2.296 1.092 0 1.75.952 1.75 2.296 0 1.372-.7 2.31-1.75 2.31zm7.88 1.344c2.058 0 3.514-1.372 3.514-3.64 0-2.24-1.456-3.668-3.514-3.668-2.044 0-3.5 1.428-3.5 3.668 0 2.268 1.442 3.64 3.5 3.64zm0-1.344c-1.064 0-1.764-.84-1.764-2.296 0-1.484.728-2.31 1.764-2.31 1.05 0 1.778.826 1.778 2.31 0 1.456-.714 2.296-1.778 2.296zm7.551 1.344c1.26 0 1.876-.686 2.142-1.19-.056.238-.056.42-.056.658v.28h1.708v-9.8h-1.708v3.276c0 .21 0 .42.056.672-.392-.658-1.05-1.204-2.114-1.204-1.596 0-3.15 1.218-3.15 3.668 0 2.408 1.316 3.64 3.122 3.64zm.406-1.344c-1.022 0-1.792-.896-1.792-2.31 0-1.358.77-2.296 1.792-2.296s1.778.896 1.778 2.296c0 1.372-.756 2.31-1.778 2.31z" fill="#12100C"/>
</g>
<defs>
<linearGradient id="paint0_linear" x1="33.806" y1="13.629" x2="22.389" y2="30.86" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFB45B"/>
<stop offset="1" stop-color="#FF8A00"/>
</linearGradient>
<filter id="filter0_d" x="0" y=".5" width="160" height="44" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy=".5"/>
<feGaussianBlur stdDeviation="1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

2
go.mod
View file

@ -6,7 +6,7 @@ require (
code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b
code.gitea.io/sdk/gitea v0.15.1 code.gitea.io/sdk/gitea v0.15.1
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb gitea.com/go-chi/binding v0.0.0-20221013104517-b29891619681
gitea.com/go-chi/cache v0.2.0 gitea.com/go-chi/cache v0.2.0
gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5 gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5
gitea.com/go-chi/session v0.0.0-20211218221615-e3605d8b28b8 gitea.com/go-chi/session v0.0.0-20211218221615-e3605d8b28b8

4
go.sum
View file

@ -81,8 +81,8 @@ contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcig
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg=
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb h1:Yy0Bxzc8R2wxiwXoG/rECGplJUSpXqCsog9PuJFgiHs= gitea.com/go-chi/binding v0.0.0-20221013104517-b29891619681 h1:MMSPgnVULVwV9kEBgvyEUhC9v/uviZ55hPJEMjpbNR4=
gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc= gitea.com/go-chi/binding v0.0.0-20221013104517-b29891619681/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc=
gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0= gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0=
gitea.com/go-chi/cache v0.2.0 h1:E0npuTfDW6CT1yD8NMDVc1SK6IeRjfmRL2zlEsCEd7w= gitea.com/go-chi/cache v0.2.0 h1:E0npuTfDW6CT1yD8NMDVc1SK6IeRjfmRL2zlEsCEd7w=
gitea.com/go-chi/cache v0.2.0/go.mod h1:iQlVK2aKTZ/rE9UcHyz9pQWGvdP9i1eI2spOpzgCrtE= gitea.com/go-chi/cache v0.2.0/go.mod h1:iQlVK2aKTZ/rE9UcHyz9pQWGvdP9i1eI2spOpzgCrtE=

View file

@ -1,15 +0,0 @@
// to run tests with ES6 module, node must run with "--experimental-vm-modules", or see Makefile's "test-frontend" for reference
export default {
rootDir: 'web_src',
setupFilesAfterEnv: ['jest-extended/all'],
testEnvironment: 'jest-environment-jsdom',
testMatch: ['<rootDir>/**/*.test.js'],
testTimeout: 20000,
transform: {
'\\.svg$': '<rootDir>/js/testUtils/jestRawLoader.js',
},
setupFiles: [
'./js/testUtils/jestSetup.js', // prepare global variables used by our code (eg: window.config)
],
verbose: false,
};

View file

@ -806,7 +806,7 @@ func getNotificationByID(ctx context.Context, notificationID int64) (*Notificati
} }
if !ok { if !ok {
return nil, db.ErrNotExist{ID: notificationID} return nil, db.ErrNotExist{Resource: "notification", ID: notificationID}
} }
return notification, nil return notification, nil

View file

@ -1,117 +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 admin_test
import (
"testing"
"code.gitea.io/gitea/models/admin"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
"github.com/stretchr/testify/assert"
)
func TestNotice_TrStr(t *testing.T) {
notice := &admin.Notice{
Type: admin.NoticeRepository,
Description: "test description",
}
assert.Equal(t, "admin.notices.type_1", notice.TrStr())
}
func TestCreateNotice(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
noticeBean := &admin.Notice{
Type: admin.NoticeRepository,
Description: "test description",
}
unittest.AssertNotExistsBean(t, noticeBean)
assert.NoError(t, admin.CreateNotice(db.DefaultContext, noticeBean.Type, noticeBean.Description))
unittest.AssertExistsAndLoadBean(t, noticeBean)
}
func TestCreateRepositoryNotice(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
noticeBean := &admin.Notice{
Type: admin.NoticeRepository,
Description: "test description",
}
unittest.AssertNotExistsBean(t, noticeBean)
assert.NoError(t, admin.CreateRepositoryNotice(noticeBean.Description))
unittest.AssertExistsAndLoadBean(t, noticeBean)
}
// TODO TestRemoveAllWithNotice
func TestCountNotices(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
assert.Equal(t, int64(3), admin.CountNotices())
}
func TestNotices(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
notices, err := admin.Notices(1, 2)
assert.NoError(t, err)
if assert.Len(t, notices, 2) {
assert.Equal(t, int64(3), notices[0].ID)
assert.Equal(t, int64(2), notices[1].ID)
}
notices, err = admin.Notices(2, 2)
assert.NoError(t, err)
if assert.Len(t, notices, 1) {
assert.Equal(t, int64(1), notices[0].ID)
}
}
func TestDeleteNotice(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 3})
assert.NoError(t, admin.DeleteNotice(3))
unittest.AssertNotExistsBean(t, &admin.Notice{ID: 3})
}
func TestDeleteNotices(t *testing.T) {
// delete a non-empty range
assert.NoError(t, unittest.PrepareTestDatabase())
unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 1})
unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 2})
unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 3})
assert.NoError(t, admin.DeleteNotices(1, 2))
unittest.AssertNotExistsBean(t, &admin.Notice{ID: 1})
unittest.AssertNotExistsBean(t, &admin.Notice{ID: 2})
unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 3})
}
func TestDeleteNotices2(t *testing.T) {
// delete an empty range
assert.NoError(t, unittest.PrepareTestDatabase())
unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 1})
unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 2})
unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 3})
assert.NoError(t, admin.DeleteNotices(3, 2))
unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 1})
unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 2})
unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 3})
}
func TestDeleteNoticesByIDs(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 1})
unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 2})
unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 3})
assert.NoError(t, admin.DeleteNoticesByIDs([]int64{1, 3}))
unittest.AssertNotExistsBean(t, &admin.Notice{ID: 1})
unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 2})
unittest.AssertNotExistsBean(t, &admin.Notice{ID: 3})
}

View file

@ -167,6 +167,10 @@ func (err ErrTaskDoesNotExist) Error() string {
err.ID, err.RepoID, err.Type) err.ID, err.RepoID, err.Type)
} }
func (err ErrTaskDoesNotExist) Unwrap() error {
return util.ErrNotExist
}
// GetMigratingTask returns the migrating task by repo's id // GetMigratingTask returns the migrating task by repo's id
func GetMigratingTask(repoID int64) (*Task, error) { func GetMigratingTask(repoID int64) (*Task, error) {
task := Task{ task := Task{

View file

@ -4,7 +4,11 @@
package asymkey package asymkey
import "fmt" import (
"fmt"
"code.gitea.io/gitea/modules/util"
)
// ErrKeyUnableVerify represents a "KeyUnableVerify" kind of error. // ErrKeyUnableVerify represents a "KeyUnableVerify" kind of error.
type ErrKeyUnableVerify struct { type ErrKeyUnableVerify struct {
@ -36,6 +40,10 @@ func (err ErrKeyNotExist) Error() string {
return fmt.Sprintf("public key does not exist [id: %d]", err.ID) return fmt.Sprintf("public key does not exist [id: %d]", err.ID)
} }
func (err ErrKeyNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrKeyAlreadyExist represents a "KeyAlreadyExist" kind of error. // ErrKeyAlreadyExist represents a "KeyAlreadyExist" kind of error.
type ErrKeyAlreadyExist struct { type ErrKeyAlreadyExist struct {
OwnerID int64 OwnerID int64
@ -54,6 +62,10 @@ func (err ErrKeyAlreadyExist) Error() string {
err.OwnerID, err.Fingerprint, err.Content) err.OwnerID, err.Fingerprint, err.Content)
} }
func (err ErrKeyAlreadyExist) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrKeyNameAlreadyUsed represents a "KeyNameAlreadyUsed" kind of error. // ErrKeyNameAlreadyUsed represents a "KeyNameAlreadyUsed" kind of error.
type ErrKeyNameAlreadyUsed struct { type ErrKeyNameAlreadyUsed struct {
OwnerID int64 OwnerID int64
@ -70,6 +82,10 @@ func (err ErrKeyNameAlreadyUsed) Error() string {
return fmt.Sprintf("public key already exists [owner_id: %d, name: %s]", err.OwnerID, err.Name) return fmt.Sprintf("public key already exists [owner_id: %d, name: %s]", err.OwnerID, err.Name)
} }
func (err ErrKeyNameAlreadyUsed) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrGPGNoEmailFound represents a "ErrGPGNoEmailFound" kind of error. // ErrGPGNoEmailFound represents a "ErrGPGNoEmailFound" kind of error.
type ErrGPGNoEmailFound struct { type ErrGPGNoEmailFound struct {
FailedEmails []string FailedEmails []string
@ -132,6 +148,10 @@ func (err ErrGPGKeyNotExist) Error() string {
return fmt.Sprintf("public gpg key does not exist [id: %d]", err.ID) return fmt.Sprintf("public gpg key does not exist [id: %d]", err.ID)
} }
func (err ErrGPGKeyNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrGPGKeyImportNotExist represents a "GPGKeyImportNotExist" kind of error. // ErrGPGKeyImportNotExist represents a "GPGKeyImportNotExist" kind of error.
type ErrGPGKeyImportNotExist struct { type ErrGPGKeyImportNotExist struct {
ID string ID string
@ -147,6 +167,10 @@ func (err ErrGPGKeyImportNotExist) Error() string {
return fmt.Sprintf("public gpg key import does not exist [id: %s]", err.ID) return fmt.Sprintf("public gpg key import does not exist [id: %s]", err.ID)
} }
func (err ErrGPGKeyImportNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrGPGKeyIDAlreadyUsed represents a "GPGKeyIDAlreadyUsed" kind of error. // ErrGPGKeyIDAlreadyUsed represents a "GPGKeyIDAlreadyUsed" kind of error.
type ErrGPGKeyIDAlreadyUsed struct { type ErrGPGKeyIDAlreadyUsed struct {
KeyID string KeyID string
@ -162,6 +186,10 @@ func (err ErrGPGKeyIDAlreadyUsed) Error() string {
return fmt.Sprintf("public key already exists [key_id: %s]", err.KeyID) return fmt.Sprintf("public key already exists [key_id: %s]", err.KeyID)
} }
func (err ErrGPGKeyIDAlreadyUsed) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrGPGKeyAccessDenied represents a "GPGKeyAccessDenied" kind of Error. // ErrGPGKeyAccessDenied represents a "GPGKeyAccessDenied" kind of Error.
type ErrGPGKeyAccessDenied struct { type ErrGPGKeyAccessDenied struct {
UserID int64 UserID int64
@ -180,6 +208,10 @@ func (err ErrGPGKeyAccessDenied) Error() string {
err.UserID, err.KeyID) err.UserID, err.KeyID)
} }
func (err ErrGPGKeyAccessDenied) Unwrap() error {
return util.ErrPermissionDenied
}
// ErrKeyAccessDenied represents a "KeyAccessDenied" kind of error. // ErrKeyAccessDenied represents a "KeyAccessDenied" kind of error.
type ErrKeyAccessDenied struct { type ErrKeyAccessDenied struct {
UserID int64 UserID int64
@ -198,6 +230,10 @@ func (err ErrKeyAccessDenied) Error() string {
err.UserID, err.KeyID, err.Note) err.UserID, err.KeyID, err.Note)
} }
func (err ErrKeyAccessDenied) Unwrap() error {
return util.ErrPermissionDenied
}
// ErrDeployKeyNotExist represents a "DeployKeyNotExist" kind of error. // ErrDeployKeyNotExist represents a "DeployKeyNotExist" kind of error.
type ErrDeployKeyNotExist struct { type ErrDeployKeyNotExist struct {
ID int64 ID int64
@ -215,6 +251,10 @@ func (err ErrDeployKeyNotExist) Error() string {
return fmt.Sprintf("Deploy key does not exist [id: %d, key_id: %d, repo_id: %d]", err.ID, err.KeyID, err.RepoID) return fmt.Sprintf("Deploy key does not exist [id: %d, key_id: %d, repo_id: %d]", err.ID, err.KeyID, err.RepoID)
} }
func (err ErrDeployKeyNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrDeployKeyAlreadyExist represents a "DeployKeyAlreadyExist" kind of error. // ErrDeployKeyAlreadyExist represents a "DeployKeyAlreadyExist" kind of error.
type ErrDeployKeyAlreadyExist struct { type ErrDeployKeyAlreadyExist struct {
KeyID int64 KeyID int64
@ -231,6 +271,10 @@ func (err ErrDeployKeyAlreadyExist) Error() string {
return fmt.Sprintf("public key already exists [key_id: %d, repo_id: %d]", err.KeyID, err.RepoID) return fmt.Sprintf("public key already exists [key_id: %d, repo_id: %d]", err.KeyID, err.RepoID)
} }
func (err ErrDeployKeyAlreadyExist) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrDeployKeyNameAlreadyUsed represents a "DeployKeyNameAlreadyUsed" kind of error. // ErrDeployKeyNameAlreadyUsed represents a "DeployKeyNameAlreadyUsed" kind of error.
type ErrDeployKeyNameAlreadyUsed struct { type ErrDeployKeyNameAlreadyUsed struct {
RepoID int64 RepoID int64
@ -247,6 +291,10 @@ func (err ErrDeployKeyNameAlreadyUsed) Error() string {
return fmt.Sprintf("public key with name already exists [repo_id: %d, name: %s]", err.RepoID, err.Name) return fmt.Sprintf("public key with name already exists [repo_id: %d, name: %s]", err.RepoID, err.Name)
} }
func (err ErrDeployKeyNameAlreadyUsed) Unwrap() error {
return util.ErrNotExist
}
// ErrSSHInvalidTokenSignature represents a "ErrSSHInvalidTokenSignature" kind of error. // ErrSSHInvalidTokenSignature represents a "ErrSSHInvalidTokenSignature" kind of error.
type ErrSSHInvalidTokenSignature struct { type ErrSSHInvalidTokenSignature struct {
Wrapped error Wrapped error
@ -262,3 +310,7 @@ func IsErrSSHInvalidTokenSignature(err error) bool {
func (err ErrSSHInvalidTokenSignature) Error() string { func (err ErrSSHInvalidTokenSignature) Error() string {
return "the provided signature does not sign the token with the provided key" return "the provided signature does not sign the token with the provided key"
} }
func (err ErrSSHInvalidTokenSignature) Unwrap() error {
return util.ErrInvalidArgument
}

View file

@ -31,6 +31,11 @@ type OAuth2Application struct {
Name string Name string
ClientID string `xorm:"unique"` ClientID string `xorm:"unique"`
ClientSecret string ClientSecret string
// OAuth defines both Confidential and Public client types
// https://datatracker.ietf.org/doc/html/rfc6749#section-2.1
// "Authorization servers MUST record the client type in the client registration details"
// https://datatracker.ietf.org/doc/html/rfc8252#section-8.4
ConfidentialClient bool `xorm:"NOT NULL DEFAULT TRUE"`
RedirectURIs []string `xorm:"redirect_uris JSON TEXT"` RedirectURIs []string `xorm:"redirect_uris JSON TEXT"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
@ -57,6 +62,7 @@ func (app *OAuth2Application) PrimaryRedirectURI() string {
// ContainsRedirectURI checks if redirectURI is allowed for app // ContainsRedirectURI checks if redirectURI is allowed for app
func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool { func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool {
if !app.ConfidentialClient {
uri, err := url.Parse(redirectURI) uri, err := url.Parse(redirectURI)
// ignore port for http loopback uris following https://datatracker.ietf.org/doc/html/rfc8252#section-7.3 // ignore port for http loopback uris following https://datatracker.ietf.org/doc/html/rfc8252#section-7.3
if err == nil && uri.Scheme == "http" && uri.Port() != "" { if err == nil && uri.Scheme == "http" && uri.Port() != "" {
@ -69,6 +75,7 @@ func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool {
} }
} }
} }
}
return util.IsStringInSlice(redirectURI, app.RedirectURIs, true) return util.IsStringInSlice(redirectURI, app.RedirectURIs, true)
} }
@ -163,6 +170,7 @@ func GetOAuth2ApplicationsByUserID(ctx context.Context, userID int64) (apps []*O
type CreateOAuth2ApplicationOptions struct { type CreateOAuth2ApplicationOptions struct {
Name string Name string
UserID int64 UserID int64
ConfidentialClient bool
RedirectURIs []string RedirectURIs []string
} }
@ -174,6 +182,7 @@ func CreateOAuth2Application(ctx context.Context, opts CreateOAuth2ApplicationOp
Name: opts.Name, Name: opts.Name,
ClientID: clientID, ClientID: clientID,
RedirectURIs: opts.RedirectURIs, RedirectURIs: opts.RedirectURIs,
ConfidentialClient: opts.ConfidentialClient,
} }
if err := db.Insert(ctx, app); err != nil { if err := db.Insert(ctx, app); err != nil {
return nil, err return nil, err
@ -186,6 +195,7 @@ type UpdateOAuth2ApplicationOptions struct {
ID int64 ID int64
Name string Name string
UserID int64 UserID int64
ConfidentialClient bool
RedirectURIs []string RedirectURIs []string
} }
@ -207,6 +217,7 @@ func UpdateOAuth2Application(opts UpdateOAuth2ApplicationOptions) (*OAuth2Applic
app.Name = opts.Name app.Name = opts.Name
app.RedirectURIs = opts.RedirectURIs app.RedirectURIs = opts.RedirectURIs
app.ConfidentialClient = opts.ConfidentialClient
if err = updateOAuth2Application(ctx, app); err != nil { if err = updateOAuth2Application(ctx, app); err != nil {
return nil, err return nil, err
@ -217,7 +228,7 @@ func UpdateOAuth2Application(opts UpdateOAuth2ApplicationOptions) (*OAuth2Applic
} }
func updateOAuth2Application(ctx context.Context, app *OAuth2Application) error { func updateOAuth2Application(ctx context.Context, app *OAuth2Application) error {
if _, err := db.GetEngine(ctx).ID(app.ID).Update(app); err != nil { if _, err := db.GetEngine(ctx).ID(app.ID).UseBool("confidential_client").Update(app); err != nil {
return err return err
} }
return nil return nil
@ -486,7 +497,7 @@ type ErrOAuthClientIDInvalid struct {
ClientID string ClientID string
} }
// IsErrOauthClientIDInvalid checks if an error is a ErrReviewNotExist. // IsErrOauthClientIDInvalid checks if an error is a ErrOAuthClientIDInvalid.
func IsErrOauthClientIDInvalid(err error) bool { func IsErrOauthClientIDInvalid(err error) bool {
_, ok := err.(ErrOAuthClientIDInvalid) _, ok := err.(ErrOAuthClientIDInvalid)
return ok return ok
@ -497,6 +508,11 @@ func (err ErrOAuthClientIDInvalid) Error() string {
return fmt.Sprintf("Client ID invalid [Client ID: %s]", err.ClientID) return fmt.Sprintf("Client ID invalid [Client ID: %s]", err.ClientID)
} }
// Unwrap unwraps this as a ErrNotExist err
func (err ErrOAuthClientIDInvalid) Unwrap() error {
return util.ErrNotExist
}
// ErrOAuthApplicationNotFound will be thrown if id cannot be found // ErrOAuthApplicationNotFound will be thrown if id cannot be found
type ErrOAuthApplicationNotFound struct { type ErrOAuthApplicationNotFound struct {
ID int64 ID int64
@ -513,6 +529,11 @@ func (err ErrOAuthApplicationNotFound) Error() string {
return fmt.Sprintf("OAuth application not found [ID: %d]", err.ID) return fmt.Sprintf("OAuth application not found [ID: %d]", err.ID)
} }
// Unwrap unwraps this as a ErrNotExist err
func (err ErrOAuthApplicationNotFound) Unwrap() error {
return util.ErrNotExist
}
// GetActiveOAuth2ProviderSources returns all actived LoginOAuth2 sources // GetActiveOAuth2ProviderSources returns all actived LoginOAuth2 sources
func GetActiveOAuth2ProviderSources() ([]*Source, error) { func GetActiveOAuth2ProviderSources() ([]*Source, error) {
sources := make([]*Source, 0, 1) sources := make([]*Source, 0, 1)

View file

@ -46,6 +46,7 @@ func TestOAuth2Application_ContainsRedirectURI(t *testing.T) {
func TestOAuth2Application_ContainsRedirectURI_WithPort(t *testing.T) { func TestOAuth2Application_ContainsRedirectURI_WithPort(t *testing.T) {
app := &auth_model.OAuth2Application{ app := &auth_model.OAuth2Application{
RedirectURIs: []string{"http://127.0.0.1/", "http://::1/", "http://192.168.0.1/", "http://intranet/", "https://127.0.0.1/"}, RedirectURIs: []string{"http://127.0.0.1/", "http://::1/", "http://192.168.0.1/", "http://intranet/", "https://127.0.0.1/"},
ConfidentialClient: false,
} }
// http loopback uris should ignore port // http loopback uris should ignore port

View file

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"xorm.io/xorm" "xorm.io/xorm"
"xorm.io/xorm/convert" "xorm.io/xorm/convert"
@ -366,6 +367,11 @@ func (err ErrSourceNotExist) Error() string {
return fmt.Sprintf("login source does not exist [id: %d]", err.ID) return fmt.Sprintf("login source does not exist [id: %d]", err.ID)
} }
// Unwrap unwraps this as a ErrNotExist err
func (err ErrSourceNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrSourceAlreadyExist represents a "SourceAlreadyExist" kind of error. // ErrSourceAlreadyExist represents a "SourceAlreadyExist" kind of error.
type ErrSourceAlreadyExist struct { type ErrSourceAlreadyExist struct {
Name string Name string
@ -381,6 +387,11 @@ func (err ErrSourceAlreadyExist) Error() string {
return fmt.Sprintf("login source already exists [name: %s]", err.Name) return fmt.Sprintf("login source already exists [name: %s]", err.Name)
} }
// Unwrap unwraps this as a ErrExist err
func (err ErrSourceAlreadyExist) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrSourceInUse represents a "SourceInUse" kind of error. // ErrSourceInUse represents a "SourceInUse" kind of error.
type ErrSourceInUse struct { type ErrSourceInUse struct {
ID int64 ID int64

View file

@ -35,6 +35,10 @@ func (err ErrAccessTokenNotExist) Error() string {
return fmt.Sprintf("access token does not exist [sha: %s]", err.Token) return fmt.Sprintf("access token does not exist [sha: %s]", err.Token)
} }
func (err ErrAccessTokenNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrAccessTokenEmpty represents a "AccessTokenEmpty" kind of error. // ErrAccessTokenEmpty represents a "AccessTokenEmpty" kind of error.
type ErrAccessTokenEmpty struct{} type ErrAccessTokenEmpty struct{}
@ -48,6 +52,10 @@ func (err ErrAccessTokenEmpty) Error() string {
return "access token is empty" return "access token is empty"
} }
func (err ErrAccessTokenEmpty) Unwrap() error {
return util.ErrInvalidArgument
}
var successfulAccessTokenCache *lru.Cache var successfulAccessTokenCache *lru.Cache
// AccessToken represents a personal access token. // AccessToken represents a personal access token.

View file

@ -41,6 +41,11 @@ func (err ErrTwoFactorNotEnrolled) Error() string {
return fmt.Sprintf("user not enrolled in 2FA [uid: %d]", err.UID) return fmt.Sprintf("user not enrolled in 2FA [uid: %d]", err.UID)
} }
// Unwrap unwraps this as a ErrNotExist err
func (err ErrTwoFactorNotEnrolled) Unwrap() error {
return util.ErrNotExist
}
// TwoFactor represents a two-factor authentication token. // TwoFactor represents a two-factor authentication token.
type TwoFactor struct { type TwoFactor struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`

View file

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"github.com/duo-labs/webauthn/webauthn" "github.com/duo-labs/webauthn/webauthn"
"xorm.io/xorm" "xorm.io/xorm"
@ -29,6 +30,11 @@ func (err ErrWebAuthnCredentialNotExist) Error() string {
return fmt.Sprintf("WebAuthn credential does not exist [credential_id: %x]", err.CredentialID) return fmt.Sprintf("WebAuthn credential does not exist [credential_id: %x]", err.CredentialID)
} }
// Unwrap unwraps this as a ErrNotExist err
func (err ErrWebAuthnCredentialNotExist) Unwrap() error {
return util.ErrNotExist
}
// IsErrWebAuthnCredentialNotExist checks if an error is a ErrWebAuthnCredentialNotExist. // IsErrWebAuthnCredentialNotExist checks if an error is a ErrWebAuthnCredentialNotExist.
func IsErrWebAuthnCredentialNotExist(err error) bool { func IsErrWebAuthnCredentialNotExist(err error) bool {
_, ok := err.(ErrWebAuthnCredentialNotExist) _, ok := err.(ErrWebAuthnCredentialNotExist)

View file

@ -13,6 +13,7 @@ import (
"sync" "sync"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -72,7 +73,7 @@ func GetEmailForHash(md5Sum string) (string, error) {
// LibravatarURL returns the URL for the given email. Slow due to the DNS lookup. // 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. // This function should only be called if a federated avatar service is enabled.
func LibravatarURL(email string) (*url.URL, error) { func LibravatarURL(email string) (*url.URL, error) {
urlStr, err := setting.LibravatarService.FromEmail(email) urlStr, err := system_model.LibravatarService.FromEmail(email)
if err != nil { if err != nil {
log.Error("LibravatarService.FromEmail(email=%s): error %v", email, err) log.Error("LibravatarService.FromEmail(email=%s): error %v", email, err)
return nil, err return nil, err
@ -149,8 +150,10 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
return DefaultAvatarLink() return DefaultAvatarLink()
} }
enableFederatedAvatar, _ := system_model.GetSetting(system_model.KeyPictureEnableFederatedAvatar)
var err error var err error
if setting.EnableFederatedAvatar && setting.LibravatarService != nil { if enableFederatedAvatar != nil && enableFederatedAvatar.GetValueBool() && system_model.LibravatarService != nil {
emailHash := saveEmailHash(email) emailHash := saveEmailHash(email)
if final { if final {
// for final link, we can spend more time on slow external query // for final link, we can spend more time on slow external query
@ -166,12 +169,16 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
urlStr += "?size=" + strconv.Itoa(size) urlStr += "?size=" + strconv.Itoa(size)
} }
return urlStr return urlStr
} else if !setting.DisableGravatar { }
disableGravatar, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
if disableGravatar != nil && !disableGravatar.GetValueBool() {
// copy GravatarSourceURL, because we will modify its Path. // copy GravatarSourceURL, because we will modify its Path.
avatarURLCopy := *setting.GravatarSourceURL avatarURLCopy := *system_model.GravatarSourceURL
avatarURLCopy.Path = path.Join(avatarURLCopy.Path, HashEmail(email)) avatarURLCopy.Path = path.Join(avatarURLCopy.Path, HashEmail(email))
return generateRecognizedAvatarURL(avatarURLCopy, size) return generateRecognizedAvatarURL(avatarURLCopy, size)
} }
return DefaultAvatarLink() return DefaultAvatarLink()
} }

View file

@ -2,12 +2,13 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package avatars package avatars_test
import ( import (
"net/url"
"testing" "testing"
avatars_model "code.gitea.io/gitea/models/avatars"
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -15,40 +16,43 @@ import (
const gravatarSource = "https://secure.gravatar.com/avatar/" const gravatarSource = "https://secure.gravatar.com/avatar/"
func disableGravatar() { func disableGravatar(t *testing.T) {
setting.EnableFederatedAvatar = false err := system_model.SetSettingNoVersion(system_model.KeyPictureEnableFederatedAvatar, "false")
setting.LibravatarService = nil assert.NoError(t, err)
setting.DisableGravatar = true err = system_model.SetSettingNoVersion(system_model.KeyPictureDisableGravatar, "true")
assert.NoError(t, err)
system_model.LibravatarService = nil
} }
func enableGravatar(t *testing.T) { func enableGravatar(t *testing.T) {
setting.DisableGravatar = false err := system_model.SetSettingNoVersion(system_model.KeyPictureDisableGravatar, "false")
var err error assert.NoError(t, err)
setting.GravatarSourceURL, err = url.Parse(gravatarSource) setting.GravatarSource = gravatarSource
err = system_model.Init()
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestHashEmail(t *testing.T) { func TestHashEmail(t *testing.T) {
assert.Equal(t, assert.Equal(t,
"d41d8cd98f00b204e9800998ecf8427e", "d41d8cd98f00b204e9800998ecf8427e",
HashEmail(""), avatars_model.HashEmail(""),
) )
assert.Equal(t, assert.Equal(t,
"353cbad9b58e69c96154ad99f92bedc7", "353cbad9b58e69c96154ad99f92bedc7",
HashEmail("gitea@example.com"), avatars_model.HashEmail("gitea@example.com"),
) )
} }
func TestSizedAvatarLink(t *testing.T) { func TestSizedAvatarLink(t *testing.T) {
setting.AppSubURL = "/testsuburl" setting.AppSubURL = "/testsuburl"
disableGravatar() disableGravatar(t)
assert.Equal(t, "/testsuburl/assets/img/avatar_default.png", assert.Equal(t, "/testsuburl/assets/img/avatar_default.png",
GenerateEmailAvatarFastLink("gitea@example.com", 100)) avatars_model.GenerateEmailAvatarFastLink("gitea@example.com", 100))
enableGravatar(t) enableGravatar(t)
assert.Equal(t, assert.Equal(t,
"https://secure.gravatar.com/avatar/353cbad9b58e69c96154ad99f92bedc7?d=identicon&s=100", "https://secure.gravatar.com/avatar/353cbad9b58e69c96154ad99f92bedc7?d=identicon&s=100",
GenerateEmailAvatarFastLink("gitea@example.com", 100), avatars_model.GenerateEmailAvatarFastLink("gitea@example.com", 100),
) )
} }

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package admin_test package avatars_test
import ( import (
"path/filepath" "path/filepath"

View file

@ -225,7 +225,7 @@ func NamesToBean(names ...string) ([]interface{}, error) {
for _, name := range names { for _, name := range names {
bean, ok := beanMap[strings.ToLower(strings.TrimSpace(name))] bean, ok := beanMap[strings.ToLower(strings.TrimSpace(name))]
if !ok { if !ok {
return nil, fmt.Errorf("No table found that matches: %s", name) return nil, fmt.Errorf("no table found that matches: %s", name)
} }
if !gotBean[bean] { if !gotBean[bean] {
beans = append(beans, bean) beans = append(beans, bean)

View file

@ -6,6 +6,8 @@ package db
import ( import (
"fmt" "fmt"
"code.gitea.io/gitea/modules/util"
) )
// ErrCancelled represents an error due to context cancellation // ErrCancelled represents an error due to context cancellation
@ -45,6 +47,7 @@ func (err ErrSSHDisabled) Error() string {
// ErrNotExist represents a non-exist error. // ErrNotExist represents a non-exist error.
type ErrNotExist struct { type ErrNotExist struct {
Resource string
ID int64 ID int64
} }
@ -55,5 +58,18 @@ func IsErrNotExist(err error) bool {
} }
func (err ErrNotExist) Error() string { func (err ErrNotExist) Error() string {
return fmt.Sprintf("record does not exist [id: %d]", err.ID) name := "record"
if err.Resource != "" {
name = err.Resource
}
if err.ID != 0 {
return fmt.Sprintf("%s does not exist [id: %d]", name, err.ID)
}
return fmt.Sprintf("%s does not exist", name)
}
// Unwrap unwraps this as a ErrNotExist err
func (err ErrNotExist) Unwrap() error {
return util.ErrNotExist
} }

View file

@ -8,45 +8,15 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"code.gitea.io/gitea/modules/setting"
) )
// ResourceIndex represents a resource index which could be used as issue/release and others // ResourceIndex represents a resource index which could be used as issue/release and others
// We can create different tables i.e. issue_index, release_index and etc. // We can create different tables i.e. issue_index, release_index, etc.
type ResourceIndex struct { type ResourceIndex struct {
GroupID int64 `xorm:"pk"` GroupID int64 `xorm:"pk"`
MaxIndex int64 `xorm:"index"` MaxIndex int64 `xorm:"index"`
} }
// UpsertResourceIndex the function will not return until it acquires the lock or receives an error.
func UpsertResourceIndex(ctx context.Context, 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 {
case setting.Database.UseSQLite3 || setting.Database.UsePostgreSQL:
_, err = Exec(ctx, fmt.Sprintf("INSERT INTO %s (group_id, max_index) "+
"VALUES (?,1) ON CONFLICT (group_id) DO UPDATE SET max_index = %s.max_index+1",
tableName, tableName), groupID)
case setting.Database.UseMySQL:
_, err = Exec(ctx, fmt.Sprintf("INSERT INTO %s (group_id, max_index) "+
"VALUES (?,1) ON DUPLICATE KEY UPDATE max_index = max_index+1", tableName),
groupID)
case setting.Database.UseMSSQL:
// https://weblogs.sqlteam.com/dang/2009/01/31/upsert-race-condition-with-merge/
_, err = Exec(ctx, fmt.Sprintf("MERGE %s WITH (HOLDLOCK) as target "+
"USING (SELECT ? AS group_id) AS src "+
"ON src.group_id = target.group_id "+
"WHEN MATCHED THEN UPDATE SET target.max_index = target.max_index+1 "+
"WHEN NOT MATCHED THEN INSERT (group_id, max_index) "+
"VALUES (src.group_id, 1);", tableName),
groupID)
default:
return fmt.Errorf("database type not supported")
}
return err
}
var ( var (
// ErrResouceOutdated represents an error when request resource outdated // ErrResouceOutdated represents an error when request resource outdated
ErrResouceOutdated = errors.New("resource outdated") ErrResouceOutdated = errors.New("resource outdated")
@ -59,53 +29,85 @@ const (
MaxDupIndexAttempts = 3 MaxDupIndexAttempts = 3
) )
// GetNextResourceIndex retried 3 times to generate a resource index // SyncMaxResourceIndex sync the max index with the resource
func GetNextResourceIndex(tableName string, groupID int64) (int64, error) { func SyncMaxResourceIndex(ctx context.Context, tableName string, groupID, maxIndex int64) (err error) {
for i := 0; i < MaxDupIndexAttempts; i++ { e := GetEngine(ctx)
idx, err := getNextResourceIndex(tableName, groupID)
if err == ErrResouceOutdated {
continue
}
if err != nil {
return 0, err
}
return idx, nil
}
return 0, ErrGetResourceIndexFailed
}
// DeleteResouceIndex delete resource index // try to update the max_index and acquire the write-lock for the record
func DeleteResouceIndex(ctx context.Context, tableName string, groupID int64) error { res, err := e.Exec(fmt.Sprintf("UPDATE %s SET max_index=? WHERE group_id=? AND max_index<?", tableName), maxIndex, groupID, maxIndex)
_, err := Exec(ctx, fmt.Sprintf("DELETE FROM %s WHERE group_id=?", tableName), groupID) if err != nil {
return err return err
}
affected, err := res.RowsAffected()
if err != nil {
return err
}
if affected == 0 {
// if nothing is updated, the record might not exist or might be larger, it's safe to try to insert it again and then check whether the record exists
_, errIns := e.Exec(fmt.Sprintf("INSERT INTO %s (group_id, max_index) VALUES (?, ?)", tableName), groupID, maxIndex)
var savedIdx int64
has, err := e.SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id=?", tableName), groupID).Get(&savedIdx)
if err != nil {
return err
}
// if the record still doesn't exist, there must be some errors (insert error)
if !has {
if errIns == nil {
return errors.New("impossible error when SyncMaxResourceIndex, insert succeeded but no record is saved")
}
return errIns
}
}
return nil
} }
// getNextResourceIndex return the next index // GetNextResourceIndex generates a resource index, it must run in the same transaction where the resource is created
func getNextResourceIndex(tableName string, groupID int64) (int64, error) { func GetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
ctx, commiter, err := TxContext() e := GetEngine(ctx)
// try to update the max_index to next value, and acquire the write-lock for the record
res, err := e.Exec(fmt.Sprintf("UPDATE %s SET max_index=max_index+1 WHERE group_id=?", tableName), groupID)
if err != nil { if err != nil {
return 0, err return 0, err
} }
defer commiter.Close() affected, err := res.RowsAffected()
var preIdx int64 if err != nil {
if _, err := GetEngine(ctx).SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id = ?", tableName), groupID).Get(&preIdx); err != nil {
return 0, err return 0, err
} }
if affected == 0 {
if err := UpsertResourceIndex(ctx, tableName, groupID); err != nil { // this slow path is only for the first time of creating a resource index
_, errIns := e.Exec(fmt.Sprintf("INSERT INTO %s (group_id, max_index) VALUES (?, 0)", tableName), groupID)
res, err = e.Exec(fmt.Sprintf("UPDATE %s SET max_index=max_index+1 WHERE group_id=?", tableName), groupID)
if err != nil {
return 0, err return 0, err
} }
affected, err = res.RowsAffected()
if err != nil {
return 0, err
}
// if the update still can not update any records, the record must not exist and there must be some errors (insert error)
if affected == 0 {
if errIns == nil {
return 0, errors.New("impossible error when GetNextResourceIndex, insert and update both succeeded but no record is updated")
}
return 0, errIns
}
}
var curIdx int64 // now, the new index is in database (protected by the transaction and write-lock)
has, err := GetEngine(ctx).SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id = ? AND max_index=?", tableName), groupID, preIdx+1).Get(&curIdx) var newIdx int64
has, err := e.SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id=?", tableName), groupID).Get(&newIdx)
if err != nil { if err != nil {
return 0, err return 0, err
} }
if !has { if !has {
return 0, ErrResouceOutdated return 0, errors.New("impossible error when GetNextResourceIndex, upsert succeeded but no record can be selected")
} }
if err := commiter.Commit(); err != nil { return newIdx, nil
return 0, err }
}
return curIdx, nil // DeleteResourceIndex delete resource index
func DeleteResourceIndex(ctx context.Context, tableName string, groupID int64) error {
_, err := Exec(ctx, fmt.Sprintf("DELETE FROM %s WHERE group_id=?", tableName), groupID)
return err
} }

127
models/db/index_test.go Normal file
View file

@ -0,0 +1,127 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package db_test
import (
"context"
"errors"
"fmt"
"testing"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
"github.com/stretchr/testify/assert"
)
type TestIndex db.ResourceIndex
func getCurrentResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
e := db.GetEngine(ctx)
var idx int64
has, err := e.SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id=?", tableName), groupID).Get(&idx)
if err != nil {
return 0, err
}
if !has {
return 0, errors.New("no record")
}
return idx, nil
}
func TestSyncMaxResourceIndex(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
xe := unittest.GetXORMEngine()
assert.NoError(t, xe.Sync(&TestIndex{}))
err := db.SyncMaxResourceIndex(db.DefaultContext, "test_index", 10, 51)
assert.NoError(t, err)
// sync new max index
maxIndex, err := getCurrentResourceIndex(db.DefaultContext, "test_index", 10)
assert.NoError(t, err)
assert.EqualValues(t, 51, maxIndex)
// smaller index doesn't change
err = db.SyncMaxResourceIndex(db.DefaultContext, "test_index", 10, 30)
assert.NoError(t, err)
maxIndex, err = getCurrentResourceIndex(db.DefaultContext, "test_index", 10)
assert.NoError(t, err)
assert.EqualValues(t, 51, maxIndex)
// larger index changes
err = db.SyncMaxResourceIndex(db.DefaultContext, "test_index", 10, 62)
assert.NoError(t, err)
maxIndex, err = getCurrentResourceIndex(db.DefaultContext, "test_index", 10)
assert.NoError(t, err)
assert.EqualValues(t, 62, maxIndex)
// commit transaction
err = db.WithTx(func(ctx context.Context) error {
err = db.SyncMaxResourceIndex(ctx, "test_index", 10, 73)
assert.NoError(t, err)
maxIndex, err = getCurrentResourceIndex(ctx, "test_index", 10)
assert.NoError(t, err)
assert.EqualValues(t, 73, maxIndex)
return nil
})
assert.NoError(t, err)
maxIndex, err = getCurrentResourceIndex(db.DefaultContext, "test_index", 10)
assert.NoError(t, err)
assert.EqualValues(t, 73, maxIndex)
// rollback transaction
err = db.WithTx(func(ctx context.Context) error {
err = db.SyncMaxResourceIndex(ctx, "test_index", 10, 84)
maxIndex, err = getCurrentResourceIndex(ctx, "test_index", 10)
assert.NoError(t, err)
assert.EqualValues(t, 84, maxIndex)
return errors.New("test rollback")
})
assert.Error(t, err)
maxIndex, err = getCurrentResourceIndex(db.DefaultContext, "test_index", 10)
assert.NoError(t, err)
assert.EqualValues(t, 73, maxIndex) // the max index doesn't change because the transaction was rolled back
}
func TestGetNextResourceIndex(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
xe := unittest.GetXORMEngine()
assert.NoError(t, xe.Sync(&TestIndex{}))
// create a new record
maxIndex, err := db.GetNextResourceIndex(db.DefaultContext, "test_index", 20)
assert.NoError(t, err)
assert.EqualValues(t, 1, maxIndex)
// increase the existing record
maxIndex, err = db.GetNextResourceIndex(db.DefaultContext, "test_index", 20)
assert.NoError(t, err)
assert.EqualValues(t, 2, maxIndex)
// commit transaction
err = db.WithTx(func(ctx context.Context) error {
maxIndex, err = db.GetNextResourceIndex(ctx, "test_index", 20)
assert.NoError(t, err)
assert.EqualValues(t, 3, maxIndex)
return nil
})
assert.NoError(t, err)
maxIndex, err = getCurrentResourceIndex(db.DefaultContext, "test_index", 20)
assert.NoError(t, err)
assert.EqualValues(t, 3, maxIndex)
// rollback transaction
err = db.WithTx(func(ctx context.Context) error {
maxIndex, err = db.GetNextResourceIndex(ctx, "test_index", 20)
assert.NoError(t, err)
assert.EqualValues(t, 4, maxIndex)
return errors.New("test rollback")
})
assert.Error(t, err)
maxIndex, err = getCurrentResourceIndex(db.DefaultContext, "test_index", 20)
assert.NoError(t, err)
assert.EqualValues(t, 3, maxIndex) // the max index doesn't change because the transaction was rolled back
}

View file

@ -5,16 +5,17 @@
package db package db
import ( import (
"errors"
"fmt" "fmt"
"regexp" "regexp"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
"code.gitea.io/gitea/modules/util"
) )
var ( var (
// ErrNameEmpty name is empty error // ErrNameEmpty name is empty error
ErrNameEmpty = errors.New("Name is empty") ErrNameEmpty = util.SilentWrap{Message: "name is empty", Err: util.ErrInvalidArgument}
// AlphaDashDotPattern characters prohibited in a user name (anything except A-Za-z0-9_.-) // AlphaDashDotPattern characters prohibited in a user name (anything except A-Za-z0-9_.-)
AlphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`) AlphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
@ -35,6 +36,11 @@ func (err ErrNameReserved) Error() string {
return fmt.Sprintf("name is reserved [name: %s]", err.Name) return fmt.Sprintf("name is reserved [name: %s]", err.Name)
} }
// Unwrap unwraps this as a ErrInvalid err
func (err ErrNameReserved) Unwrap() error {
return util.ErrInvalidArgument
}
// ErrNamePatternNotAllowed represents a "pattern not allowed" error. // ErrNamePatternNotAllowed represents a "pattern not allowed" error.
type ErrNamePatternNotAllowed struct { type ErrNamePatternNotAllowed struct {
Pattern string Pattern string
@ -50,6 +56,11 @@ func (err ErrNamePatternNotAllowed) Error() string {
return fmt.Sprintf("name pattern is not allowed [pattern: %s]", err.Pattern) return fmt.Sprintf("name pattern is not allowed [pattern: %s]", err.Pattern)
} }
// Unwrap unwraps this as a ErrInvalid err
func (err ErrNamePatternNotAllowed) Unwrap() error {
return util.ErrInvalidArgument
}
// ErrNameCharsNotAllowed represents a "character not allowed in name" error. // ErrNameCharsNotAllowed represents a "character not allowed in name" error.
type ErrNameCharsNotAllowed struct { type ErrNameCharsNotAllowed struct {
Name string Name string
@ -62,7 +73,12 @@ func IsErrNameCharsNotAllowed(err error) bool {
} }
func (err ErrNameCharsNotAllowed) Error() string { func (err ErrNameCharsNotAllowed) Error() string {
return fmt.Sprintf("User name is invalid [%s]: must be valid alpha or numeric or dash(-_) or dot characters", err.Name) return fmt.Sprintf("name is invalid [%s]: must be valid alpha or numeric or dash(-_) or dot characters", err.Name)
}
// Unwrap unwraps this as a ErrInvalid err
func (err ErrNameCharsNotAllowed) Unwrap() error {
return util.ErrInvalidArgument
} }
// IsUsableName checks if name is reserved or pattern of name is not allowed // IsUsableName checks if name is reserved or pattern of name is not allowed

View file

@ -10,6 +10,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/util"
) )
// ErrUserOwnRepos represents a "UserOwnRepos" kind of error. // ErrUserOwnRepos represents a "UserOwnRepos" kind of error.
@ -63,8 +64,8 @@ type ErrNoPendingRepoTransfer struct {
RepoID int64 RepoID int64
} }
func (e ErrNoPendingRepoTransfer) Error() string { func (err ErrNoPendingRepoTransfer) Error() string {
return fmt.Sprintf("repository doesn't have a pending transfer [repo_id: %d]", e.RepoID) return fmt.Sprintf("repository doesn't have a pending transfer [repo_id: %d]", err.RepoID)
} }
// IsErrNoPendingTransfer is an error type when a repository has no pending // IsErrNoPendingTransfer is an error type when a repository has no pending
@ -74,6 +75,10 @@ func IsErrNoPendingTransfer(err error) bool {
return ok return ok
} }
func (err ErrNoPendingRepoTransfer) Unwrap() error {
return util.ErrNotExist
}
// ErrRepoTransferInProgress represents the state of a repository that has an // ErrRepoTransferInProgress represents the state of a repository that has an
// ongoing transfer // ongoing transfer
type ErrRepoTransferInProgress struct { type ErrRepoTransferInProgress struct {
@ -91,6 +96,10 @@ func (err ErrRepoTransferInProgress) Error() string {
return fmt.Sprintf("repository is already being transferred [uname: %s, name: %s]", err.Uname, err.Name) return fmt.Sprintf("repository is already being transferred [uname: %s, name: %s]", err.Uname, err.Name)
} }
func (err ErrRepoTransferInProgress) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrInvalidCloneAddr represents a "InvalidCloneAddr" kind of error. // ErrInvalidCloneAddr represents a "InvalidCloneAddr" kind of error.
type ErrInvalidCloneAddr struct { type ErrInvalidCloneAddr struct {
Host string Host string
@ -124,6 +133,10 @@ func (err *ErrInvalidCloneAddr) Error() string {
return fmt.Sprintf("migration/cloning from '%s' is not allowed", err.Host) return fmt.Sprintf("migration/cloning from '%s' is not allowed", err.Host)
} }
func (err *ErrInvalidCloneAddr) Unwrap() error {
return util.ErrInvalidArgument
}
// ErrUpdateTaskNotExist represents a "UpdateTaskNotExist" kind of error. // ErrUpdateTaskNotExist represents a "UpdateTaskNotExist" kind of error.
type ErrUpdateTaskNotExist struct { type ErrUpdateTaskNotExist struct {
UUID string UUID string
@ -139,6 +152,10 @@ func (err ErrUpdateTaskNotExist) Error() string {
return fmt.Sprintf("update task does not exist [uuid: %s]", err.UUID) return fmt.Sprintf("update task does not exist [uuid: %s]", err.UUID)
} }
func (err ErrUpdateTaskNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrInvalidTagName represents a "InvalidTagName" kind of error. // ErrInvalidTagName represents a "InvalidTagName" kind of error.
type ErrInvalidTagName struct { type ErrInvalidTagName struct {
TagName string TagName string
@ -154,6 +171,10 @@ func (err ErrInvalidTagName) Error() string {
return fmt.Sprintf("release tag name is not valid [tag_name: %s]", err.TagName) return fmt.Sprintf("release tag name is not valid [tag_name: %s]", err.TagName)
} }
func (err ErrInvalidTagName) Unwrap() error {
return util.ErrInvalidArgument
}
// ErrProtectedTagName represents a "ProtectedTagName" kind of error. // ErrProtectedTagName represents a "ProtectedTagName" kind of error.
type ErrProtectedTagName struct { type ErrProtectedTagName struct {
TagName string TagName string
@ -169,6 +190,10 @@ func (err ErrProtectedTagName) Error() string {
return fmt.Sprintf("release tag name is protected [tag_name: %s]", err.TagName) return fmt.Sprintf("release tag name is protected [tag_name: %s]", err.TagName)
} }
func (err ErrProtectedTagName) Unwrap() error {
return util.ErrPermissionDenied
}
// ErrRepoFileAlreadyExists represents a "RepoFileAlreadyExist" kind of error. // ErrRepoFileAlreadyExists represents a "RepoFileAlreadyExist" kind of error.
type ErrRepoFileAlreadyExists struct { type ErrRepoFileAlreadyExists struct {
Path string Path string
@ -184,6 +209,10 @@ func (err ErrRepoFileAlreadyExists) Error() string {
return fmt.Sprintf("repository file already exists [path: %s]", err.Path) return fmt.Sprintf("repository file already exists [path: %s]", err.Path)
} }
func (err ErrRepoFileAlreadyExists) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrRepoFileDoesNotExist represents a "RepoFileDoesNotExist" kind of error. // ErrRepoFileDoesNotExist represents a "RepoFileDoesNotExist" kind of error.
type ErrRepoFileDoesNotExist struct { type ErrRepoFileDoesNotExist struct {
Path string Path string
@ -200,6 +229,10 @@ func (err ErrRepoFileDoesNotExist) Error() string {
return fmt.Sprintf("repository file does not exist [path: %s]", err.Path) return fmt.Sprintf("repository file does not exist [path: %s]", err.Path)
} }
func (err ErrRepoFileDoesNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrFilenameInvalid represents a "FilenameInvalid" kind of error. // ErrFilenameInvalid represents a "FilenameInvalid" kind of error.
type ErrFilenameInvalid struct { type ErrFilenameInvalid struct {
Path string Path string
@ -215,6 +248,10 @@ func (err ErrFilenameInvalid) Error() string {
return fmt.Sprintf("path contains a malformed path component [path: %s]", err.Path) return fmt.Sprintf("path contains a malformed path component [path: %s]", err.Path)
} }
func (err ErrFilenameInvalid) Unwrap() error {
return util.ErrInvalidArgument
}
// ErrUserCannotCommit represents "UserCannotCommit" kind of error. // ErrUserCannotCommit represents "UserCannotCommit" kind of error.
type ErrUserCannotCommit struct { type ErrUserCannotCommit struct {
UserName string UserName string
@ -230,6 +267,10 @@ func (err ErrUserCannotCommit) Error() string {
return fmt.Sprintf("user cannot commit to repo [user: %s]", err.UserName) return fmt.Sprintf("user cannot commit to repo [user: %s]", err.UserName)
} }
func (err ErrUserCannotCommit) Unwrap() error {
return util.ErrPermissionDenied
}
// ErrFilePathInvalid represents a "FilePathInvalid" kind of error. // ErrFilePathInvalid represents a "FilePathInvalid" kind of error.
type ErrFilePathInvalid struct { type ErrFilePathInvalid struct {
Message string Message string
@ -251,6 +292,10 @@ func (err ErrFilePathInvalid) Error() string {
return fmt.Sprintf("path is invalid [path: %s]", err.Path) return fmt.Sprintf("path is invalid [path: %s]", err.Path)
} }
func (err ErrFilePathInvalid) Unwrap() error {
return util.ErrInvalidArgument
}
// ErrFilePathProtected represents a "FilePathProtected" kind of error. // ErrFilePathProtected represents a "FilePathProtected" kind of error.
type ErrFilePathProtected struct { type ErrFilePathProtected struct {
Message string Message string
@ -270,6 +315,10 @@ func (err ErrFilePathProtected) Error() string {
return fmt.Sprintf("path is protected and can not be changed [path: %s]", err.Path) return fmt.Sprintf("path is protected and can not be changed [path: %s]", err.Path)
} }
func (err ErrFilePathProtected) Unwrap() error {
return util.ErrPermissionDenied
}
// __________ .__ // __________ .__
// \______ \____________ ____ ____ | |__ // \______ \____________ ____ ____ | |__
// | | _/\_ __ \__ \ / \_/ ___\| | \ // | | _/\_ __ \__ \ / \_/ ___\| | \
@ -292,6 +341,10 @@ func (err ErrBranchDoesNotExist) Error() string {
return fmt.Sprintf("branch does not exist [name: %s]", err.BranchName) return fmt.Sprintf("branch does not exist [name: %s]", err.BranchName)
} }
func (err ErrBranchDoesNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrBranchAlreadyExists represents an error that branch with such name already exists. // ErrBranchAlreadyExists represents an error that branch with such name already exists.
type ErrBranchAlreadyExists struct { type ErrBranchAlreadyExists struct {
BranchName string BranchName string
@ -307,6 +360,10 @@ func (err ErrBranchAlreadyExists) Error() string {
return fmt.Sprintf("branch already exists [name: %s]", err.BranchName) return fmt.Sprintf("branch already exists [name: %s]", err.BranchName)
} }
func (err ErrBranchAlreadyExists) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrBranchNameConflict represents an error that branch name conflicts with other branch. // ErrBranchNameConflict represents an error that branch name conflicts with other branch.
type ErrBranchNameConflict struct { type ErrBranchNameConflict struct {
BranchName string BranchName string
@ -322,6 +379,10 @@ func (err ErrBranchNameConflict) Error() string {
return fmt.Sprintf("branch conflicts with existing branch [name: %s]", err.BranchName) return fmt.Sprintf("branch conflicts with existing branch [name: %s]", err.BranchName)
} }
func (err ErrBranchNameConflict) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrBranchesEqual represents an error that branch name conflicts with other branch. // ErrBranchesEqual represents an error that branch name conflicts with other branch.
type ErrBranchesEqual struct { type ErrBranchesEqual struct {
BaseBranchName string BaseBranchName string
@ -338,6 +399,10 @@ func (err ErrBranchesEqual) Error() string {
return fmt.Sprintf("branches are equal [head: %sm base: %s]", err.HeadBranchName, err.BaseBranchName) return fmt.Sprintf("branches are equal [head: %sm base: %s]", err.HeadBranchName, err.BaseBranchName)
} }
func (err ErrBranchesEqual) Unwrap() error {
return util.ErrInvalidArgument
}
// ErrDisallowedToMerge represents an error that a branch is protected and the current user is not allowed to modify it. // ErrDisallowedToMerge represents an error that a branch is protected and the current user is not allowed to modify it.
type ErrDisallowedToMerge struct { type ErrDisallowedToMerge struct {
Reason string Reason string
@ -353,6 +418,10 @@ func (err ErrDisallowedToMerge) Error() string {
return fmt.Sprintf("not allowed to merge [reason: %s]", err.Reason) return fmt.Sprintf("not allowed to merge [reason: %s]", err.Reason)
} }
func (err ErrDisallowedToMerge) Unwrap() error {
return util.ErrPermissionDenied
}
// ErrTagAlreadyExists represents an error that tag with such name already exists. // ErrTagAlreadyExists represents an error that tag with such name already exists.
type ErrTagAlreadyExists struct { type ErrTagAlreadyExists struct {
TagName string TagName string
@ -368,6 +437,10 @@ func (err ErrTagAlreadyExists) Error() string {
return fmt.Sprintf("tag already exists [name: %s]", err.TagName) return fmt.Sprintf("tag already exists [name: %s]", err.TagName)
} }
func (err ErrTagAlreadyExists) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrSHADoesNotMatch represents a "SHADoesNotMatch" kind of error. // ErrSHADoesNotMatch represents a "SHADoesNotMatch" kind of error.
type ErrSHADoesNotMatch struct { type ErrSHADoesNotMatch struct {
Path string Path string
@ -400,6 +473,10 @@ func (err ErrSHANotFound) Error() string {
return fmt.Sprintf("sha not found [%s]", err.SHA) return fmt.Sprintf("sha not found [%s]", err.SHA)
} }
func (err ErrSHANotFound) Unwrap() error {
return util.ErrNotExist
}
// ErrCommitIDDoesNotMatch represents a "CommitIDDoesNotMatch" kind of error. // ErrCommitIDDoesNotMatch represents a "CommitIDDoesNotMatch" kind of error.
type ErrCommitIDDoesNotMatch struct { type ErrCommitIDDoesNotMatch struct {
GivenCommitID string GivenCommitID string
@ -446,6 +523,10 @@ func (err ErrInvalidMergeStyle) Error() string {
err.ID, err.Style) err.ID, err.Style)
} }
func (err ErrInvalidMergeStyle) Unwrap() error {
return util.ErrInvalidArgument
}
// ErrMergeConflicts represents an error if merging fails with a conflict // ErrMergeConflicts represents an error if merging fails with a conflict
type ErrMergeConflicts struct { type ErrMergeConflicts struct {
Style repo_model.MergeStyle Style repo_model.MergeStyle

View file

@ -3,9 +3,12 @@
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11 uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11
repo_id: 1 repo_id: 1
issue_id: 1 issue_id: 1
release_id: 0
uploader_id: 0
comment_id: 0 comment_id: 0
name: attach1 name: attach1
download_count: 0 download_count: 0
size: 0
created_unix: 946684800 created_unix: 946684800
- -
@ -13,9 +16,12 @@
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12 uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12
repo_id: 2 repo_id: 2
issue_id: 4 issue_id: 4
release_id: 0
uploader_id: 0
comment_id: 0 comment_id: 0
name: attach2 name: attach2
download_count: 1 download_count: 1
size: 0
created_unix: 946684800 created_unix: 946684800
- -
@ -23,9 +29,12 @@
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a13 uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a13
repo_id: 1 repo_id: 1
issue_id: 2 issue_id: 2
release_id: 0
uploader_id: 0
comment_id: 1 comment_id: 1
name: attach1 name: attach1
download_count: 0 download_count: 0
size: 0
created_unix: 946684800 created_unix: 946684800
- -
@ -33,9 +42,12 @@
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14 uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14
repo_id: 1 repo_id: 1
issue_id: 3 issue_id: 3
release_id: 0
uploader_id: 0
comment_id: 1 comment_id: 1
name: attach2 name: attach2
download_count: 1 download_count: 1
size: 0
created_unix: 946684800 created_unix: 946684800
- -
@ -43,9 +55,12 @@
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a15 uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a15
repo_id: 2 repo_id: 2
issue_id: 4 issue_id: 4
release_id: 0
uploader_id: 0
comment_id: 0 comment_id: 0
name: attach1 name: attach1
download_count: 0 download_count: 0
size: 0
created_unix: 946684800 created_unix: 946684800
- -
@ -53,9 +68,12 @@
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a16 uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a16
repo_id: 1 repo_id: 1
issue_id: 5 issue_id: 5
release_id: 0
uploader_id: 0
comment_id: 2 comment_id: 2
name: attach1 name: attach1
download_count: 0 download_count: 0
size: 0
created_unix: 946684800 created_unix: 946684800
- -
@ -63,9 +81,12 @@
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17 uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17
repo_id: 1 repo_id: 1
issue_id: 5 issue_id: 5
release_id: 0
uploader_id: 0
comment_id: 2 comment_id: 2
name: attach1 name: attach1
download_count: 0 download_count: 0
size: 0
created_unix: 946684800 created_unix: 946684800
- -
@ -73,34 +94,49 @@
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a18 uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a18
repo_id: 3 repo_id: 3
issue_id: 6 issue_id: 6
release_id: 0
uploader_id: 0
comment_id: 0 comment_id: 0
name: attach1 name: attach1
download_count: 0 download_count: 0
size: 0
created_unix: 946684800 created_unix: 946684800
- -
id: 9 id: 9
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a19 uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a19
repo_id: 1 repo_id: 1
issue_id: 0
release_id: 1 release_id: 1
uploader_id: 0
comment_id: 0
name: attach1 name: attach1
download_count: 0 download_count: 0
size: 0
created_unix: 946684800 created_unix: 946684800
- -
id: 10 id: 10
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a20 uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a20
repo_id: 0 # TestGetAttachment/NotLinked repo_id: 0 # TestGetAttachment/NotLinked
issue_id: 0
release_id: 0
uploader_id: 8 uploader_id: 8
comment_id: 0
name: attach1 name: attach1
download_count: 0 download_count: 0
size: 0
created_unix: 946684800 created_unix: 946684800
- -
id: 11 id: 11
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a21 uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a21
repo_id: 40 repo_id: 40
issue_id: 0
release_id: 2 release_id: 2
uploader_id: 0
comment_id: 0
name: attach1 name: attach1
download_count: 0 download_count: 0
size: 0
created_unix: 946684800 created_unix: 946684800

View file

@ -1,6 +1,5 @@
- -
id: 1 id: 1
repo_id: 1
hook_id: 1 hook_id: 1
uuid: uuid1 uuid: uuid1
is_delivered: true is_delivered: true

View file

@ -3,208 +3,287 @@
repo_id: 1 repo_id: 1
index: 1 index: 1
poster_id: 1 poster_id: 1
original_author_id: 0
name: issue1 name: issue1
content: content for the first issue content: content for the first issue
milestone_id: 0
priority: 0
is_closed: false is_closed: false
is_pull: false is_pull: false
num_comments: 2 num_comments: 2
created_unix: 946684800 created_unix: 946684800
updated_unix: 978307200 updated_unix: 978307200
is_locked: false
- -
id: 2 id: 2
repo_id: 1 repo_id: 1
index: 2 index: 2
poster_id: 1 poster_id: 1
original_author_id: 0
name: issue2 name: issue2
content: content for the second issue content: content for the second issue
milestone_id: 1 milestone_id: 1
priority: 0
is_closed: false is_closed: false
is_pull: true is_pull: true
num_comments: 0
created_unix: 946684810 created_unix: 946684810
updated_unix: 978307190 updated_unix: 978307190
is_locked: false
- -
id: 3 id: 3
repo_id: 1 repo_id: 1
index: 3 index: 3
poster_id: 1 poster_id: 1
original_author_id: 0
name: issue3 name: issue3
content: content for the third issue content: content for the third issue
milestone_id: 3 milestone_id: 3
priority: 0
is_closed: false is_closed: false
is_pull: true is_pull: true
num_comments: 0
created_unix: 946684820 created_unix: 946684820
updated_unix: 978307180 updated_unix: 978307180
is_locked: false
- -
id: 4 id: 4
repo_id: 2 repo_id: 2
index: 1 index: 1
poster_id: 2 poster_id: 2
original_author_id: 0
name: issue4 name: issue4
content: content for the fourth issue content: content for the fourth issue
milestone_id: 0
priority: 0
is_closed: true is_closed: true
is_pull: false is_pull: false
num_comments: 0
created_unix: 946684830 created_unix: 946684830
updated_unix: 978307200 updated_unix: 978307200
is_locked: false
- -
id: 5 id: 5
repo_id: 1 repo_id: 1
index: 4 index: 4
poster_id: 2 poster_id: 2
original_author_id: 0
name: issue5 name: issue5
content: content for the fifth issue content: content for the fifth issue
milestone_id: 0
priority: 0
is_closed: true is_closed: true
is_pull: false is_pull: false
num_comments: 0
created_unix: 946684840 created_unix: 946684840
updated_unix: 978307200 updated_unix: 978307200
is_locked: false
- -
id: 6 id: 6
repo_id: 3 repo_id: 3
index: 1 index: 1
poster_id: 1 poster_id: 1
original_author_id: 0
name: issue6 name: issue6
content: content6 content: content6
milestone_id: 0
priority: 0
is_closed: false is_closed: false
is_pull: false is_pull: false
num_comments: 0 num_comments: 0
created_unix: 946684850 created_unix: 946684850
updated_unix: 978307200 updated_unix: 978307200
is_locked: false
- -
id: 7 id: 7
repo_id: 2 repo_id: 2
index: 2 index: 2
poster_id: 2 poster_id: 2
original_author_id: 0
name: issue7 name: issue7
content: content for the seventh issue content: content for the seventh issue
milestone_id: 0
priority: 0
is_closed: false is_closed: false
is_pull: false is_pull: false
num_comments: 0
created_unix: 946684830 created_unix: 946684830
updated_unix: 978307200 updated_unix: 978307200
is_locked: false
- -
id: 8 id: 8
repo_id: 10 repo_id: 10
index: 1 index: 1
poster_id: 11 poster_id: 11
original_author_id: 0
name: pr2 name: pr2
content: a pull request content: a pull request
milestone_id: 0
priority: 0
is_closed: false is_closed: false
is_pull: true is_pull: true
num_comments: 0
created_unix: 946684820 created_unix: 946684820
updated_unix: 978307180 updated_unix: 978307180
is_locked: false
- -
id: 9 id: 9
repo_id: 48 repo_id: 48
index: 1 index: 1
poster_id: 11 poster_id: 11
original_author_id: 0
name: pr1 name: pr1
content: a pull request content: a pull request
milestone_id: 0
priority: 0
is_closed: false is_closed: false
is_pull: true is_pull: true
num_comments: 0
created_unix: 946684820 created_unix: 946684820
updated_unix: 978307180 updated_unix: 978307180
is_locked: false
- -
id: 10 id: 10
repo_id: 42 repo_id: 42
index: 1 index: 1
poster_id: 500 poster_id: 500
original_author_id: 0
name: issue from deleted account name: issue from deleted account
content: content from deleted account content: content from deleted account
milestone_id: 0
priority: 0
is_closed: false is_closed: false
is_pull: false is_pull: false
num_comments: 0
deadline_unix: 1019307200
created_unix: 946684830 created_unix: 946684830
updated_unix: 999307200 updated_unix: 999307200
deadline_unix: 1019307200 is_locked: false
- -
id: 11 id: 11
repo_id: 1 repo_id: 1
index: 5 index: 5
poster_id: 1 poster_id: 1
original_author_id: 0
name: pull5 name: pull5
content: content for the a pull request content: content for the a pull request
milestone_id: 0
priority: 0
is_closed: false is_closed: false
is_pull: true is_pull: true
num_comments: 0
created_unix: 1579194806 created_unix: 1579194806
updated_unix: 1579194806 updated_unix: 1579194806
is_locked: false
- -
id: 12 id: 12
repo_id: 3 repo_id: 3
index: 2 index: 2
poster_id: 2 poster_id: 2
original_author_id: 0
name: pull6 name: pull6
content: content for the a pull request content: content for the a pull request
milestone_id: 0
priority: 0
is_closed: false is_closed: false
is_pull: true is_pull: true
num_comments: 0
created_unix: 1602935696 created_unix: 1602935696
updated_unix: 1602935696 updated_unix: 1602935696
is_locked: false
- -
id: 13 id: 13
repo_id: 50 repo_id: 50
index: 1 index: 1
poster_id: 2 poster_id: 2
original_author_id: 0
name: issue in active repo name: issue in active repo
content: we'll be testing github issue 13171 with this. content: we'll be testing github issue 13171 with this.
milestone_id: 0
priority: 0
is_closed: false is_closed: false
is_pull: false is_pull: false
num_comments: 0
created_unix: 1602935696 created_unix: 1602935696
updated_unix: 1602935696 updated_unix: 1602935696
is_locked: false
- -
id: 14 id: 14
repo_id: 51 repo_id: 51
index: 1 index: 1
poster_id: 2 poster_id: 2
original_author_id: 0
name: issue in archived repo name: issue in archived repo
content: we'll be testing github issue 13171 with this. content: we'll be testing github issue 13171 with this.
milestone_id: 0
priority: 0
is_closed: false is_closed: false
is_pull: false is_pull: false
num_comments: 0
created_unix: 1602935696 created_unix: 1602935696
updated_unix: 1602935696 updated_unix: 1602935696
is_locked: false
- -
id: 15 id: 15
repo_id: 5 repo_id: 5
index: 1 index: 1
poster_id: 2 poster_id: 2
original_author_id: 0
name: issue in repo not linked to team1 name: issue in repo not linked to team1
content: content content: content
milestone_id: 0
priority: 0
is_closed: false is_closed: false
is_pull: false is_pull: false
num_comments: 0
created_unix: 1602935696 created_unix: 1602935696
updated_unix: 1602935696 updated_unix: 1602935696
is_locked: false
- -
id: 16 id: 16
repo_id: 32 repo_id: 32
index: 1 index: 1
poster_id: 2 poster_id: 2
original_author_id: 0
name: just a normal issue name: just a normal issue
content: content content: content
milestone_id: 0
priority: 0
is_closed: false is_closed: false
is_pull: false is_pull: false
num_comments: 0
created_unix: 1602935696 created_unix: 1602935696
updated_unix: 1602935696 updated_unix: 1602935696
is_locked: false
- -
id: 17 id: 17
repo_id: 32 repo_id: 32
index: 2 index: 2
poster_id: 15 poster_id: 15
original_author_id: 0
name: a issue with a assignment name: a issue with a assignment
content: content content: content
milestone_id: 0
priority: 0
is_closed: false is_closed: false
is_pull: false is_pull: false
num_comments: 0
created_unix: 1602935696 created_unix: 1602935696
updated_unix: 1602935696 updated_unix: 1602935696
is_locked: false

View file

@ -15,6 +15,7 @@
color: '#000000' color: '#000000'
num_issues: 1 num_issues: 1
num_closed_issues: 1 num_closed_issues: 1
- -
id: 3 id: 3
repo_id: 0 repo_id: 0

View file

@ -6,6 +6,7 @@
is_closed: false is_closed: false
num_issues: 1 num_issues: 1
num_closed_issues: 0 num_closed_issues: 0
completeness: 0
deadline_unix: 253370764800 deadline_unix: 253370764800
- -
@ -16,6 +17,7 @@
is_closed: false is_closed: false
num_issues: 0 num_issues: 0
num_closed_issues: 0 num_closed_issues: 0
completeness: 0
deadline_unix: 253370764800 deadline_unix: 253370764800
- -
@ -26,6 +28,7 @@
is_closed: true is_closed: true
num_issues: 1 num_issues: 1
num_closed_issues: 0 num_closed_issues: 0
completeness: 0
deadline_unix: 253370764800 deadline_unix: 253370764800
- -
@ -36,6 +39,7 @@
is_closed: false is_closed: false
num_issues: 0 num_issues: 0
num_closed_issues: 0 num_closed_issues: 0
completeness: 0
deadline_unix: 253370764800 deadline_unix: 253370764800
- -
@ -46,4 +50,5 @@
is_closed: false is_closed: false
num_issues: 0 num_issues: 0
num_closed_issues: 0 num_closed_issues: 0
completeness: 0
deadline_unix: 253370764800 deadline_unix: 253370764800

View file

@ -7,3 +7,14 @@
redirect_uris: '["a", "https://example.com/xyzzy"]' redirect_uris: '["a", "https://example.com/xyzzy"]'
created_unix: 1546869730 created_unix: 1546869730
updated_unix: 1546869730 updated_unix: 1546869730
confidential_client: true
-
id: 2
uid: 2
name: "Test native app"
client_id: "ce5a1322-42a7-11ed-b878-0242ac120002"
client_secret: "$2a$10$UYRgUSgekzBp6hYe8pAdc.cgB4Gn06QRKsORUnIYTYQADs.YR/uvi" # bcrypt of "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=
redirect_uris: '["http://127.0.0.1"]'
created_unix: 1546869730
updated_unix: 1546869730
confidential_client: false

View file

@ -6,3 +6,10 @@
redirect_uri: "a" redirect_uri: "a"
valid_until: 3546869730 valid_until: 3546869730
- id: 2
grant_id: 4
code: "authcodepublic"
code_challenge: "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg" # Code Verifier: N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt
code_challenge_method: "S256"
redirect_uri: "http://127.0.0.1/"
valid_until: 3546869730

View file

@ -21,3 +21,11 @@
scope: "openid profile email" scope: "openid profile email"
created_unix: 1546869730 created_unix: 1546869730
updated_unix: 1546869730 updated_unix: 1546869730
- id: 4
user_id: 99
application_id: 2
counter: 1
scope: "whatever"
created_unix: 1546869730
updated_unix: 1546869730

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,15 @@
-
id: 1
setting_key: 'disable_gravatar'
setting_value: 'false'
version: 1
created: 1653533198
updated: 1653533198
-
id: 2
setting_key: 'enable_federated_avatar'
setting_value: 'false'
version: 1
created: 1653533198
updated: 1653533198

View file

@ -6,6 +6,7 @@
authorize: 4 # owner authorize: 4 # owner
num_repos: 3 num_repos: 3
num_members: 1 num_members: 1
includes_all_repositories: false
can_create_org_repo: true can_create_org_repo: true
- -
@ -16,6 +17,7 @@
authorize: 2 # write authorize: 2 # write
num_repos: 1 num_repos: 1
num_members: 2 num_members: 2
includes_all_repositories: false
can_create_org_repo: false can_create_org_repo: false
- -
@ -26,6 +28,7 @@
authorize: 4 # owner authorize: 4 # owner
num_repos: 0 num_repos: 0
num_members: 1 num_members: 1
includes_all_repositories: false
can_create_org_repo: true can_create_org_repo: true
- -
@ -36,6 +39,7 @@
authorize: 4 # owner authorize: 4 # owner
num_repos: 0 num_repos: 0
num_members: 1 num_members: 1
includes_all_repositories: false
can_create_org_repo: true can_create_org_repo: true
- -
@ -46,6 +50,7 @@
authorize: 4 # owner authorize: 4 # owner
num_repos: 2 num_repos: 2
num_members: 2 num_members: 2
includes_all_repositories: false
can_create_org_repo: true can_create_org_repo: true
- -
@ -56,6 +61,7 @@
authorize: 4 # owner authorize: 4 # owner
num_repos: 2 num_repos: 2
num_members: 2 num_members: 2
includes_all_repositories: false
can_create_org_repo: true can_create_org_repo: true
- -
@ -66,6 +72,7 @@
authorize: 2 # write authorize: 2 # write
num_repos: 1 num_repos: 1
num_members: 1 num_members: 1
includes_all_repositories: false
can_create_org_repo: false can_create_org_repo: false
- -
@ -76,6 +83,7 @@
authorize: 2 # write authorize: 2 # write
num_repos: 1 num_repos: 1
num_members: 1 num_members: 1
includes_all_repositories: false
can_create_org_repo: false can_create_org_repo: false
- -
@ -86,6 +94,7 @@
authorize: 1 # read authorize: 1 # read
num_repos: 1 num_repos: 1
num_members: 2 num_members: 2
includes_all_repositories: false
can_create_org_repo: false can_create_org_repo: false
- -
@ -93,9 +102,10 @@
org_id: 25 org_id: 25
lower_name: notowners lower_name: notowners
name: NotOwners name: NotOwners
authorize: 1 # owner authorize: 1 # read
num_repos: 0 num_repos: 0
num_members: 1 num_members: 1
includes_all_repositories: false
can_create_org_repo: false can_create_org_repo: false
- -
@ -106,6 +116,7 @@
authorize: 1 # read authorize: 1 # read
num_repos: 0 num_repos: 0
num_members: 0 num_members: 0
includes_all_repositories: false
can_create_org_repo: false can_create_org_repo: false
- -
@ -116,6 +127,7 @@
authorize: 3 # admin authorize: 3 # admin
num_repos: 0 num_repos: 0
num_members: 1 num_members: 1
includes_all_repositories: false
can_create_org_repo: true can_create_org_repo: true
- -
@ -126,4 +138,5 @@
authorize: 3 # admin authorize: 3 # admin
num_repos: 0 num_repos: 0
num_members: 1 num_members: 1
includes_all_repositories: false
can_create_org_repo: false can_create_org_repo: false

View file

@ -8,18 +8,22 @@
name: database name: database
repo_count: 1 repo_count: 1
- id: 3 -
id: 3
name: SQL name: SQL
repo_count: 1 repo_count: 1
- id: 4 -
id: 4
name: graphql name: graphql
repo_count: 1 repo_count: 1
- id: 5 -
id: 5
name: topicname1 name: topicname1
repo_count: 1 repo_count: 1
- id: 6 -
id: 6
name: topicname2 name: topicname2
repo_count: 2 repo_count: 2

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
- id: 1 -
name: "WebAuthn credential" id: 1
name: WebAuthn credential
user_id: 32 user_id: 32
attestation_type: none attestation_type: none
sign_count: 0 sign_count: 0

View file

@ -6,6 +6,8 @@ package foreignreference
import ( import (
"fmt" "fmt"
"code.gitea.io/gitea/modules/util"
) )
// ErrLocalIndexNotExist represents a "LocalIndexNotExist" kind of error. // ErrLocalIndexNotExist represents a "LocalIndexNotExist" kind of error.
@ -25,6 +27,10 @@ func (err ErrLocalIndexNotExist) Error() string {
return fmt.Sprintf("repository %d has no LocalIndex for ForeignIndex %d of type %s", err.RepoID, err.ForeignIndex, err.Type) return fmt.Sprintf("repository %d has no LocalIndex for ForeignIndex %d of type %s", err.RepoID, err.ForeignIndex, err.Type)
} }
func (err ErrLocalIndexNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrForeignIndexNotExist represents a "ForeignIndexNotExist" kind of error. // ErrForeignIndexNotExist represents a "ForeignIndexNotExist" kind of error.
type ErrForeignIndexNotExist struct { type ErrForeignIndexNotExist struct {
RepoID int64 RepoID int64
@ -41,3 +47,7 @@ func IsErrForeignIndexNotExist(err error) bool {
func (err ErrForeignIndexNotExist) Error() string { func (err ErrForeignIndexNotExist) Error() string {
return fmt.Sprintf("repository %d has no ForeignIndex for LocalIndex %d of type %s", err.RepoID, err.LocalIndex, err.Type) return fmt.Sprintf("repository %d has no ForeignIndex for LocalIndex %d of type %s", err.RepoID, err.LocalIndex, err.Type)
} }
func (err ErrForeignIndexNotExist) Unwrap() error {
return util.ErrNotExist
}

View file

@ -6,7 +6,6 @@ package git
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -17,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder" "xorm.io/builder"
) )
@ -38,6 +38,10 @@ func (err ErrLFSLockNotExist) Error() string {
return fmt.Sprintf("lfs lock does not exist [id: %d, rid: %d, path: %s]", err.ID, err.RepoID, err.Path) return fmt.Sprintf("lfs lock does not exist [id: %d, rid: %d, path: %s]", err.ID, err.RepoID, err.Path)
} }
func (err ErrLFSLockNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrLFSUnauthorizedAction represents a "LFSUnauthorizedAction" kind of error. // ErrLFSUnauthorizedAction represents a "LFSUnauthorizedAction" kind of error.
type ErrLFSUnauthorizedAction struct { type ErrLFSUnauthorizedAction struct {
RepoID int64 RepoID int64
@ -58,6 +62,10 @@ func (err ErrLFSUnauthorizedAction) Error() string {
return fmt.Sprintf("User %s doesn't have read access for lfs lock [rid: %d]", err.UserName, err.RepoID) return fmt.Sprintf("User %s doesn't have read access for lfs lock [rid: %d]", err.UserName, err.RepoID)
} }
func (err ErrLFSUnauthorizedAction) Unwrap() error {
return util.ErrPermissionDenied
}
// ErrLFSLockAlreadyExist represents a "LFSLockAlreadyExist" kind of error. // ErrLFSLockAlreadyExist represents a "LFSLockAlreadyExist" kind of error.
type ErrLFSLockAlreadyExist struct { type ErrLFSLockAlreadyExist struct {
RepoID int64 RepoID int64
@ -74,6 +82,10 @@ func (err ErrLFSLockAlreadyExist) Error() string {
return fmt.Sprintf("lfs lock already exists [rid: %d, path: %s]", err.RepoID, err.Path) return fmt.Sprintf("lfs lock already exists [rid: %d, path: %s]", err.RepoID, err.Path)
} }
func (err ErrLFSLockAlreadyExist) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrLFSFileLocked represents a "LFSFileLocked" kind of error. // ErrLFSFileLocked represents a "LFSFileLocked" kind of error.
type ErrLFSFileLocked struct { type ErrLFSFileLocked struct {
RepoID int64 RepoID int64
@ -91,6 +103,10 @@ func (err ErrLFSFileLocked) Error() string {
return fmt.Sprintf("File is lfs locked [repo: %d, locked by: %s, path: %s]", err.RepoID, err.UserName, err.Path) return fmt.Sprintf("File is lfs locked [repo: %d, locked by: %s, path: %s]", err.RepoID, err.UserName, err.Path)
} }
func (err ErrLFSFileLocked) Unwrap() error {
return util.ErrPermissionDenied
}
// LFSMetaObject stores metadata for LFS tracked files. // LFSMetaObject stores metadata for LFS tracked files.
type LFSMetaObject struct { type LFSMetaObject struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
@ -114,7 +130,7 @@ type LFSTokenResponse struct {
// ErrLFSObjectNotExist is returned from lfs models functions in order // ErrLFSObjectNotExist is returned from lfs models functions in order
// to differentiate between database and missing object errors. // to differentiate between database and missing object errors.
var ErrLFSObjectNotExist = errors.New("LFS Meta object does not exist") var ErrLFSObjectNotExist = db.ErrNotExist{Resource: "LFS Meta object"}
// NewLFSMetaObject stores a given populated LFSMetaObject structure in the database // NewLFSMetaObject stores a given populated LFSMetaObject structure in the database
// if it is not already present. // if it is not already present.

View file

@ -28,6 +28,7 @@ import (
"code.gitea.io/gitea/modules/references" "code.gitea.io/gitea/modules/references"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder" "xorm.io/builder"
"xorm.io/xorm" "xorm.io/xorm"
@ -49,6 +50,10 @@ func (err ErrCommentNotExist) Error() string {
return fmt.Sprintf("comment does not exist [id: %d, issue_id: %d]", err.ID, err.IssueID) return fmt.Sprintf("comment does not exist [id: %d, issue_id: %d]", err.ID, err.IssueID)
} }
func (err ErrCommentNotExist) Unwrap() error {
return util.ErrNotExist
}
// CommentType defines whether a comment is just a simple comment, an action (like close) or a reference. // CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
type CommentType int type CommentType int

View file

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder" "xorm.io/builder"
) )
@ -201,6 +202,10 @@ func (err ErrIssueContentHistoryNotExist) Error() string {
return fmt.Sprintf("issue content history does not exist [id: %d]", err.ID) return fmt.Sprintf("issue content history does not exist [id: %d]", err.ID)
} }
func (err ErrIssueContentHistoryNotExist) Unwrap() error {
return util.ErrNotExist
}
// GetIssueContentHistoryByID get issue content history // GetIssueContentHistoryByID get issue content history
func GetIssueContentHistoryByID(dbCtx context.Context, id int64) (*ContentHistory, error) { func GetIssueContentHistoryByID(dbCtx context.Context, id int64) (*ContentHistory, error) {
h := &ContentHistory{} h := &ContentHistory{}

View file

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
) )
// ErrDependencyExists represents a "DependencyAlreadyExists" kind of error. // ErrDependencyExists represents a "DependencyAlreadyExists" kind of error.
@ -29,6 +30,10 @@ func (err ErrDependencyExists) Error() string {
return fmt.Sprintf("issue dependency does already exist [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID) return fmt.Sprintf("issue dependency does already exist [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
} }
func (err ErrDependencyExists) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrDependencyNotExists represents a "DependencyAlreadyExists" kind of error. // ErrDependencyNotExists represents a "DependencyAlreadyExists" kind of error.
type ErrDependencyNotExists struct { type ErrDependencyNotExists struct {
IssueID int64 IssueID int64
@ -45,6 +50,10 @@ func (err ErrDependencyNotExists) Error() string {
return fmt.Sprintf("issue dependency does not exist [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID) return fmt.Sprintf("issue dependency does not exist [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
} }
func (err ErrDependencyNotExists) Unwrap() error {
return util.ErrNotExist
}
// ErrCircularDependency represents a "DependencyCircular" kind of error. // ErrCircularDependency represents a "DependencyCircular" kind of error.
type ErrCircularDependency struct { type ErrCircularDependency struct {
IssueID int64 IssueID int64
@ -91,6 +100,10 @@ func (err ErrUnknownDependencyType) Error() string {
return fmt.Sprintf("unknown dependency type [type: %d]", err.Type) return fmt.Sprintf("unknown dependency type [type: %d]", err.Type)
} }
func (err ErrUnknownDependencyType) Unwrap() error {
return util.ErrInvalidArgument
}
// IssueDependency represents an issue dependency // IssueDependency represents an issue dependency
type IssueDependency struct { type IssueDependency struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`

View file

@ -13,7 +13,6 @@ import (
"strconv" "strconv"
"strings" "strings"
admin_model "code.gitea.io/gitea/models/admin"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/foreignreference" "code.gitea.io/gitea/models/foreignreference"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
@ -21,6 +20,7 @@ import (
access_model "code.gitea.io/gitea/models/perm/access" access_model "code.gitea.io/gitea/models/perm/access"
project_model "code.gitea.io/gitea/models/project" project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
@ -52,6 +52,10 @@ func (err ErrIssueNotExist) Error() string {
return fmt.Sprintf("issue does not exist [id: %d, repo_id: %d, index: %d]", err.ID, err.RepoID, err.Index) return fmt.Sprintf("issue does not exist [id: %d, repo_id: %d, index: %d]", err.ID, err.RepoID, err.Index)
} }
func (err ErrIssueNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrIssueIsClosed represents a "IssueIsClosed" kind of error. // ErrIssueIsClosed represents a "IssueIsClosed" kind of error.
type ErrIssueIsClosed struct { type ErrIssueIsClosed struct {
ID int64 ID int64
@ -1064,19 +1068,19 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
// NewIssue creates new issue with labels for repository. // NewIssue creates new issue with labels for repository.
func NewIssue(repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) { func NewIssue(repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
idx, err := db.GetNextResourceIndex("issue_index", repo.ID)
if err != nil {
return fmt.Errorf("generate issue index failed: %v", err)
}
issue.Index = idx
ctx, committer, err := db.TxContext() ctx, committer, err := db.TxContext()
if err != nil { if err != nil {
return err return err
} }
defer committer.Close() defer committer.Close()
idx, err := db.GetNextResourceIndex(ctx, "issue_index", repo.ID)
if err != nil {
return fmt.Errorf("generate issue index failed: %w", err)
}
issue.Index = idx
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{ if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
Repo: repo, Repo: repo,
Issue: issue, Issue: issue,
@ -1492,7 +1496,8 @@ func applySubscribedCondition(sess *xorm.Session, subscriberID int64) *xorm.Sess
builder.In("issue.repo_id", builder. builder.In("issue.repo_id", builder.
Select("id"). Select("id").
From("watch"). From("watch").
Where(builder.Eq{"user_id": subscriberID, "mode": true}), Where(builder.And(builder.Eq{"user_id": subscriberID},
builder.In("mode", repo_model.WatchModeNormal, repo_model.WatchModeAuto))),
), ),
), ),
) )
@ -2470,7 +2475,7 @@ func DeleteOrphanedIssues() error {
// Remove issue attachment files. // Remove issue attachment files.
for i := range attachmentPaths { for i := range attachmentPaths {
admin_model.RemoveAllWithNotice(db.DefaultContext, "Delete issue attachment", attachmentPaths[i]) system_model.RemoveAllWithNotice(db.DefaultContext, "Delete issue attachment", attachmentPaths[i])
} }
return nil return nil
} }

View file

@ -15,16 +15,12 @@ func RecalculateIssueIndexForRepo(repoID int64) error {
} }
defer committer.Close() defer committer.Close()
if err := db.UpsertResourceIndex(ctx, "issue_index", repoID); err != nil {
return err
}
var max int64 var max int64
if _, err := db.GetEngine(ctx).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 return err
} }
if _, err := db.GetEngine(ctx).Exec("UPDATE `issue_index` SET max_index=? WHERE group_id=?", max, repoID); err != nil { if err = db.SyncMaxResourceIndex(ctx, "issue_index", repoID, max); err != nil {
return err return err
} }

View file

@ -131,7 +131,11 @@ func testCreateIssue(t *testing.T, repo, doer int64, title, content string, ispu
r := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo}) r := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo})
d := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: doer}) d := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: doer})
idx, err := db.GetNextResourceIndex("issue_index", r.ID) ctx, committer, err := db.TxContext()
assert.NoError(t, err)
defer committer.Close()
idx, err := db.GetNextResourceIndex(ctx, "issue_index", r.ID)
assert.NoError(t, err) assert.NoError(t, err)
i := &issues_model.Issue{ i := &issues_model.Issue{
RepoID: r.ID, RepoID: r.ID,
@ -143,9 +147,6 @@ func testCreateIssue(t *testing.T, repo, doer int64, title, content string, ispu
Index: idx, Index: idx,
} }
ctx, committer, err := db.TxContext()
assert.NoError(t, err)
defer committer.Close()
err = issues_model.NewIssueWithIndex(ctx, d, issues_model.NewIssueOptions{ err = issues_model.NewIssueWithIndex(ctx, d, issues_model.NewIssueOptions{
Repo: r, Repo: r,
Issue: i, Issue: i,

View file

@ -38,6 +38,10 @@ func (err ErrRepoLabelNotExist) Error() string {
return fmt.Sprintf("label does not exist [label_id: %d, repo_id: %d]", err.LabelID, err.RepoID) return fmt.Sprintf("label does not exist [label_id: %d, repo_id: %d]", err.LabelID, err.RepoID)
} }
func (err ErrRepoLabelNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrOrgLabelNotExist represents a "OrgLabelNotExist" kind of error. // ErrOrgLabelNotExist represents a "OrgLabelNotExist" kind of error.
type ErrOrgLabelNotExist struct { type ErrOrgLabelNotExist struct {
LabelID int64 LabelID int64
@ -54,6 +58,10 @@ func (err ErrOrgLabelNotExist) Error() string {
return fmt.Sprintf("label does not exist [label_id: %d, org_id: %d]", err.LabelID, err.OrgID) return fmt.Sprintf("label does not exist [label_id: %d, org_id: %d]", err.LabelID, err.OrgID)
} }
func (err ErrOrgLabelNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrLabelNotExist represents a "LabelNotExist" kind of error. // ErrLabelNotExist represents a "LabelNotExist" kind of error.
type ErrLabelNotExist struct { type ErrLabelNotExist struct {
LabelID int64 LabelID int64
@ -69,6 +77,10 @@ func (err ErrLabelNotExist) Error() string {
return fmt.Sprintf("label does not exist [label_id: %d]", err.LabelID) return fmt.Sprintf("label does not exist [label_id: %d]", err.LabelID)
} }
func (err ErrLabelNotExist) Unwrap() error {
return util.ErrNotExist
}
// LabelColorPattern is a regexp witch can validate LabelColor // LabelColorPattern is a regexp witch can validate LabelColor
var LabelColorPattern = regexp.MustCompile("^#?(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{3})$") var LabelColorPattern = regexp.MustCompile("^#?(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{3})$")

View file

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder" "xorm.io/builder"
) )
@ -39,6 +40,10 @@ func (err ErrMilestoneNotExist) Error() string {
return fmt.Sprintf("milestone does not exist [id: %d, repo_id: %d]", err.ID, err.RepoID) return fmt.Sprintf("milestone does not exist [id: %d, repo_id: %d]", err.ID, err.RepoID)
} }
func (err ErrMilestoneNotExist) Unwrap() error {
return util.ErrNotExist
}
// Milestone represents a milestone of repository. // Milestone represents a milestone of repository.
type Milestone struct { type Milestone struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`

View file

@ -46,6 +46,10 @@ func (err ErrPullRequestNotExist) Error() string {
err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBranch, err.BaseBranch) err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBranch, err.BaseBranch)
} }
func (err ErrPullRequestNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrPullRequestAlreadyExists represents a "PullRequestAlreadyExists"-error // ErrPullRequestAlreadyExists represents a "PullRequestAlreadyExists"-error
type ErrPullRequestAlreadyExists struct { type ErrPullRequestAlreadyExists struct {
ID int64 ID int64
@ -68,6 +72,10 @@ func (err ErrPullRequestAlreadyExists) Error() string {
err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBranch, err.BaseBranch) err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBranch, err.BaseBranch)
} }
func (err ErrPullRequestAlreadyExists) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrPullRequestHeadRepoMissing represents a "ErrPullRequestHeadRepoMissing" error // ErrPullRequestHeadRepoMissing represents a "ErrPullRequestHeadRepoMissing" error
type ErrPullRequestHeadRepoMissing struct { type ErrPullRequestHeadRepoMissing struct {
ID int64 ID int64
@ -490,13 +498,6 @@ func (pr *PullRequest) SetMerged(ctx context.Context) (bool, error) {
// NewPullRequest creates new pull request with labels for repository. // NewPullRequest creates new pull request with labels for repository.
func NewPullRequest(outerCtx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string, pr *PullRequest) (err error) { func NewPullRequest(outerCtx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string, pr *PullRequest) (err error) {
idx, err := db.GetNextResourceIndex("issue_index", repo.ID)
if err != nil {
return fmt.Errorf("generate pull request index failed: %v", err)
}
issue.Index = idx
ctx, committer, err := db.TxContext() ctx, committer, err := db.TxContext()
if err != nil { if err != nil {
return err return err
@ -504,6 +505,13 @@ func NewPullRequest(outerCtx context.Context, repo *repo_model.Repository, issue
defer committer.Close() defer committer.Close()
ctx.WithContext(outerCtx) ctx.WithContext(outerCtx)
idx, err := db.GetNextResourceIndex(ctx, "issue_index", repo.ID)
if err != nil {
return fmt.Errorf("generate pull request index failed: %v", err)
}
issue.Index = idx
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{ if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
Repo: repo, Repo: repo,
Issue: issue, Issue: issue,

View file

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder" "xorm.io/builder"
) )
@ -34,6 +35,10 @@ func (err ErrForbiddenIssueReaction) Error() string {
return fmt.Sprintf("'%s' is not an allowed reaction", err.Reaction) return fmt.Sprintf("'%s' is not an allowed reaction", err.Reaction)
} }
func (err ErrForbiddenIssueReaction) Unwrap() error {
return util.ErrPermissionDenied
}
// ErrReactionAlreadyExist is used when a existing reaction was try to created // ErrReactionAlreadyExist is used when a existing reaction was try to created
type ErrReactionAlreadyExist struct { type ErrReactionAlreadyExist struct {
Reaction string Reaction string
@ -49,6 +54,10 @@ func (err ErrReactionAlreadyExist) Error() string {
return fmt.Sprintf("reaction '%s' already exists", err.Reaction) return fmt.Sprintf("reaction '%s' already exists", err.Reaction)
} }
func (err ErrReactionAlreadyExist) Unwrap() error {
return util.ErrAlreadyExist
}
// Reaction represents a reactions on issues and comments. // Reaction represents a reactions on issues and comments.
type Reaction struct { type Reaction struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`

View file

@ -39,6 +39,10 @@ func (err ErrReviewNotExist) Error() string {
return fmt.Sprintf("review does not exist [id: %d]", err.ID) return fmt.Sprintf("review does not exist [id: %d]", err.ID)
} }
func (err ErrReviewNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrNotValidReviewRequest an not allowed review request modify // ErrNotValidReviewRequest an not allowed review request modify
type ErrNotValidReviewRequest struct { type ErrNotValidReviewRequest struct {
Reason string Reason string
@ -59,6 +63,10 @@ func (err ErrNotValidReviewRequest) Error() string {
err.RepoID) err.RepoID)
} }
func (err ErrNotValidReviewRequest) Unwrap() error {
return util.ErrInvalidArgument
}
// ReviewType defines the sort of feedback a review gives // ReviewType defines the sort of feedback a review gives
type ReviewType int type ReviewType int

View file

@ -25,6 +25,10 @@ func (err ErrIssueStopwatchNotExist) Error() string {
return fmt.Sprintf("issue stopwatch doesn't exist[uid: %d, issue_id: %d", err.UserID, err.IssueID) return fmt.Sprintf("issue stopwatch doesn't exist[uid: %d, issue_id: %d", err.UserID, err.IssueID)
} }
func (err ErrIssueStopwatchNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrIssueStopwatchAlreadyExist represents an error that stopwatch is already exist // ErrIssueStopwatchAlreadyExist represents an error that stopwatch is already exist
type ErrIssueStopwatchAlreadyExist struct { type ErrIssueStopwatchAlreadyExist struct {
UserID int64 UserID int64
@ -35,6 +39,10 @@ func (err ErrIssueStopwatchAlreadyExist) Error() string {
return fmt.Sprintf("issue stopwatch already exists[uid: %d, issue_id: %d", err.UserID, err.IssueID) return fmt.Sprintf("issue stopwatch already exists[uid: %d, issue_id: %d", err.UserID, err.IssueID)
} }
func (err ErrIssueStopwatchAlreadyExist) Unwrap() error {
return util.ErrAlreadyExist
}
// Stopwatch represents a stopwatch for time tracking. // Stopwatch represents a stopwatch for time tracking.
type Stopwatch struct { type Stopwatch struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`

View file

@ -236,7 +236,7 @@ func DeleteIssueUserTimes(issue *Issue, user *user_model.User) error {
return err return err
} }
if removedTime == 0 { if removedTime == 0 {
return db.ErrNotExist{} return db.ErrNotExist{Resource: "tracked_time"}
} }
if err := issue.LoadRepo(ctx); err != nil { if err := issue.LoadRepo(ctx); err != nil {
@ -296,7 +296,7 @@ func deleteTimes(ctx context.Context, opts FindTrackedTimesOptions) (removedTime
func deleteTime(ctx context.Context, t *TrackedTime) error { func deleteTime(ctx context.Context, t *TrackedTime) error {
if t.Deleted { if t.Deleted {
return db.ErrNotExist{ID: t.ID} return db.ErrNotExist{Resource: "tracked_time", ID: t.ID}
} }
t.Deleted = true t.Deleted = true
_, err := db.GetEngine(ctx).ID(t.ID).Cols("deleted").Update(t) _, err := db.GetEngine(ctx).ID(t.ID).Cols("deleted").Update(t)
@ -310,7 +310,7 @@ func GetTrackedTimeByID(id int64) (*TrackedTime, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} else if !has { } else if !has {
return nil, db.ErrNotExist{ID: id} return nil, db.ErrNotExist{Resource: "tracked_time", ID: id}
} }
return time, nil return time, nil
} }

View file

@ -14,6 +14,8 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
_ "code.gitea.io/gitea/models/system"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View file

@ -0,0 +1,19 @@
# type Milestone struct {
# ID int64 `xorm:"pk autoincr"`
# IsClosed bool
# NumIssues int
# NumClosedIssues int
# Completeness int // Percentage(1-100).
# }
-
id: 1
is_closed: false
num_issues: 3
num_closed_issues: 1
completeness: 33
-
id: 2
is_closed: true
num_issues: 5
num_closed_issues: 5
completeness: 100

View file

@ -0,0 +1,25 @@
# type Issue struct {
# ID int64 `xorm:"pk autoincr"`
# RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"`
# Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
# MilestoneID int64 `xorm:"INDEX"`
# IsClosed bool `xorm:"INDEX"`
# }
-
id: 1
repo_id: 1
index: 1
milestone_id: 1
is_closed: false
-
id: 2
repo_id: 1
index: 2
milestone_id: 1
is_closed: true
-
id: 4
repo_id: 1
index: 3
milestone_id: 1
is_closed: false

View file

@ -0,0 +1,19 @@
# type Milestone struct {
# ID int64 `xorm:"pk autoincr"`
# IsClosed bool
# NumIssues int
# NumClosedIssues int
# Completeness int // Percentage(1-100).
# }
-
id: 1
is_closed: false
num_issues: 4
num_closed_issues: 2
completeness: 50
-
id: 2
is_closed: true
num_issues: 5
num_closed_issues: 5
completeness: 100

View file

@ -415,6 +415,14 @@ var migrations = []Migration{
NewMigration("Alter gpg_key/public_key content TEXT fields to MEDIUMTEXT", alterPublicGPGKeyContentFieldsToMediumText), NewMigration("Alter gpg_key/public_key content TEXT fields to MEDIUMTEXT", alterPublicGPGKeyContentFieldsToMediumText),
// v226 -> v227 // v226 -> v227
NewMigration("Conan and generic packages do not need to be semantically versioned", fixPackageSemverField), NewMigration("Conan and generic packages do not need to be semantically versioned", fixPackageSemverField),
// v227 -> v228
NewMigration("Create key/value table for system settings", createSystemSettingsTable),
// v228 -> v229
NewMigration("Add TeamInvite table", addTeamInviteTable),
// v229 -> v230
NewMigration("Update counts of all open milestones", updateOpenMilestoneCounts),
// v230 -> v231
NewMigration("Add ConfidentialClient column (default true) to OAuth2Application table", addConfidentialClientColumnToOAuth2ApplicationTable),
} }
// GetCurrentDBVersion returns the current db version // GetCurrentDBVersion returns the current db version

View file

@ -83,17 +83,17 @@ func fixMergeBase(x *xorm.Engine) error {
if !pr.HasMerged { if !pr.HasMerged {
var err error var err error
pr.MergeBase, _, err = git.NewCommand(git.DefaultContext, "merge-base", "--", pr.BaseBranch, gitRefName).RunStdString(&git.RunOpts{Dir: repoPath}) pr.MergeBase, _, err = git.NewCommand(git.DefaultContext, "merge-base").AddDashesAndList(pr.BaseBranch, gitRefName).RunStdString(&git.RunOpts{Dir: repoPath})
if err != nil { if err != nil {
var err2 error var err2 error
pr.MergeBase, _, err2 = git.NewCommand(git.DefaultContext, "rev-parse", git.BranchPrefix+pr.BaseBranch).RunStdString(&git.RunOpts{Dir: repoPath}) pr.MergeBase, _, err2 = git.NewCommand(git.DefaultContext, "rev-parse").AddDynamicArguments(git.BranchPrefix + pr.BaseBranch).RunStdString(&git.RunOpts{Dir: repoPath})
if err2 != nil { if err2 != nil {
log.Error("Unable to get merge base for PR ID %d, Index %d in %s/%s. Error: %v & %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err, err2) log.Error("Unable to get merge base for PR ID %d, Index %d in %s/%s. Error: %v & %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err, err2)
continue continue
} }
} }
} else { } else {
parentsString, _, err := git.NewCommand(git.DefaultContext, "rev-list", "--parents", "-n", "1", pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath}) parentsString, _, err := git.NewCommand(git.DefaultContext, "rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath})
if err != nil { if err != nil {
log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
continue continue
@ -103,10 +103,11 @@ func fixMergeBase(x *xorm.Engine) error {
continue continue
} }
args := append([]string{"merge-base", "--"}, parents[1:]...) refs := append([]string{}, parents[1:]...)
args = append(args, gitRefName) refs = append(refs, gitRefName)
cmd := git.NewCommand(git.DefaultContext, "merge-base").AddDashesAndList(refs...)
pr.MergeBase, _, err = git.NewCommand(git.DefaultContext, args...).RunStdString(&git.RunOpts{Dir: repoPath}) pr.MergeBase, _, err = cmd.RunStdString(&git.RunOpts{Dir: repoPath})
if err != nil { if err != nil {
log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
continue continue

View file

@ -80,7 +80,7 @@ func refixMergeBase(x *xorm.Engine) error {
gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index) gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index)
parentsString, _, err := git.NewCommand(git.DefaultContext, "rev-list", "--parents", "-n", "1", pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath}) parentsString, _, err := git.NewCommand(git.DefaultContext, "rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath})
if err != nil { if err != nil {
log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
continue continue
@ -91,10 +91,11 @@ func refixMergeBase(x *xorm.Engine) error {
} }
// we should recalculate // we should recalculate
args := append([]string{"merge-base", "--"}, parents[1:]...) refs := append([]string{}, parents[1:]...)
args = append(args, gitRefName) refs = append(refs, gitRefName)
cmd := git.NewCommand(git.DefaultContext, "merge-base").AddDashesAndList(refs...)
pr.MergeBase, _, err = git.NewCommand(git.DefaultContext, args...).RunStdString(&git.RunOpts{Dir: repoPath}) pr.MergeBase, _, err = cmd.RunStdString(&git.RunOpts{Dir: repoPath})
if err != nil { if err != nil {
log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
continue continue

64
models/migrations/v227.go Normal file
View file

@ -0,0 +1,64 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"fmt"
"strconv"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
type SystemSetting struct {
ID int64 `xorm:"pk autoincr"`
SettingKey string `xorm:"varchar(255) unique"` // ensure key is always lowercase
SettingValue string `xorm:"text"`
Version int `xorm:"version"` // prevent to override
Created timeutil.TimeStamp `xorm:"created"`
Updated timeutil.TimeStamp `xorm:"updated"`
}
func insertSettingsIfNotExist(x *xorm.Engine, sysSettings []*SystemSetting) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
for _, setting := range sysSettings {
exist, err := sess.Table("system_setting").Where("setting_key=?", setting.SettingKey).Exist()
if err != nil {
return err
}
if !exist {
if _, err := sess.Insert(setting); err != nil {
return err
}
}
}
return sess.Commit()
}
func createSystemSettingsTable(x *xorm.Engine) error {
if err := x.Sync2(new(SystemSetting)); err != nil {
return fmt.Errorf("sync2: %v", err)
}
// migrate xx to database
sysSettings := []*SystemSetting{
{
SettingKey: "picture.disable_gravatar",
SettingValue: strconv.FormatBool(setting.DisableGravatar),
},
{
SettingKey: "picture.enable_federated_avatar",
SettingValue: strconv.FormatBool(setting.EnableFederatedAvatar),
},
}
return insertSettingsIfNotExist(x, sysSettings)
}

26
models/migrations/v228.go Normal file
View file

@ -0,0 +1,26 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
func addTeamInviteTable(x *xorm.Engine) error {
type TeamInvite struct {
ID int64 `xorm:"pk autoincr"`
Token string `xorm:"UNIQUE(token) INDEX NOT NULL DEFAULT ''"`
InviterID int64 `xorm:"NOT NULL DEFAULT 0"`
OrgID int64 `xorm:"INDEX NOT NULL DEFAULT 0"`
TeamID int64 `xorm:"UNIQUE(team_mail) INDEX NOT NULL DEFAULT 0"`
Email string `xorm:"UNIQUE(team_mail) NOT NULL DEFAULT ''"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
return x.Sync2(new(TeamInvite))
}

47
models/migrations/v229.go Normal file
View file

@ -0,0 +1,47 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"fmt"
"code.gitea.io/gitea/models/issues"
"xorm.io/builder"
"xorm.io/xorm"
)
func updateOpenMilestoneCounts(x *xorm.Engine) error {
var openMilestoneIDs []int64
err := x.Table("milestone").Select("id").Where(builder.Neq{"is_closed": 1}).Find(&openMilestoneIDs)
if err != nil {
return fmt.Errorf("error selecting open milestone IDs: %w", err)
}
for _, id := range openMilestoneIDs {
_, err := x.ID(id).
SetExpr("num_issues", builder.Select("count(*)").From("issue").Where(
builder.Eq{"milestone_id": id},
)).
SetExpr("num_closed_issues", builder.Select("count(*)").From("issue").Where(
builder.Eq{
"milestone_id": id,
"is_closed": true,
},
)).
Update(&issues.Milestone{})
if err != nil {
return fmt.Errorf("error updating issue counts in milestone %d: %w", id, err)
}
_, err = x.Exec("UPDATE `milestone` SET completeness=100*num_closed_issues/(CASE WHEN num_issues > 0 THEN num_issues ELSE 1 END) WHERE id=?",
id,
)
if err != nil {
return fmt.Errorf("error setting completeness on milestone %d: %w", id, err)
}
}
return nil
}

View file

@ -0,0 +1,46 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"testing"
"code.gitea.io/gitea/models/issues"
"github.com/stretchr/testify/assert"
)
func Test_updateOpenMilestoneCounts(t *testing.T) {
type ExpectedMilestone issues.Milestone
// Prepare and load the testing database
x, deferable := prepareTestEnv(t, 0, new(issues.Milestone), new(ExpectedMilestone), new(issues.Issue))
defer deferable()
if x == nil || t.Failed() {
return
}
if err := updateOpenMilestoneCounts(x); err != nil {
assert.NoError(t, err)
return
}
expected := []ExpectedMilestone{}
if err := x.Table("expected_milestone").Asc("id").Find(&expected); !assert.NoError(t, err) {
return
}
got := []issues.Milestone{}
if err := x.Table("milestone").Asc("id").Find(&got); !assert.NoError(t, err) {
return
}
for i, e := range expected {
got := got[i]
assert.Equal(t, e.ID, got.ID)
assert.Equal(t, e.NumIssues, got.NumIssues)
assert.Equal(t, e.NumClosedIssues, got.NumClosedIssues)
}
}

18
models/migrations/v230.go Normal file
View file

@ -0,0 +1,18 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"xorm.io/xorm"
)
// addConfidentialColumnToOAuth2ApplicationTable: add ConfidentialClient column, setting existing rows to true
func addConfidentialClientColumnToOAuth2ApplicationTable(x *xorm.Engine) error {
type OAuth2Application struct {
ConfidentialClient bool `xorm:"NOT NULL DEFAULT TRUE"`
}
return x.Sync(new(OAuth2Application))
}

View file

@ -0,0 +1,46 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_addConfidentialClientColumnToOAuth2ApplicationTable(t *testing.T) {
// premigration
type OAuth2Application struct {
ID int64
}
// Prepare and load the testing database
x, deferable := prepareTestEnv(t, 0, new(OAuth2Application))
defer deferable()
if x == nil || t.Failed() {
return
}
if err := addConfidentialClientColumnToOAuth2ApplicationTable(x); err != nil {
assert.NoError(t, err)
return
}
// postmigration
type ExpectedOAuth2Application struct {
ID int64
ConfidentialClient bool
}
got := []ExpectedOAuth2Application{}
if err := x.Table("o_auth2_application").Select("id, confidential_client").Find(&got); !assert.NoError(t, err) {
return
}
assert.NotEmpty(t, got)
for _, e := range got {
assert.True(t, e.ConfidentialClient)
}
}

View file

@ -431,25 +431,15 @@ func DeleteTeam(t *organization.Team) error {
} }
} }
// Delete team-user. if err := db.DeleteBeans(ctx,
if _, err := sess. &organization.Team{ID: t.ID},
Where("org_id=?", t.OrgID). &organization.TeamUser{OrgID: t.OrgID, TeamID: t.ID},
Where("team_id=?", t.ID). &organization.TeamUnit{TeamID: t.ID},
Delete(new(organization.TeamUser)); err != nil { &organization.TeamInvite{TeamID: t.ID},
); err != nil {
return err return err
} }
// Delete team-unit.
if _, err := sess.
Where("team_id=?", t.ID).
Delete(new(organization.TeamUnit)); err != nil {
return err
}
// Delete team.
if _, err := sess.ID(t.ID).Delete(new(organization.Team)); err != nil {
return err
}
// Update organization number of teams. // Update organization number of teams.
if _, err := sess.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil { if _, err := sess.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil {
return err return err

View file

@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder" "xorm.io/builder"
) )
@ -45,6 +46,10 @@ func (err ErrOrgNotExist) Error() string {
return fmt.Sprintf("org does not exist [id: %d, name: %s]", err.ID, err.Name) return fmt.Sprintf("org does not exist [id: %d, name: %s]", err.ID, err.Name)
} }
func (err ErrOrgNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrLastOrgOwner represents a "LastOrgOwner" kind of error. // ErrLastOrgOwner represents a "LastOrgOwner" kind of error.
type ErrLastOrgOwner struct { type ErrLastOrgOwner struct {
UID int64 UID int64
@ -73,6 +78,10 @@ func (err ErrUserNotAllowedCreateOrg) Error() string {
return "user is not allowed to create organizations" return "user is not allowed to create organizations"
} }
func (err ErrUserNotAllowedCreateOrg) Unwrap() error {
return util.ErrPermissionDenied
}
// Organization represents an organization // Organization represents an organization
type Organization user_model.User type Organization user_model.User
@ -361,8 +370,9 @@ func DeleteOrganization(ctx context.Context, org *Organization) error {
&OrgUser{OrgID: org.ID}, &OrgUser{OrgID: org.ID},
&TeamUser{OrgID: org.ID}, &TeamUser{OrgID: org.ID},
&TeamUnit{OrgID: org.ID}, &TeamUnit{OrgID: org.ID},
&TeamInvite{OrgID: org.ID},
); err != nil { ); err != nil {
return fmt.Errorf("deleteBeans: %v", err) return fmt.Errorf("DeleteBeans: %v", err)
} }
if _, err := db.GetEngine(ctx).ID(org.ID).Delete(new(user_model.User)); err != nil { if _, err := db.GetEngine(ctx).ID(org.ID).Delete(new(user_model.User)); err != nil {

View file

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder" "xorm.io/builder"
) )
@ -43,6 +44,10 @@ func (err ErrTeamAlreadyExist) Error() string {
return fmt.Sprintf("team already exists [org_id: %d, name: %s]", err.OrgID, err.Name) return fmt.Sprintf("team already exists [org_id: %d, name: %s]", err.OrgID, err.Name)
} }
func (err ErrTeamAlreadyExist) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrTeamNotExist represents a "TeamNotExist" error // ErrTeamNotExist represents a "TeamNotExist" error
type ErrTeamNotExist struct { type ErrTeamNotExist struct {
OrgID int64 OrgID int64
@ -60,6 +65,10 @@ 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) return fmt.Sprintf("team does not exist [org_id %d, team_id %d, name: %s]", err.OrgID, err.TeamID, err.Name)
} }
func (err ErrTeamNotExist) Unwrap() error {
return util.ErrNotExist
}
// OwnerTeamName return the owner team name // OwnerTeamName return the owner team name
const OwnerTeamName = "Owners" const OwnerTeamName = "Owners"
@ -85,6 +94,7 @@ func init() {
db.RegisterModel(new(TeamUser)) db.RegisterModel(new(TeamUser))
db.RegisterModel(new(TeamRepo)) db.RegisterModel(new(TeamRepo))
db.RegisterModel(new(TeamUnit)) db.RegisterModel(new(TeamUnit))
db.RegisterModel(new(TeamInvite))
} }
// SearchTeamOptions holds the search options // SearchTeamOptions holds the search options

View file

@ -0,0 +1,162 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package organization
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder"
)
type ErrTeamInviteAlreadyExist struct {
TeamID int64
Email string
}
func IsErrTeamInviteAlreadyExist(err error) bool {
_, ok := err.(ErrTeamInviteAlreadyExist)
return ok
}
func (err ErrTeamInviteAlreadyExist) Error() string {
return fmt.Sprintf("team invite already exists [team_id: %d, email: %s]", err.TeamID, err.Email)
}
func (err ErrTeamInviteAlreadyExist) Unwrap() error {
return util.ErrAlreadyExist
}
type ErrTeamInviteNotFound struct {
Token string
}
func IsErrTeamInviteNotFound(err error) bool {
_, ok := err.(ErrTeamInviteNotFound)
return ok
}
func (err ErrTeamInviteNotFound) Error() string {
return fmt.Sprintf("team invite was not found [token: %s]", err.Token)
}
func (err ErrTeamInviteNotFound) Unwrap() error {
return util.ErrNotExist
}
// ErrUserEmailAlreadyAdded represents a "user by email already added to team" error.
type ErrUserEmailAlreadyAdded struct {
Email string
}
// IsErrUserEmailAlreadyAdded checks if an error is a ErrUserEmailAlreadyAdded.
func IsErrUserEmailAlreadyAdded(err error) bool {
_, ok := err.(ErrUserEmailAlreadyAdded)
return ok
}
func (err ErrUserEmailAlreadyAdded) Error() string {
return fmt.Sprintf("user with email already added [email: %s]", err.Email)
}
func (err ErrUserEmailAlreadyAdded) Unwrap() error {
return util.ErrAlreadyExist
}
// TeamInvite represents an invite to a team
type TeamInvite struct {
ID int64 `xorm:"pk autoincr"`
Token string `xorm:"UNIQUE(token) INDEX NOT NULL DEFAULT ''"`
InviterID int64 `xorm:"NOT NULL DEFAULT 0"`
OrgID int64 `xorm:"INDEX NOT NULL DEFAULT 0"`
TeamID int64 `xorm:"UNIQUE(team_mail) INDEX NOT NULL DEFAULT 0"`
Email string `xorm:"UNIQUE(team_mail) NOT NULL DEFAULT ''"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
func CreateTeamInvite(ctx context.Context, doer *user_model.User, team *Team, email string) (*TeamInvite, error) {
has, err := db.GetEngine(ctx).Exist(&TeamInvite{
TeamID: team.ID,
Email: email,
})
if err != nil {
return nil, err
}
if has {
return nil, ErrTeamInviteAlreadyExist{
TeamID: team.ID,
Email: email,
}
}
// check if the user is already a team member by email
exist, err := db.GetEngine(ctx).
Where(builder.Eq{
"team_user.org_id": team.OrgID,
"team_user.team_id": team.ID,
"`user`.email": email,
}).
Join("INNER", "`user`", "`user`.id = team_user.uid").
Table("team_user").
Exist()
if err != nil {
return nil, err
}
if exist {
return nil, ErrUserEmailAlreadyAdded{
Email: email,
}
}
token, err := util.CryptoRandomString(25)
if err != nil {
return nil, err
}
invite := &TeamInvite{
Token: token,
InviterID: doer.ID,
OrgID: team.OrgID,
TeamID: team.ID,
Email: email,
}
return invite, db.Insert(ctx, invite)
}
func RemoveInviteByID(ctx context.Context, inviteID, teamID int64) error {
_, err := db.DeleteByBean(ctx, &TeamInvite{
ID: inviteID,
TeamID: teamID,
})
return err
}
func GetInvitesByTeamID(ctx context.Context, teamID int64) ([]*TeamInvite, error) {
invites := make([]*TeamInvite, 0, 10)
return invites, db.GetEngine(ctx).
Where("team_id=?", teamID).
Find(&invites)
}
func GetInviteByToken(ctx context.Context, token string) (*TeamInvite, error) {
invite := &TeamInvite{}
has, err := db.GetEngine(ctx).Where("token=?", token).Get(invite)
if err != nil {
return nil, err
}
if !has {
return nil, ErrTeamInviteNotFound{Token: token}
}
return invite, nil
}

View file

@ -0,0 +1,49 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package organization_test
import (
"testing"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"github.com/stretchr/testify/assert"
)
func TestTeamInvite(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
t.Run("MailExistsInTeam", func(t *testing.T) {
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
// user 2 already added to team 2, should result in error
_, err := organization.CreateTeamInvite(db.DefaultContext, user2, team, user2.Email)
assert.Error(t, err)
})
t.Run("CreateAndRemove", func(t *testing.T) {
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
invite, err := organization.CreateTeamInvite(db.DefaultContext, user1, team, "user3@example.com")
assert.NotNil(t, invite)
assert.NoError(t, err)
// Shouldn't allow duplicate invite
_, err = organization.CreateTeamInvite(db.DefaultContext, user1, team, "user3@example.com")
assert.Error(t, err)
// should remove invite
assert.NoError(t, organization.RemoveInviteByID(db.DefaultContext, invite.ID, invite.TeamID))
// invite should not exist
_, err = organization.GetInviteByToken(db.DefaultContext, invite.Token)
assert.Error(t, err)
})
}

View file

@ -165,6 +165,7 @@ type ImageTagsSearchOptions struct {
PackageID int64 PackageID int64
Query string Query string
IsTagged bool IsTagged bool
Sort packages.VersionSort
db.Paginator db.Paginator
} }
@ -195,12 +196,26 @@ func (opts *ImageTagsSearchOptions) toConds() builder.Cond {
return cond return cond
} }
func (opts *ImageTagsSearchOptions) configureOrderBy(e db.Engine) {
switch opts.Sort {
case packages.SortVersionDesc:
e.Desc("package_version.version")
case packages.SortVersionAsc:
e.Asc("package_version.version")
case packages.SortCreatedAsc:
e.Asc("package_version.created_unix")
default:
e.Desc("package_version.created_unix")
}
}
// SearchImageTags gets a sorted list of the tags of an image // SearchImageTags gets a sorted list of the tags of an image
func SearchImageTags(ctx context.Context, opts *ImageTagsSearchOptions) ([]*packages.PackageVersion, int64, error) { func SearchImageTags(ctx context.Context, opts *ImageTagsSearchOptions) ([]*packages.PackageVersion, int64, error) {
sess := db.GetEngine(ctx). sess := db.GetEngine(ctx).
Join("INNER", "package", "package.id = package_version.package_id"). Join("INNER", "package", "package.id = package_version.package_id").
Where(opts.toConds()). Where(opts.toConds())
Desc("package_version.created_unix")
opts.configureOrderBy(sess)
if opts.Paginator != nil { if opts.Paginator != nil {
sess = db.SetSessionPagination(sess, opts) sess = db.SetSessionPagination(sess, opts)

View file

@ -163,6 +163,17 @@ type SearchValue struct {
ExactMatch bool ExactMatch bool
} }
type VersionSort = string
const (
SortNameAsc VersionSort = "name_asc"
SortNameDesc VersionSort = "name_desc"
SortVersionAsc VersionSort = "version_asc"
SortVersionDesc VersionSort = "version_desc"
SortCreatedAsc VersionSort = "created_asc"
SortCreatedDesc VersionSort = "created_desc"
)
// PackageSearchOptions are options for SearchXXX methods // PackageSearchOptions are options for SearchXXX methods
// Besides IsInternal are all fields optional and are not used if they have their default value (nil, "", 0) // Besides IsInternal are all fields optional and are not used if they have their default value (nil, "", 0)
type PackageSearchOptions struct { type PackageSearchOptions struct {
@ -176,7 +187,7 @@ type PackageSearchOptions struct {
IsInternal util.OptionalBool IsInternal util.OptionalBool
HasFileWithName string // only results are found which are associated with a file with the specific name HasFileWithName string // only results are found which are associated with a file with the specific name
HasFiles util.OptionalBool // only results are found which have associated files HasFiles util.OptionalBool // only results are found which have associated files
Sort string Sort VersionSort
db.Paginator db.Paginator
} }
@ -254,15 +265,15 @@ func (opts *PackageSearchOptions) toConds() builder.Cond {
func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) { func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
switch opts.Sort { switch opts.Sort {
case "alphabetically": case SortNameAsc:
e.Asc("package.name") e.Asc("package.name")
case "reversealphabetically": case SortNameDesc:
e.Desc("package.name") e.Desc("package.name")
case "highestversion": case SortVersionDesc:
e.Desc("package_version.version") e.Desc("package_version.version")
case "lowestversion": case SortVersionAsc:
e.Asc("package_version.version") e.Asc("package_version.version")
case "oldest": case SortCreatedAsc:
e.Asc("package_version.created_unix") e.Asc("package_version.created_unix")
default: default:
e.Desc("package_version.created_unix") e.Desc("package_version.created_unix")

View file

@ -55,6 +55,10 @@ func (err ErrProjectNotExist) Error() string {
return fmt.Sprintf("projects does not exist [id: %d]", err.ID) return fmt.Sprintf("projects does not exist [id: %d]", err.ID)
} }
func (err ErrProjectNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrProjectBoardNotExist represents a "ProjectBoardNotExist" kind of error. // ErrProjectBoardNotExist represents a "ProjectBoardNotExist" kind of error.
type ErrProjectBoardNotExist struct { type ErrProjectBoardNotExist struct {
BoardID int64 BoardID int64
@ -70,6 +74,10 @@ func (err ErrProjectBoardNotExist) Error() string {
return fmt.Sprintf("project board does not exist [id: %d]", err.BoardID) return fmt.Sprintf("project board does not exist [id: %d]", err.BoardID)
} }
func (err ErrProjectBoardNotExist) Unwrap() error {
return util.ErrNotExist
}
// Project represents a project board // Project represents a project board
type Project struct { type Project struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`

View file

@ -90,7 +90,7 @@ func DeleteScheduledAutoMerge(ctx context.Context, pullID int64) error {
if err != nil { if err != nil {
return err return err
} else if !exist { } else if !exist {
return db.ErrNotExist{ID: pullID} return db.ErrNotExist{Resource: "auto_merge", ID: pullID}
} }
_, err = db.GetEngine(ctx).ID(scheduledPRM.ID).Delete(&AutoMerge{}) _, err = db.GetEngine(ctx).ID(scheduledPRM.ID).Delete(&AutoMerge{})

View file

@ -22,6 +22,7 @@ import (
access_model "code.gitea.io/gitea/models/perm/access" access_model "code.gitea.io/gitea/models/perm/access"
project_model "code.gitea.io/gitea/models/project" project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/models/webhook"
@ -32,9 +33,13 @@ import (
"xorm.io/builder" "xorm.io/builder"
) )
// NewRepoContext creates a new repository context // ItemsPerPage maximum items per page in forks, watchers and stars of a repo
func NewRepoContext() { var ItemsPerPage = 40
// Init initialize model
func Init() error {
unit.LoadUnitConfig() unit.LoadUnitConfig()
return system_model.Init()
} }
// DeleteRepository deletes a repository for a user or organization. // DeleteRepository deletes a repository for a user or organization.
@ -118,6 +123,11 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
return err return err
} }
if _, err := db.GetEngine(ctx).In("hook_id", builder.Select("id").From("webhook").Where(builder.Eq{"webhook.repo_id": repo.ID})).
Delete(&webhook.HookTask{}); err != nil {
return err
}
if err := db.DeleteBeans(ctx, if err := db.DeleteBeans(ctx,
&access_model.Access{RepoID: repo.ID}, &access_model.Access{RepoID: repo.ID},
&activities_model.Action{RepoID: repo.ID}, &activities_model.Action{RepoID: repo.ID},
@ -125,7 +135,6 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
&issues_model.Comment{RefRepoID: repoID}, &issues_model.Comment{RefRepoID: repoID},
&git_model.CommitStatus{RepoID: repoID}, &git_model.CommitStatus{RepoID: repoID},
&git_model.DeletedBranch{RepoID: repoID}, &git_model.DeletedBranch{RepoID: repoID},
&webhook.HookTask{RepoID: repoID},
&git_model.LFSLock{RepoID: repoID}, &git_model.LFSLock{RepoID: repoID},
&repo_model.LanguageStat{RepoID: repoID}, &repo_model.LanguageStat{RepoID: repoID},
&issues_model.Milestone{RepoID: repoID}, &issues_model.Milestone{RepoID: repoID},
@ -163,7 +172,7 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
} }
// Delete issue index // Delete issue index
if err := db.DeleteResouceIndex(ctx, "issue_index", repoID); err != nil { if err := db.DeleteResourceIndex(ctx, "issue_index", repoID); err != nil {
return err return err
} }
@ -267,36 +276,36 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
// Remove repository files. // Remove repository files.
repoPath := repo.RepoPath() repoPath := repo.RepoPath()
admin_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository files", repoPath) system_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository files", repoPath)
// Remove wiki files // Remove wiki files
if repo.HasWiki() { if repo.HasWiki() {
admin_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository wiki", repo.WikiPath()) system_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository wiki", repo.WikiPath())
} }
// Remove archives // Remove archives
for _, archive := range archivePaths { for _, archive := range archivePaths {
admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.RepoArchives, "Delete repo archive file", archive) system_model.RemoveStorageWithNotice(db.DefaultContext, storage.RepoArchives, "Delete repo archive file", archive)
} }
// Remove lfs objects // Remove lfs objects
for _, lfsObj := range lfsPaths { for _, lfsObj := range lfsPaths {
admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.LFS, "Delete orphaned LFS file", lfsObj) system_model.RemoveStorageWithNotice(db.DefaultContext, storage.LFS, "Delete orphaned LFS file", lfsObj)
} }
// Remove issue attachment files. // Remove issue attachment files.
for _, attachment := range attachmentPaths { for _, attachment := range attachmentPaths {
admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", attachment) system_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", attachment)
} }
// Remove release attachment files. // Remove release attachment files.
for _, releaseAttachment := range releaseAttachments { for _, releaseAttachment := range releaseAttachments {
admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete release attachment", releaseAttachment) system_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete release attachment", releaseAttachment)
} }
// Remove attachment with no issue_id and release_id. // Remove attachment with no issue_id and release_id.
for _, newAttachment := range newAttachmentPaths { for _, newAttachment := range newAttachmentPaths {
admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", newAttachment) system_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", newAttachment)
} }
if len(repo.Avatar) > 0 { if len(repo.Avatar) > 0 {

View file

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
) )
// Attachment represent a attachment of issue/comment/release. // Attachment represent a attachment of issue/comment/release.
@ -83,6 +84,10 @@ func (err ErrAttachmentNotExist) Error() string {
return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID) return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID)
} }
func (err ErrAttachmentNotExist) Unwrap() error {
return util.ErrNotExist
}
// GetAttachmentByID returns attachment by given id // GetAttachmentByID returns attachment by given id
func GetAttachmentByID(ctx context.Context, id int64) (*Attachment, error) { func GetAttachmentByID(ctx context.Context, id int64) (*Attachment, error) {
attach := &Attachment{} attach := &Attachment{}

View file

@ -10,6 +10,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/util"
) )
// ErrRedirectNotExist represents a "RedirectNotExist" kind of error. // ErrRedirectNotExist represents a "RedirectNotExist" kind of error.
@ -28,6 +29,10 @@ func (err ErrRedirectNotExist) Error() string {
return fmt.Sprintf("repository redirect does not exist [uid: %d, name: %s]", err.OwnerID, err.RepoName) return fmt.Sprintf("repository redirect does not exist [uid: %d, name: %s]", err.OwnerID, err.RepoName)
} }
func (err ErrRedirectNotExist) Unwrap() error {
return util.ErrNotExist
}
// Redirect represents that a repo name should be redirected to another // Redirect represents that a repo name should be redirected to another
type Redirect struct { type Redirect struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`

View file

@ -37,6 +37,10 @@ func (err ErrReleaseAlreadyExist) Error() string {
return fmt.Sprintf("release tag already exist [tag_name: %s]", err.TagName) return fmt.Sprintf("release tag already exist [tag_name: %s]", err.TagName)
} }
func (err ErrReleaseAlreadyExist) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrReleaseNotExist represents a "ReleaseNotExist" kind of error. // ErrReleaseNotExist represents a "ReleaseNotExist" kind of error.
type ErrReleaseNotExist struct { type ErrReleaseNotExist struct {
ID int64 ID int64
@ -53,6 +57,10 @@ func (err ErrReleaseNotExist) Error() string {
return fmt.Sprintf("release tag does not exist [id: %d, tag_name: %s]", err.ID, err.TagName) return fmt.Sprintf("release tag does not exist [id: %d, tag_name: %s]", err.ID, err.TagName)
} }
func (err ErrReleaseNotExist) Unwrap() error {
return util.ErrNotExist
}
// Release represents a release of repository. // Release represents a release of repository.
type Release struct { type Release struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`

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