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"
remote: "git@github.com:go-gitea/gitea.git"
environment:
DRONE_COMMIT_AUTHOR_EMAIL: "teabot@gitea.io"
DRONE_COMMIT_AUTHOR: GiteaBot
GIT_PUSH_SSH_KEY:
from_secret: git_push_ssh_key
@ -670,12 +672,14 @@ steps:
pull: always
settings:
author_email: "teabot@gitea.io"
author_name: GiteaBot
author_name: "GiteaBot"
branch: main
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"
environment:
DRONE_COMMIT_AUTHOR_EMAIL: "teabot@gitea.io"
DRONE_COMMIT_AUTHOR: "GiteaBot"
GIT_PUSH_SSH_KEY:
from_secret: git_push_ssh_key

View file

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

View file

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

View file

@ -12,7 +12,6 @@ linters:
- dupl
#- gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time.
- gofmt
- misspell
- gocritic
- bidichk
- ineffassign
@ -148,9 +147,6 @@ issues:
- path: models/issue_comment_list.go
linters:
- dupl
- linters:
- misspell
text: '`Unknwon` is a misspelling of `Unknown`'
- path: models/update.go
linters:
- 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
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
* 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.
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
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)
xinyu <xinyu@nerv.org.cn> (@penlinux)
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)
GO_DIRS := cmd tests models modules routers build services tools
WEB_DIRS := web_src/js web_src/less
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)
@ -263,11 +264,24 @@ clean:
.PHONY: 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'))
@# strip whitespace after '{{' and before `}}` unless there is only whitespace before it
@$(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
vet:
@echo "Running go vet..."
@ -311,22 +325,6 @@ errcheck:
@echo "Running errcheck..."
$(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
checks: checks-frontend checks-backend
@ -334,7 +332,7 @@ checks: checks-frontend checks-backend
checks-frontend: lockfile-check svg-check
.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
lint: lint-frontend lint-backend
@ -372,7 +370,7 @@ test-backend:
.PHONY: test-frontend
test-frontend: node_modules
@NODE_OPTIONS="--experimental-vm-modules --no-warnings" npx jest --color
npx vitest
.PHONY: 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/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://cynkra.com/" target="_blank"><img src="https://images.opencollective.com/cynkra/logo/square/64/192.png"></a>
## FAQ

View file

@ -20,7 +20,7 @@ import (
)
// 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.
@ -195,7 +195,6 @@ Options:
Commands:
%[1]s gofmt ...
%[1]s misspell ...
Arguments:
{file-list} the file list
@ -206,6 +205,17 @@ Example:
`, "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) {
fileFilter := mainOptions["file-filter"]
if fileFilter == "" {
@ -228,9 +238,9 @@ func containsString(a []string, s string) bool {
return false
}
func giteaFormatGoImports(files []string, hasChangedFiles, doWriteFile bool) error {
func giteaFormatGoImports(files []string, doWriteFile bool) error {
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)
return err
}
@ -269,10 +279,8 @@ func main() {
if containsString(subArgs, "-d") {
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, passThroughCmd("go", append([]string{"run", os.Getenv("GOFUMPT_PACKAGE"), "-extra", "-lang", "1.17"}, substArgs...)))
case "misspell":
cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("MISSPELL_PACKAGE")}, substArgs...)))
cmdErrors = append(cmdErrors, giteaFormatGoImports(files, containsString(subArgs, "-w")))
cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("GOFUMPT_PACKAGE"), "-extra", "-lang", getGoVersion()}, substArgs...)))
default:
log.Fatalf("unknown cmd: %s %v", subCmd, subArgs)
}

View file

@ -7,7 +7,6 @@ package codeformat
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"sort"
@ -159,7 +158,7 @@ func formatGoImports(contentBytes []byte) ([]byte, error) {
}
// 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)
if err != nil {
return err
@ -183,10 +182,6 @@ func FormatGoImports(file string, doChangedFiles, doWriteFile bool) error {
return nil
}
if doChangedFiles {
fmt.Println(file)
}
if doWriteFile {
f, err = os.OpenFile(file, os.O_TRUNC|os.O_WRONLY, 0o644)
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",
Usage: "Skip package data",
},
cli.BoolFlag{
Name: "skip-index",
Usage: "Skip bleve index data",
},
cli.GenericFlag{
Name: "type",
Value: outputTypeEnum,
@ -327,6 +331,11 @@ func runDump(ctx *cli.Context) error {
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.LFS.Path)
excludes = append(excludes, setting.Attachment.Path)

View file

@ -2,14 +2,43 @@
DIR=/var/lib/gitea
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}"
command="/usr/local/bin/gitea"
command_args="web -c /etc/gitea/app.ini"
command_background=yes
pidfile=/run/gitea.pid
depend()
{
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"
"log"
"net/http"
"net/url"
"os"
"os/exec"
"os/user"
@ -34,6 +33,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"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/config"
@ -62,11 +62,7 @@ func runPR() {
}
setting.AppWorkPath = curDir
setting.StaticRootPath = curDir
setting.GravatarSourceURL, err = url.Parse("https://secure.gravatar.com/avatar/")
if err != nil {
log.Fatalf("url.Parse: %v\n", err)
}
setting.GravatarSource = "https://secure.gravatar.com/avatar/"
setting.AppURL = "http://localhost:8080/"
setting.HTTPPort = "8080"
setting.SSH.Domain = "localhost"
@ -117,7 +113,7 @@ func runPR() {
log.Printf("[PR] Setting up router\n")
// routers.GlobalInit()
external.RegisterRenderers()
markup.Init()
markup.Init(markup_service.ProcessorHelper())
c := routers.NormalRoutes(graceful.GetManager().HammerContext())
log.Printf("[PR] Ready for testing !\n")

View file

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

View file

@ -23,7 +23,7 @@ menu:
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

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.
## 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
**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.
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:_
@ -34,102 +34,110 @@ _Symbols used in table:_
## General Features
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
| ----------------------------------- | ---------------------------------------------------| ---- | --------- | --------- | --------- | -------------- | ------------ |
| ------------------------------------------------ | --------------------------------------------------- | ---- | --------- | --------- | --------- | --------- | ------------ |
| Open source and free | ✓ | ✓ | ✘ | ✓ | ✘ | ✘ | ✓ |
| Low resource usage (RAM/CPU) | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ |
| Low RAM/ CPU usage | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ |
| Multiple database support | ✓ | ✓ | ✘ | | | ✓ | ✓ |
| Multiple OS support | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ |
| Easy upgrade process | ✓ | ✓ | ✘ | ✓ | ✓ | ✘ | ✓ |
| Markdown support | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Orgmode support | ✓ | ✘ | ✓ | ✘ | ✘ | ✘ | ? |
| CSV support | ✓ | ✘ | ✓ | ✘ | ✘ | ✓ | ? |
| Easy upgrades | ✓ | ✓ | ✘ | ✓ | ✓ | ✘ | ✓ |
| Telemetry | **✘** | ✘ | ✓ | ✓ | ✓ | ✓ | ? |
| Third-party render tool support | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ | ? |
| Static Git-powered pages | [](https://github.com/go-gitea/gitea/issues/302) | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Integrated Git-powered wiki | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ (cloud only) | ✘ |
| WebAuthn (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ? |
| 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 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Repository Tokens with write rights | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Built-in Package/Container Registry | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| External git mirroring | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ |
| WebAuthn (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ? |
| Built-in CI/CD | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| RSS Feeds | ✓ | ✘ | ✓ | ✘ | ✘ | ✘ | ✘ |
| Built-in CI/CD | [](https://github.com/go-gitea/gitea/issues/13539) | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| 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 | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Math syntax in Markdown | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
## Code management
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
| -------------------------------------------- | ------------------------------------------------ | ---- | --------- | --------- | --------- | --------- | ------------ |
| ------------------------------------------- | --------------------------------------------------- | ---- | --------- | --------- | --------- | --------- | ------------ |
| Repository topics | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Repository code search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Global code search | ✓ | ✘ | ✓ | ✘ | ✓ | ✓ | ✓ |
| Git LFS 2.0 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Group Milestones | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Granular user roles (Code, Issues, Wiki etc) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Group Milestones | [](https://github.com/go-gitea/gitea/issues/14622) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Granular user roles (Code, Issues, Wiki, …) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Verified Committer | | ✘ | ? | ✓ | ✓ | ✓ | ✘ |
| GPG Signed Commits | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| SSH Signed Commits | ✓ | ✘ | ✘ | ✘ | ✘ | ? | ? |
| Reject unsigned commits | [](https://github.com/go-gitea/gitea/pull/9708) | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| SSH Signed Commits | ✓ | ✘ | ✓ | ✘ | ✘ | ? | ? |
| Reject unsigned commits | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Migrating repos from other services | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Repository Activity page | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Branch manager | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Create new branches | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Web code editor | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Commit graph | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Template Repositories | [](https://github.com/go-gitea/gitea/pull/8768) | ✘ | ✓ | ✘ | ✓ | ✓ | ✘ |
| Template Repositories | ✓ | ✘ | ✓ | ✘ | ✓ | ✓ | ✘ |
| Git Blame | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Visual comparison of image changes | ✓ | ✘ | ✓ | ? | ? | ? | ? |
## Issue Tracker
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
| ------------------------------- | -------------------------------------------------- | --------------------------------------------- | --------- | ----------------------------------------------------------------------- | --------- | -------------- | ------------ |
| Issue tracker | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ (cloud only) | ✘ |
| ----------------------------- | --------------------------------------------------- | ---- | --------- | --------- | --------- | --------- | ------------ |
| Issue tracker | ✓ | ✓ | ✓ | ✓ | ✓ | / | ✘ |
| Issue templates | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Labels | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Time tracking | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| 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) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Comment reactions | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Lock Discussion | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Batch issue handling | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Issue Boards (Kanban) | [](https://github.com/go-gitea/gitea/pull/8346) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Create new branches from issues | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Issue Boards (Kanban) | [/](https://github.com/go-gitea/gitea/issues/14710) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Create branch from issue | [](https://github.com/go-gitea/gitea/issues/20226) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Convert comment to new issue | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| 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 | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ |
| Create issue via email | [](https://github.com/go-gitea/gitea/issues/6226) | [](https://github.com/gogs/gogs/issues/2602) | ✘ | ✘ | ✓ | ✓ | ✘ |
| Service Desk | [](https://github.com/go-gitea/gitea/issues/6219) | ✘ | ✘ | [](https://gitlab.com/groups/gitlab-org/-/epics/3103) | ✓ | ✘ | ✘ |
| Create issue via email | [](https://github.com/go-gitea/gitea/issues/6226) | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ |
| Service Desk | [](https://github.com/go-gitea/gitea/issues/6219) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
## Pull/Merge requests
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
| ----------------------------------------------- | -------------------------------------------------- | ---- | --------- | --------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------ | ------------ |
| ----------------------------------------------- | -------------------------------------------------- | ---- | --------- | --------- | --------- | --------- | ------------ |
| Pull/Merge requests | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Squash merging | ✓ | ✘ | ✓ | [](https://docs.gitlab.com/ce/user/project/merge_requests/squash_and_merge.html) | ✓ | ✓ | ✓ |
| Rebase merging | ✓ | ✓ | ✓ | ✘ | | ✘ | ✓ |
| Squash merging | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Rebase merging | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Pull/Merge request inline comments | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Pull/Merge request approval | ✓ | ✘ | | ✓ | ✓ | ✓ | ✓ |
| Merge conflict resolution | [](https://github.com/go-gitea/gitea/issues/5158) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Restrict push and merge access to certain users | ✓ | ✘ | ✓ | | ✓ | ✓ | ✓ |
| Revert specific commits or a merge request | [](https://github.com/go-gitea/gitea/issues/5158) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Pull/Merge request approval | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Merge conflict resolution | [](https://github.com/go-gitea/gitea/issues/9014) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Restrict push and merge access to certain users | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Revert specific commits | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Pull/Merge requests templates | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Cherry-picking changes | [](https://github.com/go-gitea/gitea/issues/5158) | ✘ | ✘ | | ✓ | ✘ | ✘ |
| Download Patch | ✓ | ✘ | ✓ | ✓ | ✓ | [/](https://jira.atlassian.com/plugins/servlet/mobile#issue/BCLOUD-8323) | ✘ |
| Cherry-picking changes | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Download Patch | ✓ | ✘ | ✓ | ✓ | ✓ | / | ✘ |
## 3rd-party integrations
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
| ---------------------------------------------- | ------------------------------------------------ | ---- | --------- | --------- | --------- | --------- | ------------ |
| Webhook support | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Custom Git Hooks | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Webhooks | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Git Hooks | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| AD / LDAP integration | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Multiple LDAP / AD server support | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ |
| LDAP user synchronization | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| SAML 2.0 service provider | [](https://github.com/go-gitea/gitea/issues/5512) | [](https://github.com/gogs/gogs/issues/1221) | ✓ | ✓ | ✓ | ✓ | ✘ |
| OpenId Connect support | ✓ | ✘ | ✓ | ✓ | ✓ | ? | ✘ |
| SAML 2.0 service provider | [](https://github.com/go-gitea/gitea/issues/5512) | | ✓ | ✓ | ✓ | ✓ | ✘ |
| OpenID Connect support | ✓ | ✘ | ✓ | ✓ | ✓ | ? | ✘ |
| 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) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Mattermost/Slack integration | ✓ | ✓ | | ✓ | ✓ | | ✓ |
| Discord integration | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Microsoft Teams integration | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| External CI/CD status display | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Integration with the most common services | ✓ | / | | ✓ | ✓ | | ✓ |
| Incorporate external CI/CD | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |

View file

@ -71,7 +71,7 @@ choco install gitea
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
```

View file

@ -14,7 +14,7 @@ menu:
# 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**

View file

@ -13,6 +13,12 @@ menu:
identifier: "reverse-proxies"
---
# 反向代理
**目录**
{{< toc >}}
## 使用 Nginx 作为反向代理服务
如果您想使用 Nginx 作为 Gitea 的反向代理服务,您可以参照以下 `nginx.conf` 配置中 `server``http` 部分:
@ -24,6 +30,10 @@ server {
location / {
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/ {
# 注意: 反向代理后端 URL 的最后需要有一个路径符号
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/sdk/gitea v0.15.1
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/captcha v0.0.0-20211013065431-70641c1a35d5
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=
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg=
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb h1:Yy0Bxzc8R2wxiwXoG/rECGplJUSpXqCsog9PuJFgiHs=
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 h1:MMSPgnVULVwV9kEBgvyEUhC9v/uviZ55hPJEMjpbNR4=
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.2.0 h1:E0npuTfDW6CT1yD8NMDVc1SK6IeRjfmRL2zlEsCEd7w=
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 {
return nil, db.ErrNotExist{ID: notificationID}
return nil, db.ErrNotExist{Resource: "notification", ID: notificationID}
}
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)
}
func (err ErrTaskDoesNotExist) Unwrap() error {
return util.ErrNotExist
}
// GetMigratingTask returns the migrating task by repo's id
func GetMigratingTask(repoID int64) (*Task, error) {
task := Task{

View file

@ -4,7 +4,11 @@
package asymkey
import "fmt"
import (
"fmt"
"code.gitea.io/gitea/modules/util"
)
// ErrKeyUnableVerify represents a "KeyUnableVerify" kind of error.
type ErrKeyUnableVerify struct {
@ -36,6 +40,10 @@ func (err ErrKeyNotExist) Error() string {
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.
type ErrKeyAlreadyExist struct {
OwnerID int64
@ -54,6 +62,10 @@ func (err ErrKeyAlreadyExist) Error() string {
err.OwnerID, err.Fingerprint, err.Content)
}
func (err ErrKeyAlreadyExist) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrKeyNameAlreadyUsed represents a "KeyNameAlreadyUsed" kind of error.
type ErrKeyNameAlreadyUsed struct {
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)
}
func (err ErrKeyNameAlreadyUsed) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrGPGNoEmailFound represents a "ErrGPGNoEmailFound" kind of error.
type ErrGPGNoEmailFound struct {
FailedEmails []string
@ -132,6 +148,10 @@ func (err ErrGPGKeyNotExist) Error() string {
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.
type ErrGPGKeyImportNotExist struct {
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)
}
func (err ErrGPGKeyImportNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrGPGKeyIDAlreadyUsed represents a "GPGKeyIDAlreadyUsed" kind of error.
type ErrGPGKeyIDAlreadyUsed struct {
KeyID string
@ -162,6 +186,10 @@ func (err ErrGPGKeyIDAlreadyUsed) Error() string {
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.
type ErrGPGKeyAccessDenied struct {
UserID int64
@ -180,6 +208,10 @@ func (err ErrGPGKeyAccessDenied) Error() string {
err.UserID, err.KeyID)
}
func (err ErrGPGKeyAccessDenied) Unwrap() error {
return util.ErrPermissionDenied
}
// ErrKeyAccessDenied represents a "KeyAccessDenied" kind of error.
type ErrKeyAccessDenied struct {
UserID int64
@ -198,6 +230,10 @@ func (err ErrKeyAccessDenied) Error() string {
err.UserID, err.KeyID, err.Note)
}
func (err ErrKeyAccessDenied) Unwrap() error {
return util.ErrPermissionDenied
}
// ErrDeployKeyNotExist represents a "DeployKeyNotExist" kind of error.
type ErrDeployKeyNotExist struct {
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)
}
func (err ErrDeployKeyNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrDeployKeyAlreadyExist represents a "DeployKeyAlreadyExist" kind of error.
type ErrDeployKeyAlreadyExist struct {
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)
}
func (err ErrDeployKeyAlreadyExist) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrDeployKeyNameAlreadyUsed represents a "DeployKeyNameAlreadyUsed" kind of error.
type ErrDeployKeyNameAlreadyUsed struct {
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)
}
func (err ErrDeployKeyNameAlreadyUsed) Unwrap() error {
return util.ErrNotExist
}
// ErrSSHInvalidTokenSignature represents a "ErrSSHInvalidTokenSignature" kind of error.
type ErrSSHInvalidTokenSignature struct {
Wrapped error
@ -262,3 +310,7 @@ func IsErrSSHInvalidTokenSignature(err error) bool {
func (err ErrSSHInvalidTokenSignature) Error() string {
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
ClientID string `xorm:"unique"`
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"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
@ -57,6 +62,7 @@ func (app *OAuth2Application) PrimaryRedirectURI() string {
// ContainsRedirectURI checks if redirectURI is allowed for app
func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool {
if !app.ConfidentialClient {
uri, err := url.Parse(redirectURI)
// 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() != "" {
@ -69,6 +75,7 @@ func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool {
}
}
}
}
return util.IsStringInSlice(redirectURI, app.RedirectURIs, true)
}
@ -163,6 +170,7 @@ func GetOAuth2ApplicationsByUserID(ctx context.Context, userID int64) (apps []*O
type CreateOAuth2ApplicationOptions struct {
Name string
UserID int64
ConfidentialClient bool
RedirectURIs []string
}
@ -174,6 +182,7 @@ func CreateOAuth2Application(ctx context.Context, opts CreateOAuth2ApplicationOp
Name: opts.Name,
ClientID: clientID,
RedirectURIs: opts.RedirectURIs,
ConfidentialClient: opts.ConfidentialClient,
}
if err := db.Insert(ctx, app); err != nil {
return nil, err
@ -186,6 +195,7 @@ type UpdateOAuth2ApplicationOptions struct {
ID int64
Name string
UserID int64
ConfidentialClient bool
RedirectURIs []string
}
@ -207,6 +217,7 @@ func UpdateOAuth2Application(opts UpdateOAuth2ApplicationOptions) (*OAuth2Applic
app.Name = opts.Name
app.RedirectURIs = opts.RedirectURIs
app.ConfidentialClient = opts.ConfidentialClient
if err = updateOAuth2Application(ctx, app); err != nil {
return nil, err
@ -217,7 +228,7 @@ func UpdateOAuth2Application(opts UpdateOAuth2ApplicationOptions) (*OAuth2Applic
}
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 nil
@ -486,7 +497,7 @@ type ErrOAuthClientIDInvalid struct {
ClientID string
}
// IsErrOauthClientIDInvalid checks if an error is a ErrReviewNotExist.
// IsErrOauthClientIDInvalid checks if an error is a ErrOAuthClientIDInvalid.
func IsErrOauthClientIDInvalid(err error) bool {
_, ok := err.(ErrOAuthClientIDInvalid)
return ok
@ -497,6 +508,11 @@ func (err ErrOAuthClientIDInvalid) Error() string {
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
type ErrOAuthApplicationNotFound struct {
ID int64
@ -513,6 +529,11 @@ func (err ErrOAuthApplicationNotFound) Error() string {
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
func GetActiveOAuth2ProviderSources() ([]*Source, error) {
sources := make([]*Source, 0, 1)

View file

@ -46,6 +46,7 @@ func TestOAuth2Application_ContainsRedirectURI(t *testing.T) {
func TestOAuth2Application_ContainsRedirectURI_WithPort(t *testing.T) {
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/"},
ConfidentialClient: false,
}
// http loopback uris should ignore port

View file

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"xorm.io/xorm"
"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)
}
// Unwrap unwraps this as a ErrNotExist err
func (err ErrSourceNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrSourceAlreadyExist represents a "SourceAlreadyExist" kind of error.
type ErrSourceAlreadyExist struct {
Name string
@ -381,6 +387,11 @@ func (err ErrSourceAlreadyExist) Error() string {
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.
type ErrSourceInUse struct {
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)
}
func (err ErrAccessTokenNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrAccessTokenEmpty represents a "AccessTokenEmpty" kind of error.
type ErrAccessTokenEmpty struct{}
@ -48,6 +52,10 @@ func (err ErrAccessTokenEmpty) Error() string {
return "access token is empty"
}
func (err ErrAccessTokenEmpty) Unwrap() error {
return util.ErrInvalidArgument
}
var successfulAccessTokenCache *lru.Cache
// 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)
}
// Unwrap unwraps this as a ErrNotExist err
func (err ErrTwoFactorNotEnrolled) Unwrap() error {
return util.ErrNotExist
}
// TwoFactor represents a two-factor authentication token.
type TwoFactor struct {
ID int64 `xorm:"pk autoincr"`

View file

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"github.com/duo-labs/webauthn/webauthn"
"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)
}
// Unwrap unwraps this as a ErrNotExist err
func (err ErrWebAuthnCredentialNotExist) Unwrap() error {
return util.ErrNotExist
}
// IsErrWebAuthnCredentialNotExist checks if an error is a ErrWebAuthnCredentialNotExist.
func IsErrWebAuthnCredentialNotExist(err error) bool {
_, ok := err.(ErrWebAuthnCredentialNotExist)

View file

@ -13,6 +13,7 @@ import (
"sync"
"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/cache"
"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.
// This function should only be called if a federated avatar service is enabled.
func LibravatarURL(email string) (*url.URL, error) {
urlStr, err := setting.LibravatarService.FromEmail(email)
urlStr, err := system_model.LibravatarService.FromEmail(email)
if err != nil {
log.Error("LibravatarService.FromEmail(email=%s): error %v", email, err)
return nil, err
@ -149,8 +150,10 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
return DefaultAvatarLink()
}
enableFederatedAvatar, _ := system_model.GetSetting(system_model.KeyPictureEnableFederatedAvatar)
var err error
if setting.EnableFederatedAvatar && setting.LibravatarService != nil {
if enableFederatedAvatar != nil && enableFederatedAvatar.GetValueBool() && system_model.LibravatarService != nil {
emailHash := saveEmailHash(email)
if final {
// 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)
}
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.
avatarURLCopy := *setting.GravatarSourceURL
avatarURLCopy := *system_model.GravatarSourceURL
avatarURLCopy.Path = path.Join(avatarURLCopy.Path, HashEmail(email))
return generateRecognizedAvatarURL(avatarURLCopy, size)
}
return DefaultAvatarLink()
}

View file

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

View file

@ -225,7 +225,7 @@ func NamesToBean(names ...string) ([]interface{}, error) {
for _, name := range names {
bean, ok := beanMap[strings.ToLower(strings.TrimSpace(name))]
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] {
beans = append(beans, bean)

View file

@ -6,6 +6,8 @@ package db
import (
"fmt"
"code.gitea.io/gitea/modules/util"
)
// ErrCancelled represents an error due to context cancellation
@ -45,6 +47,7 @@ func (err ErrSSHDisabled) Error() string {
// ErrNotExist represents a non-exist error.
type ErrNotExist struct {
Resource string
ID int64
}
@ -55,5 +58,18 @@ func IsErrNotExist(err error) bool {
}
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"
"errors"
"fmt"
"code.gitea.io/gitea/modules/setting"
)
// 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 {
GroupID int64 `xorm:"pk"`
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 (
// ErrResouceOutdated represents an error when request resource outdated
ErrResouceOutdated = errors.New("resource outdated")
@ -59,53 +29,85 @@ const (
MaxDupIndexAttempts = 3
)
// GetNextResourceIndex retried 3 times to generate a resource index
func GetNextResourceIndex(tableName string, groupID int64) (int64, error) {
for i := 0; i < MaxDupIndexAttempts; i++ {
idx, err := getNextResourceIndex(tableName, groupID)
if err == ErrResouceOutdated {
continue
}
if err != nil {
return 0, err
}
return idx, nil
}
return 0, ErrGetResourceIndexFailed
}
// SyncMaxResourceIndex sync the max index with the resource
func SyncMaxResourceIndex(ctx context.Context, tableName string, groupID, maxIndex int64) (err error) {
e := GetEngine(ctx)
// DeleteResouceIndex delete resource index
func DeleteResouceIndex(ctx context.Context, tableName string, groupID int64) error {
_, err := Exec(ctx, fmt.Sprintf("DELETE FROM %s WHERE group_id=?", tableName), groupID)
// try to update the max_index and acquire the write-lock for the record
res, err := e.Exec(fmt.Sprintf("UPDATE %s SET max_index=? WHERE group_id=? AND max_index<?", tableName), maxIndex, groupID, maxIndex)
if err != nil {
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
func getNextResourceIndex(tableName string, groupID int64) (int64, error) {
ctx, commiter, err := TxContext()
// GetNextResourceIndex generates a resource index, it must run in the same transaction where the resource is created
func GetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
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 {
return 0, err
}
defer commiter.Close()
var preIdx int64
if _, err := GetEngine(ctx).SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id = ?", tableName), groupID).Get(&preIdx); err != nil {
affected, err := res.RowsAffected()
if err != nil {
return 0, err
}
if err := UpsertResourceIndex(ctx, tableName, groupID); err != nil {
if affected == 0 {
// 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
}
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
has, err := GetEngine(ctx).SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id = ? AND max_index=?", tableName), groupID, preIdx+1).Get(&curIdx)
// now, the new index is in database (protected by the transaction and write-lock)
var newIdx int64
has, err := e.SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id=?", tableName), groupID).Get(&newIdx)
if err != nil {
return 0, err
}
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 0, err
}
return curIdx, nil
return newIdx, 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
import (
"errors"
"fmt"
"regexp"
"strings"
"unicode/utf8"
"code.gitea.io/gitea/modules/util"
)
var (
// 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 = regexp.MustCompile(`[^\w-\.]`)
@ -35,6 +36,11 @@ func (err ErrNameReserved) Error() string {
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.
type ErrNamePatternNotAllowed struct {
Pattern string
@ -50,6 +56,11 @@ func (err ErrNamePatternNotAllowed) Error() string {
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.
type ErrNameCharsNotAllowed struct {
Name string
@ -62,7 +73,12 @@ func IsErrNameCharsNotAllowed(err error) bool {
}
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

View file

@ -10,6 +10,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/util"
)
// ErrUserOwnRepos represents a "UserOwnRepos" kind of error.
@ -63,8 +64,8 @@ type ErrNoPendingRepoTransfer struct {
RepoID int64
}
func (e ErrNoPendingRepoTransfer) Error() string {
return fmt.Sprintf("repository doesn't have a pending transfer [repo_id: %d]", e.RepoID)
func (err ErrNoPendingRepoTransfer) Error() string {
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
@ -74,6 +75,10 @@ func IsErrNoPendingTransfer(err error) bool {
return ok
}
func (err ErrNoPendingRepoTransfer) Unwrap() error {
return util.ErrNotExist
}
// ErrRepoTransferInProgress represents the state of a repository that has an
// ongoing transfer
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)
}
func (err ErrRepoTransferInProgress) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrInvalidCloneAddr represents a "InvalidCloneAddr" kind of error.
type ErrInvalidCloneAddr struct {
Host string
@ -124,6 +133,10 @@ func (err *ErrInvalidCloneAddr) Error() string {
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.
type ErrUpdateTaskNotExist struct {
UUID string
@ -139,6 +152,10 @@ func (err ErrUpdateTaskNotExist) Error() string {
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.
type ErrInvalidTagName struct {
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)
}
func (err ErrInvalidTagName) Unwrap() error {
return util.ErrInvalidArgument
}
// ErrProtectedTagName represents a "ProtectedTagName" kind of error.
type ErrProtectedTagName struct {
TagName string
@ -169,6 +190,10 @@ func (err ErrProtectedTagName) Error() string {
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.
type ErrRepoFileAlreadyExists struct {
Path string
@ -184,6 +209,10 @@ func (err ErrRepoFileAlreadyExists) Error() string {
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.
type ErrRepoFileDoesNotExist struct {
Path string
@ -200,6 +229,10 @@ func (err ErrRepoFileDoesNotExist) Error() string {
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.
type ErrFilenameInvalid struct {
Path string
@ -215,6 +248,10 @@ func (err ErrFilenameInvalid) Error() string {
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.
type ErrUserCannotCommit struct {
UserName string
@ -230,6 +267,10 @@ func (err ErrUserCannotCommit) Error() string {
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.
type ErrFilePathInvalid struct {
Message string
@ -251,6 +292,10 @@ func (err ErrFilePathInvalid) Error() string {
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.
type ErrFilePathProtected struct {
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)
}
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)
}
func (err ErrBranchDoesNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrBranchAlreadyExists represents an error that branch with such name already exists.
type ErrBranchAlreadyExists struct {
BranchName string
@ -307,6 +360,10 @@ func (err ErrBranchAlreadyExists) Error() string {
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.
type ErrBranchNameConflict struct {
BranchName string
@ -322,6 +379,10 @@ func (err ErrBranchNameConflict) Error() string {
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.
type ErrBranchesEqual struct {
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)
}
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.
type ErrDisallowedToMerge struct {
Reason string
@ -353,6 +418,10 @@ func (err ErrDisallowedToMerge) Error() string {
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.
type ErrTagAlreadyExists struct {
TagName string
@ -368,6 +437,10 @@ func (err ErrTagAlreadyExists) Error() string {
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.
type ErrSHADoesNotMatch struct {
Path string
@ -400,6 +473,10 @@ func (err ErrSHANotFound) Error() string {
return fmt.Sprintf("sha not found [%s]", err.SHA)
}
func (err ErrSHANotFound) Unwrap() error {
return util.ErrNotExist
}
// ErrCommitIDDoesNotMatch represents a "CommitIDDoesNotMatch" kind of error.
type ErrCommitIDDoesNotMatch struct {
GivenCommitID string
@ -446,6 +523,10 @@ func (err ErrInvalidMergeStyle) Error() string {
err.ID, err.Style)
}
func (err ErrInvalidMergeStyle) Unwrap() error {
return util.ErrInvalidArgument
}
// ErrMergeConflicts represents an error if merging fails with a conflict
type ErrMergeConflicts struct {
Style repo_model.MergeStyle

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,3 +7,14 @@
redirect_uris: '["a", "https://example.com/xyzzy"]'
created_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"
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"
created_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
num_repos: 3
num_members: 1
includes_all_repositories: false
can_create_org_repo: true
-
@ -16,6 +17,7 @@
authorize: 2 # write
num_repos: 1
num_members: 2
includes_all_repositories: false
can_create_org_repo: false
-
@ -26,6 +28,7 @@
authorize: 4 # owner
num_repos: 0
num_members: 1
includes_all_repositories: false
can_create_org_repo: true
-
@ -36,6 +39,7 @@
authorize: 4 # owner
num_repos: 0
num_members: 1
includes_all_repositories: false
can_create_org_repo: true
-
@ -46,6 +50,7 @@
authorize: 4 # owner
num_repos: 2
num_members: 2
includes_all_repositories: false
can_create_org_repo: true
-
@ -56,6 +61,7 @@
authorize: 4 # owner
num_repos: 2
num_members: 2
includes_all_repositories: false
can_create_org_repo: true
-
@ -66,6 +72,7 @@
authorize: 2 # write
num_repos: 1
num_members: 1
includes_all_repositories: false
can_create_org_repo: false
-
@ -76,6 +83,7 @@
authorize: 2 # write
num_repos: 1
num_members: 1
includes_all_repositories: false
can_create_org_repo: false
-
@ -86,6 +94,7 @@
authorize: 1 # read
num_repos: 1
num_members: 2
includes_all_repositories: false
can_create_org_repo: false
-
@ -93,9 +102,10 @@
org_id: 25
lower_name: notowners
name: NotOwners
authorize: 1 # owner
authorize: 1 # read
num_repos: 0
num_members: 1
includes_all_repositories: false
can_create_org_repo: false
-
@ -106,6 +116,7 @@
authorize: 1 # read
num_repos: 0
num_members: 0
includes_all_repositories: false
can_create_org_repo: false
-
@ -116,6 +127,7 @@
authorize: 3 # admin
num_repos: 0
num_members: 1
includes_all_repositories: false
can_create_org_repo: true
-
@ -126,4 +138,5 @@
authorize: 3 # admin
num_repos: 0
num_members: 1
includes_all_repositories: false
can_create_org_repo: false

View file

@ -8,18 +8,22 @@
name: database
repo_count: 1
- id: 3
-
id: 3
name: SQL
repo_count: 1
- id: 4
-
id: 4
name: graphql
repo_count: 1
- id: 5
-
id: 5
name: topicname1
repo_count: 1
- id: 6
-
id: 6
name: topicname2
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
attestation_type: none
sign_count: 0

View file

@ -6,6 +6,8 @@ package foreignreference
import (
"fmt"
"code.gitea.io/gitea/modules/util"
)
// 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)
}
func (err ErrLocalIndexNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrForeignIndexNotExist represents a "ForeignIndexNotExist" kind of error.
type ErrForeignIndexNotExist struct {
RepoID int64
@ -41,3 +47,7 @@ func IsErrForeignIndexNotExist(err error) bool {
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)
}
func (err ErrForeignIndexNotExist) Unwrap() error {
return util.ErrNotExist
}

View file

@ -6,7 +6,6 @@ package git
import (
"context"
"errors"
"fmt"
"code.gitea.io/gitea/models/db"
@ -17,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"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)
}
func (err ErrLFSLockNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrLFSUnauthorizedAction represents a "LFSUnauthorizedAction" kind of error.
type ErrLFSUnauthorizedAction struct {
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)
}
func (err ErrLFSUnauthorizedAction) Unwrap() error {
return util.ErrPermissionDenied
}
// ErrLFSLockAlreadyExist represents a "LFSLockAlreadyExist" kind of error.
type ErrLFSLockAlreadyExist struct {
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)
}
func (err ErrLFSLockAlreadyExist) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrLFSFileLocked represents a "LFSFileLocked" kind of error.
type ErrLFSFileLocked struct {
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)
}
func (err ErrLFSFileLocked) Unwrap() error {
return util.ErrPermissionDenied
}
// LFSMetaObject stores metadata for LFS tracked files.
type LFSMetaObject struct {
ID int64 `xorm:"pk autoincr"`
@ -114,7 +130,7 @@ type LFSTokenResponse struct {
// ErrLFSObjectNotExist is returned from lfs models functions in order
// 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
// if it is not already present.

View file

@ -28,6 +28,7 @@ import (
"code.gitea.io/gitea/modules/references"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder"
"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)
}
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.
type CommentType int

View file

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"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)
}
func (err ErrIssueContentHistoryNotExist) Unwrap() error {
return util.ErrNotExist
}
// GetIssueContentHistoryByID get issue content history
func GetIssueContentHistoryByID(dbCtx context.Context, id int64) (*ContentHistory, error) {
h := &ContentHistory{}

View file

@ -11,6 +11,7 @@ import (
"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"
)
// 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)
}
func (err ErrDependencyExists) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrDependencyNotExists represents a "DependencyAlreadyExists" kind of error.
type ErrDependencyNotExists struct {
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)
}
func (err ErrDependencyNotExists) Unwrap() error {
return util.ErrNotExist
}
// ErrCircularDependency represents a "DependencyCircular" kind of error.
type ErrCircularDependency struct {
IssueID int64
@ -91,6 +100,10 @@ func (err ErrUnknownDependencyType) Error() string {
return fmt.Sprintf("unknown dependency type [type: %d]", err.Type)
}
func (err ErrUnknownDependencyType) Unwrap() error {
return util.ErrInvalidArgument
}
// IssueDependency represents an issue dependency
type IssueDependency struct {
ID int64 `xorm:"pk autoincr"`

View file

@ -13,7 +13,6 @@ import (
"strconv"
"strings"
admin_model "code.gitea.io/gitea/models/admin"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/foreignreference"
"code.gitea.io/gitea/models/organization"
@ -21,6 +20,7 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"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)
}
func (err ErrIssueNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrIssueIsClosed represents a "IssueIsClosed" kind of error.
type ErrIssueIsClosed struct {
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.
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()
if err != nil {
return err
}
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{
Repo: repo,
Issue: issue,
@ -1492,7 +1496,8 @@ func applySubscribedCondition(sess *xorm.Session, subscriberID int64) *xorm.Sess
builder.In("issue.repo_id", builder.
Select("id").
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.
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
}

View file

@ -15,16 +15,12 @@ func RecalculateIssueIndexForRepo(repoID int64) error {
}
defer committer.Close()
if err := db.UpsertResourceIndex(ctx, "issue_index", repoID); err != nil {
return err
}
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
}
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
}

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})
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)
i := &issues_model.Issue{
RepoID: r.ID,
@ -143,9 +147,6 @@ func testCreateIssue(t *testing.T, repo, doer int64, title, content string, ispu
Index: idx,
}
ctx, committer, err := db.TxContext()
assert.NoError(t, err)
defer committer.Close()
err = issues_model.NewIssueWithIndex(ctx, d, issues_model.NewIssueOptions{
Repo: r,
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)
}
func (err ErrRepoLabelNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrOrgLabelNotExist represents a "OrgLabelNotExist" kind of error.
type ErrOrgLabelNotExist struct {
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)
}
func (err ErrOrgLabelNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrLabelNotExist represents a "LabelNotExist" kind of error.
type ErrLabelNotExist struct {
LabelID int64
@ -69,6 +77,10 @@ func (err ErrLabelNotExist) Error() string {
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
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"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"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)
}
func (err ErrMilestoneNotExist) Unwrap() error {
return util.ErrNotExist
}
// Milestone represents a milestone of repository.
type Milestone struct {
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)
}
func (err ErrPullRequestNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrPullRequestAlreadyExists represents a "PullRequestAlreadyExists"-error
type ErrPullRequestAlreadyExists struct {
ID int64
@ -68,6 +72,10 @@ func (err ErrPullRequestAlreadyExists) Error() string {
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
type ErrPullRequestHeadRepoMissing struct {
ID int64
@ -490,13 +498,6 @@ func (pr *PullRequest) SetMerged(ctx context.Context) (bool, error) {
// 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) {
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()
if err != nil {
return err
@ -504,6 +505,13 @@ func NewPullRequest(outerCtx context.Context, repo *repo_model.Repository, issue
defer committer.Close()
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{
Repo: repo,
Issue: issue,

View file

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder"
)
@ -34,6 +35,10 @@ func (err ErrForbiddenIssueReaction) Error() string {
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
type ErrReactionAlreadyExist struct {
Reaction string
@ -49,6 +54,10 @@ func (err ErrReactionAlreadyExist) Error() string {
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.
type Reaction struct {
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)
}
func (err ErrReviewNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrNotValidReviewRequest an not allowed review request modify
type ErrNotValidReviewRequest struct {
Reason string
@ -59,6 +63,10 @@ func (err ErrNotValidReviewRequest) Error() string {
err.RepoID)
}
func (err ErrNotValidReviewRequest) Unwrap() error {
return util.ErrInvalidArgument
}
// ReviewType defines the sort of feedback a review gives
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)
}
func (err ErrIssueStopwatchNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrIssueStopwatchAlreadyExist represents an error that stopwatch is already exist
type ErrIssueStopwatchAlreadyExist struct {
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)
}
func (err ErrIssueStopwatchAlreadyExist) Unwrap() error {
return util.ErrAlreadyExist
}
// Stopwatch represents a stopwatch for time tracking.
type Stopwatch struct {
ID int64 `xorm:"pk autoincr"`

View file

@ -236,7 +236,7 @@ func DeleteIssueUserTimes(issue *Issue, user *user_model.User) error {
return err
}
if removedTime == 0 {
return db.ErrNotExist{}
return db.ErrNotExist{Resource: "tracked_time"}
}
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 {
if t.Deleted {
return db.ErrNotExist{ID: t.ID}
return db.ErrNotExist{Resource: "tracked_time", ID: t.ID}
}
t.Deleted = true
_, err := db.GetEngine(ctx).ID(t.ID).Cols("deleted").Update(t)
@ -310,7 +310,7 @@ func GetTrackedTimeByID(id int64) (*TrackedTime, error) {
if err != nil {
return nil, err
} else if !has {
return nil, db.ErrNotExist{ID: id}
return nil, db.ErrNotExist{Resource: "tracked_time", ID: id}
}
return time, nil
}

View file

@ -14,6 +14,8 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
_ "code.gitea.io/gitea/models/system"
"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),
// v226 -> v227
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

View file

@ -83,17 +83,17 @@ func fixMergeBase(x *xorm.Engine) error {
if !pr.HasMerged {
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 {
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 {
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
}
}
} 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 {
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
@ -103,10 +103,11 @@ func fixMergeBase(x *xorm.Engine) error {
continue
}
args := append([]string{"merge-base", "--"}, parents[1:]...)
args = append(args, gitRefName)
refs := append([]string{}, parents[1:]...)
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 {
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

View file

@ -80,7 +80,7 @@ func refixMergeBase(x *xorm.Engine) error {
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 {
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
@ -91,10 +91,11 @@ func refixMergeBase(x *xorm.Engine) error {
}
// we should recalculate
args := append([]string{"merge-base", "--"}, parents[1:]...)
args = append(args, gitRefName)
refs := append([]string{}, parents[1:]...)
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 {
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

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 := sess.
Where("org_id=?", t.OrgID).
Where("team_id=?", t.ID).
Delete(new(organization.TeamUser)); err != nil {
if err := db.DeleteBeans(ctx,
&organization.Team{ID: t.ID},
&organization.TeamUser{OrgID: t.OrgID, TeamID: t.ID},
&organization.TeamUnit{TeamID: t.ID},
&organization.TeamInvite{TeamID: t.ID},
); err != nil {
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.
if _, err := sess.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil {
return err

View file

@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"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)
}
func (err ErrOrgNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrLastOrgOwner represents a "LastOrgOwner" kind of error.
type ErrLastOrgOwner struct {
UID int64
@ -73,6 +78,10 @@ func (err ErrUserNotAllowedCreateOrg) Error() string {
return "user is not allowed to create organizations"
}
func (err ErrUserNotAllowedCreateOrg) Unwrap() error {
return util.ErrPermissionDenied
}
// Organization represents an organization
type Organization user_model.User
@ -361,8 +370,9 @@ func DeleteOrganization(ctx context.Context, org *Organization) error {
&OrgUser{OrgID: org.ID},
&TeamUser{OrgID: org.ID},
&TeamUnit{OrgID: org.ID},
&TeamInvite{OrgID: org.ID},
); 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 {

View file

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
"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)
}
func (err ErrTeamAlreadyExist) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrTeamNotExist represents a "TeamNotExist" error
type ErrTeamNotExist struct {
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)
}
func (err ErrTeamNotExist) Unwrap() error {
return util.ErrNotExist
}
// OwnerTeamName return the owner team name
const OwnerTeamName = "Owners"
@ -85,6 +94,7 @@ func init() {
db.RegisterModel(new(TeamUser))
db.RegisterModel(new(TeamRepo))
db.RegisterModel(new(TeamUnit))
db.RegisterModel(new(TeamInvite))
}
// 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
Query string
IsTagged bool
Sort packages.VersionSort
db.Paginator
}
@ -195,12 +196,26 @@ func (opts *ImageTagsSearchOptions) toConds() builder.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
func SearchImageTags(ctx context.Context, opts *ImageTagsSearchOptions) ([]*packages.PackageVersion, int64, error) {
sess := db.GetEngine(ctx).
Join("INNER", "package", "package.id = package_version.package_id").
Where(opts.toConds()).
Desc("package_version.created_unix")
Where(opts.toConds())
opts.configureOrderBy(sess)
if opts.Paginator != nil {
sess = db.SetSessionPagination(sess, opts)

View file

@ -163,6 +163,17 @@ type SearchValue struct {
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
// Besides IsInternal are all fields optional and are not used if they have their default value (nil, "", 0)
type PackageSearchOptions struct {
@ -176,7 +187,7 @@ type PackageSearchOptions struct {
IsInternal util.OptionalBool
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
Sort string
Sort VersionSort
db.Paginator
}
@ -254,15 +265,15 @@ func (opts *PackageSearchOptions) toConds() builder.Cond {
func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
switch opts.Sort {
case "alphabetically":
case SortNameAsc:
e.Asc("package.name")
case "reversealphabetically":
case SortNameDesc:
e.Desc("package.name")
case "highestversion":
case SortVersionDesc:
e.Desc("package_version.version")
case "lowestversion":
case SortVersionAsc:
e.Asc("package_version.version")
case "oldest":
case SortCreatedAsc:
e.Asc("package_version.created_unix")
default:
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)
}
func (err ErrProjectNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrProjectBoardNotExist represents a "ProjectBoardNotExist" kind of error.
type ErrProjectBoardNotExist struct {
BoardID int64
@ -70,6 +74,10 @@ func (err ErrProjectBoardNotExist) Error() string {
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
type Project struct {
ID int64 `xorm:"pk autoincr"`

View file

@ -90,7 +90,7 @@ func DeleteScheduledAutoMerge(ctx context.Context, pullID int64) error {
if err != nil {
return err
} 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{})

View file

@ -22,6 +22,7 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
@ -32,9 +33,13 @@ import (
"xorm.io/builder"
)
// NewRepoContext creates a new repository context
func NewRepoContext() {
// ItemsPerPage maximum items per page in forks, watchers and stars of a repo
var ItemsPerPage = 40
// Init initialize model
func Init() error {
unit.LoadUnitConfig()
return system_model.Init()
}
// 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
}
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,
&access_model.Access{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},
&git_model.CommitStatus{RepoID: repoID},
&git_model.DeletedBranch{RepoID: repoID},
&webhook.HookTask{RepoID: repoID},
&git_model.LFSLock{RepoID: repoID},
&repo_model.LanguageStat{RepoID: repoID},
&issues_model.Milestone{RepoID: repoID},
@ -163,7 +172,7 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
}
// 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
}
@ -267,36 +276,36 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
// Remove repository files.
repoPath := repo.RepoPath()
admin_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository files", repoPath)
system_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository files", repoPath)
// Remove wiki files
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
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
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.
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.
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.
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 {

View file

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
)
// 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)
}
func (err ErrAttachmentNotExist) Unwrap() error {
return util.ErrNotExist
}
// GetAttachmentByID returns attachment by given id
func GetAttachmentByID(ctx context.Context, id int64) (*Attachment, error) {
attach := &Attachment{}

View file

@ -10,6 +10,7 @@ import (
"strings"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/util"
)
// 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)
}
func (err ErrRedirectNotExist) Unwrap() error {
return util.ErrNotExist
}
// Redirect represents that a repo name should be redirected to another
type Redirect struct {
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)
}
func (err ErrReleaseAlreadyExist) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrReleaseNotExist represents a "ReleaseNotExist" kind of error.
type ErrReleaseNotExist struct {
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)
}
func (err ErrReleaseNotExist) Unwrap() error {
return util.ErrNotExist
}
// Release represents a release of repository.
type Release struct {
ID int64 `xorm:"pk autoincr"`

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